> ## Documentation Index
> Fetch the complete documentation index at: https://docs.gr4vy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Trustly (United States)

> Connect to Trustly to accept bank payments in the United States.

<Note>
  For non-United States payments please use the [Trustly Europe](./trustlyeurope-trustlyeurope) connector.
</Note>

## Setup

When setting up Trustly in the dashboard, configure the
following credentials, which can all be obtained from Trustly after the account has been created.

* **Access ID** - The identifier that acts as a username when connecting to the Trustly API.
* **Access Key** - The key that acts as a password when connecting to the Trustly API.
* **Merchant ID** - The ID that is used to target the specific merchant account to process payments for.

## Integration

### Redirect integration

The default integration for Trustly is through a redirect to a hosted payments page.

<Tabs>
  <Tab title="Redirect">
    Start by creating a new transaction with the following required fields.

    ```json POST /transactions theme={"system"}
    {
        "amount": 100,
        "currency": "USD",
        "country": "US",
        "intent": "capture",
        "payment_method": {
            "method": "trustly",
            "redirect_url": "https://example.com/callback",
            "country": "US",
            "currency": "USD"
        }
    }
    ```

    After the transaction is created, the API response includes a `payment_method.approval_url` and the transaction is set to `buyer_approval_pending`.

    ```json theme={"system"}
    {
        "type": "transaction",
        "id": "ea1efdd0-20f9-44d9-9b0b-0a3d71e9b625",
        "payment_method": {
            "type": "payment-method",
            "approval_url": "https://cdn.sandbox.spider.gr4vy.app/connectors/trustly/trustly.html?token=...",
           ...
        },
        "method": "trustly",
       ...
    }
    ```

    Redirect the buyer to the `approval_url` so they can complete authentication and approve the payment.
    After approval the buyer is redirected to the `redirect_url` provided when creating the transaction.
    Do not rely solely on the redirect - either poll the transaction or (recommended) rely on webhooks to detect the final status (for example `capture_succeeded` or failure states).
    In sandbox, use the **Demo Bank** and any credentials to simulate a successful flow.
  </Tab>

  <Tab title="Webviews (mobile)">
    For applications that use a multi-platform architecture, in which a web-based checkout experience is
    embedded in a mobile app via a Webview, attention must be paid to ensure Trustly is integrated successfully.

    In addition to the [Trustly guide](https://amer.developers.trustly.com/payments/reference/webview) on this topic,
    support has been added to pass the `urlScheme` required for this integration. When passing this value, this is forwarded to Trustly, as well
    as the `integrationContext`, allowing for the integration to listen to webhooks.

    In short, the flow would be as follows.

    * When creating a Trustly transaction, pass the `urlScheme = "YOUR_APP://SOME_RESOURCE"` property [to the API](/reference/transactions/new-transaction#body-connection-options-trustly-trustly).
    * In the application, load the `approval_url` provided from the API into the application's webview.
    * The `urlScheme` as well as the right `integrationContext` are passed to the `establishData` as defined in the Trustly guide.
    * The application listens to all the various callbacks from Trustly as defined in the Trustly guide.
    * When the transaction completes, the app invokes `window.Trustly.proceedToChooseAccount();` on the webview as defined in the guide.
    * The hosted page gets notified by the Trustly lightbox and completes the payment.
  </Tab>
</Tabs>

### Direct integration

Trustly provides [web](https://amer.developers.trustly.com/payments/docs/sdk) and [mobile SDKs](https://amer.developers.trustly.com/payments/reference/sdks) for an in-context (direct) integration. For these flows, indicate the platform by setting an appropriate `integration_client`
when creating the transaction, and then build a client-side integration that uses the [`POST /transactions/:transaction_id/session`](/reference/transactions/get-transaction-session) API to initialize the Trustly SDK.

To start, create a new transaction with the appropriate `integration_client`.

<CodeGroup>
  ```json Web theme={"system"}
  POST /transactions

  {
      "amount": 100,
      "currency": "USD",
      "country": "US",
      "intent": "capture",
      "integration_client": "web",
      "payment_method": {
          "method": "trustly",
          "redirect_url": "https://example.com/callback",
          "country": "US",
          "currency": "USD"
      }
  }
  ```

  ```json iOS theme={"system"}
  POST /transactions

  {
      "amount": 100,
      "currency": "USD",
      "country": "US",
      "intent": "capture",
      "integration_client": "ios",
      "payment_method": {
          "method": "trustly",
          "redirect_url": "yourapp://",
          "country": "US",
          "currency": "USD"
      },
  }
  ```

  ```json Android theme={"system"}
  POST /transactions

  {
      "amount": 100,
      "currency": "USD",
      "country": "US",
      "intent": "capture",
      "integration_client": "android",
      "payment_method": {
          "method": "trustly",
          "redirect_url": "yourapp://",
          "country": "US",
          "currency": "USD"
      },
  }
  ```
</CodeGroup>

After the transaction is created, the API response includes a `session_token` which can be used to get the [session data](/reference/transactions/get-transaction-session) for that transaction.

```sh theme={"system"}
POST /transactions/:transaction_id/session?token=:session_token
```

<CodeGroup>
  ```json Web theme={"system"}
  {
      "session_data": {
          "baseUrl": "https://sandbox.trustly.one",
          "establishData": {
              "amount": "0.00",
              "accessId": "Ev6e2YzsthnCLEefTdYs",
              "currency": "USD",
              "customer": {},
              "cancelUrl": "https://api.sandbox.spider.gr4vy.app/transactions/approve/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.cj9hcFjrQh6fPVGFQ2KVtw.MXnZvx-h4H5uWucMDQSXoM0KhZb9n0rl-S3KwxaX_to?gr4vy_store=false&cancel=true",
              "returnUrl": "https://api.sandbox.spider.gr4vy.app/transactions/approve/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.cj9hcFjrQh6fPVGFQ2KVtw.MXnZvx-h4H5uWucMDQSXoM0KhZb9n0rl-S3KwxaX_to?gr4vy_store=false",
              "merchantId": "1019347005",
              "description": null,
              "paymentType": "Deferred",
              "notificationUrl": "https://api.sandbox.spider.gr4vy.app/i/hWmg8tTgTA6MzV_7mz7-QnRydXN0bHktdHJ1c3RseQ/7XRgAbqH5nMLp9Xhg5err3odYmF7_07HYKHNcg9nSaI",
              "requestSignature": "2T1o/igBnnjYMYIf9g5MBE4nIb8=",
              "merchantReference": "3Ta5b7GDKE83rCysLkfFbD"
          }
      },
      "default_completion_url": "https://api.sandbox.spider.gr4vy.app/transactions/approve/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.cj9hcFjrQh6fPVGFQ2KVtw.MXnZvx-h4H5uWucMDQSXoM0KhZb9n0rl-S3KwxaX_to?gr4vy_store=false",
      "integration_client": "web"
  }
  ```

  ```json iOS theme={"system"}
  {
      "session_data": {
          "baseUrl": "https://sandbox.trustly.one",
          "establishData": {
              "amount": "0.00",
              "accessId": "Ev6e2YzsthnCLEefTdYs",
              "currency": "USD",
              "customer": {},
              "metadata": {
                  "urlScheme": "yourapp://"
              },
              "merchantId": "1019347005",
              "description": null,
              "paymentType": "Deferred",
              "notificationUrl": "https://api.sandbox.spider.gr4vy.app/i/hWmg8tTgTA6MzV_7mz7-QnRydXN0bHktdHJ1c3RseQ/7XRgAbqH5nMLp9Xhg5err3odYmF7_07HYKHNcg9nSaI",
              "requestSignature": "AxsiNyiaG5qu2tyJxHKKoJsPF9w=",
              "merchantReference": "1eWc74qJyrX9OlbVInQWhD"
          }
      },
      "default_completion_url": "https://api.sandbox.spider.gr4vy.app/transactions/approve/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.NlVnD1xDQbivneh49nnZNw.Auq5dMJQ7UpVho9AhoUnXrXyZllxVdyq34dHVH6kRAE?gr4vy_store=false",
      "integration_client": "ios"
  }
  ```

  ```json Android theme={"system"}
  {
      "session_data": {
          "baseUrl": "https://sandbox.trustly.one",
          "establishData": {
              "amount": "0.00",
              "accessId": "Ev6e2YzsthnCLEefTdYs",
              "currency": "USD",
              "customer": {},
              "metadata": {
                  "urlScheme": "yourapp://"
              },
              "merchantId": "1019347005",
              "description": null,
              "paymentType": "Deferred",
              "notificationUrl": "https://api.sandbox.spider.gr4vy.app/i/hWmg8tTgTA6MzV_7mz7-QnRydXN0bHktdHJ1c3RseQ/7XRgAbqH5nMLp9Xhg5err3odYmF7_07HYKHNcg9nSaI",
              "requestSignature": "AxsiNyiaG5qu2tyJxHKKoJsPF9w=",
              "merchantReference": "1eWc74qJyrX9OlbVInQWhD"
          }
      },
      "default_completion_url": "https://api.sandbox.spider.gr4vy.app/transactions/approve/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.NlVnD1xDQbivneh49nnZNw.Auq5dMJQ7UpVho9AhoUnXrXyZllxVdyq34dHVH6kRAE?gr4vy_store=false",
      "integration_client": "android"
  }
  ```
</CodeGroup>

This session data provides the `establishData` required to load the Trustly SDK.

<CodeGroup>
  ```js Web theme={"system"}
  // Determine the URL of the script, and the method to invoke on the Trustly SDK
  const { establishData, baseUrl } = response.session_data
  const method = establishData.paymentType === "Verification" ? 'establish' : 'selectBankWidget'
  const url = `${baseUrl}/start/scripts/trustly.js?accessId=${establishData.accessId}`

  // Dynamically load the script with the access ID
  const script = document.createElement("script");
  script.src = url;
  script.onload = () => {
      // Initialize the Trustly SDK and attach it to the #component
      Trustly[method](establishData, {
          hideCloseButton: true,
          dragAndDrop: true,
          widgetContainerId: "component",
      });
  };

  document.head.appendChild(script);
  ```

  ```swift iOS theme={"system"}
  // https://amer.developers.trustly.com/payments/reference/ios
  var establishData:Dictionary<AnyHashable,Any>?

  override func viewDidLoad() {
          super.viewDidLoad()

          let widgetVC = WidgetViewController(establishData: establishData)
          widgetVC.delegate = self

          widgetVC.view.frame = CGRect(x: 16, y: 220, width: 350, height: 500)
          view.addSubview(widgetVC.view)
  }
  ```

  ```kotlin Android theme={"system"}
  // https://amer.developers.trustly.com/payments/reference/android
  // MainActivity.kt

  val establishData = EstablishData.getEstablishDataValues().toMutableMap()

  val connectTrustlyButton = findViewById<AppCompatButton>(R.id.btnConnectWithMyBank)
  connectTrustlyButton.setOnClickListener {
      val intent = Intent(this@MainActivity, LightboxActivity::class.java)
      intent.putExtra(LightboxActivity.ESTABLISH_DATA, establishData as Serializable)
      startActivity(intent)
  }
  ```
</CodeGroup>

Please refer to the Trustly documentation for the [web](https://amer.developers.trustly.com/payments/docs/sdk) and [mobile SDKs](https://amer.developers.trustly.com/payments/reference/sdks) for further guidance.

<Card icon="github" cta="Visit GitHub" href="https://github.com/gr4vy/sample-trustly-direct-web">
  View a complete, ready-to-run example for direct Trustly Web SDK integration on GitHub. The repository includes setup instructions, session initialization, sample client code for loading the Trustly script, and guidance for handling redirects and webhooks.
</Card>

#### Complete the transaction

<Badge color="green">Recommended</Badge>

On mobile integrations, after the buyer completes the payment flow, the Trustly SDK provides the developer with the `transactionId` and `status`.

The system automatically syncs the status through webhooks, but to complete the transaction it is also recommended to send a `GET` request to the `default_completion_url`
provided in the session response with the `transactionId` and the `status` as query parameters to finalize the transaction. This also moves the transaction
to a completed status without waiting for the webhook to be sent.

The call returns a `204 No Content` response. After receiving this response, you can also optionally fetch the transaction to confirm final status.

<CodeGroup>
  ```kotlin Android theme={"system"}
  // Define two functions to handle callbacks and pass them into the onReturn and onCancel parameters of the establish method.

  lightboxView.establish(establishData)
        .onReturn(
            (TrustlyCallback { lightboxView: Trustly, returnData: Map<String, String> ->
              val transactionId = returnData["transactionId"]
              val status = returnData["status"]

              // append transactionId and status to the `default_completion_url`
              val uri = Uri.parse(defaultCompletionUrl)
                  .buildUpon()
                  .appendQueryParameter("transactionId", transactionId)
                  .appendQueryParameter("status", status)
                  .build()

              val approvalUrl = URL(uri.toString())

              // placeholder function to call the approval url
              makeRequest(approvalUrl)

              redirectToScreen(Callback.RETURN)
            })
        ).onCancel(
            (TrustlyCallback { lightboxView: Trustly, cancelData: Map<String, String> ->
                redirectToScreen(Callback.CANCEL)
            })
        )

  ```

  ```swift iOS theme={"system"}
  // Define two functions to handle callbacks and pass them into the onReturn and onCancel parameters of the establish method.

  var establishData:Dictionary<AnyHashable,Any>?
  var amount: String?
  var delegate: TrustlyLightboxViewProtocol?

  override func viewDidLoad() {
      super.viewDidLoad()
      
      let trustlyLightboxPanel = TrustlyView()
      
      guard let amountText = amount else { return }
      
      self.establishData = [
        "accessId": YOUR_ACCESS_ID,
        "merchantId": YOUR_MERCHANT_ID,
        "requestSignature": GENERATED_HASH,
        "description": "transaction description",
        "merchantReference": YOUR_UNIQUE_TRANSACTION_REF,
        "amount": "0.00",
        "paymentType": "Deferred",
        "currency": "USD",
        "metadata.lang": "en_US",
        "metadata.urlScheme": RETURN_URL
      ]
      
      self.view = trustlyLightboxPanel.establish(self.establishData, onReturn: {(trustly, returnParameters)->Void in
          let response = returnParameters as! [String:String]

          let status = response["status"]
          let transactionId = response["transactionId"]

          // Use URLComponents to safely build the URL with query parameters
          var components = URLComponents(string: defaultCompletionUrl)

          // Add transactionId and status as query parameters
          components.queryItems = [
              URLQueryItem(name: "transactionId", value: transactionId),
              URLQueryItem(name: "status", value: status)
          ]

          // Constructed approval URL
          let approvalURL = components.url

          // placeholder function to call the approval url
          self.makeRequest(url: approvalURL)
          
          self.delegate?.onReturnWithTransactionId(transactionId: response["transactionId"]!, controller: self)
      }, onCancel: {(trustly, returnParameters)->Void in
          let response = returnParameters as! [String:String]
          self.delegate?.onCancelWithTransactionId(transactionId: response["transactionId"]!, controller: self)
      })
  }
  ```
</CodeGroup>

For more information refer to the [iOS](https://amer.developers.trustly.com/payments/reference/ios#add-callback-functions) and [Android](https://amer.developers.trustly.com/payments/reference/android#add-callback-functions) documentation.

## Limitations

* Most API calls in Trustly are asynchronous and therefore captures and refunds are in a pending
  state until a notification is received. It's important to set up webhooks from the environment to yours in
  order to be notified of these status changes.
* Partial refunds are supported. However, another refund cannot be initiated while there is an
  outstanding in-progress refund.
* `payment_service_transaction_id` could change during the transaction lifecycle. Use the `additional_identifiers` object to get a stable reference to the PSP transaction ID.

## Recurring payments

This connector supports recurring payments via the API and via Embed. If using Embed,
most of the complexity is handled, but for direct API integrations it's important to ensure
the right recurring payment flags are sent on the initial and subsequent payments.

### Initial payment

For an initial recurring payment, please make sure to use a suitable `payment_source` (either `recurring` or `card_on_file`)
and the `merchant_initiated` and `is_subsequent_payment` flags are set to `false`. This ensures a customer-present flow is
triggered and a payment method is created that keeps track of Trustly's split token.

```json POST /transactions theme={"system"}
{
    "amount": 1299,
    "payment_method": {
        "method": "trustly",
        "redirect_url": "https://example.com/callback",
        "country": "US",
        "currency": "USD"
    },
    "country": "US",
    "currency": "USD",
    "intent": "capture",
    "buyer_id": "9cedaaea-68fe-4f07-bf94-55225be98d0f",
    "store": true,
    "payment_source": "recurring",
    "merchant_initiated": false,
    "is_subsequent_payment": false
}
```

### Subsequent payment

For a subsequent recurring payment, please make sure to use the same `payment_source` (either `recurring` or `card_on_file`)
and the `merchant_initiated` and `is_subsequent_payment` flags are set to `true`. This ensures a customer-not-present flow is
triggered with the stored split token.

```json POST /transactions theme={"system"}
{
    "amount": 1299,
    "payment_method": {
        "method": "id",
        "id": "c31a7f72-dac4-469b-a00a-5e3607f57b01"
    },
    "country": "US",
    "currency": "USD",
    "intent": "capture",
    "buyer_id": "9cedaaea-68fe-4f07-bf94-55225be98d0f",
    "payment_source": "recurring",
    "merchant_initiated": true,
    "is_subsequent_payment": true
}
```

### Renewing split token

In some situations, a recurring payment may fail because the Trustly "split token" has expired.

```json theme={"system"}
{
    "type": "transaction",
    "id": "285771d9-afc1-4beb-a368-06e2cb03ec3a",
    "status": "authorization_declined",
    "method": "trustly",
    "raw_response_code": "326",
    "raw_response_description": "Expired Split Token",
    ...
}
```

In this situation, reach out to the customer to renew their agreement. This can be done by
setting the `refreshSplitToken` on a new customer-present transaction. It is important in this request to
set a `redirect_url` to redirect the user back to the site after they have approved the transaction.

```json theme={"system"}
{
    "amount": 1299,
    "payment_method": {
        "method": "id",
        "redirect_url": "https://example.com/callback",
        "id": "c31a7f72-dac4-469b-a00a-5e3607f57b01"
    },
    "country": "US",
    "currency": "USD",
    "intent": "authorize",
    "buyer_id": "9cedaaea-68fe-4f07-bf94-55225be98d0f",
    "payment_source": "recurring",
    "merchant_initiated": false,
    "is_subsequent_payment": true,
    "connection_options": {
        "trustly-trustly": {
            "refreshSplitToken": true
        }
    }
}
```
