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

# In-App Integration Guide

Subotiz supports completing payments inside your own iOS / Android app. This page covers the overall approach, how to choose an integration mode, the supported payment methods, the integration steps, and the constraints that apply.

<Info>
  **Who this is for**: Developers (client + server) who need to integrate Subotiz payments inside their own iOS / Android app.

  **What you will build**: Launching the Subotiz checkout page inside your app, receiving the payment result, and confirming the order via webhook.

  **Prerequisites**: A Subotiz merchant account with a Secret API Key and a webhook signing key; a server environment capable of receiving webhooks.

  **Scope**: In-app payment integration. Web / H5 integration and merchant-dashboard payment method configuration are out of scope.
</Info>

## Overview

In-app payments use the **link-to-checkout** model: your server creates a Checkout Session, and your app opens the Subotiz checkout page with the system browser **inside the app**. The checkout page is presented as an overlay on top of your app, so **the user never leaves your app**. Once payment is complete the browser closes and hands control back to the app; the order status is determined by the webhook.

```mermaid theme={null}
sequenceDiagram
    actor U as Customer
    participant A as Your App
    participant Y as Your Server
    participant S as Subotiz
    participant P as in-app browser

    U->>A: Tap buy
    A->>Y: Request order creation
    Y->>S: Create Checkout Session
    S-->>Y: session_id and session_url
    Y-->>A: session_url
    A->>P: Open session_url
    U->>P: Complete payment on checkout page
    P->>A: Redirect to return_url browser closes
    S-->>Y: webhook pushes payment result authoritative
    A->>Y: Query order status
    Y-->>A: Order status
    A->>U: Update UI
```

The integration consists of four steps, with responsibilities split as follows.

| Step | Action                        | Implemented by | Output                          |
| :--- | :---------------------------- | :------------- | :------------------------------ |
| 1    | Create a Checkout Session     | Your server    | `session_url`                   |
| 2    | Open the checkout page in-app | Your app       | Customer sees the checkout page |
| 3    | Receive the return            | Your app       | Browser closes, back in the app |
| 4    | Confirm the order             | Your server    | Authoritative order status      |

<Tip>
  **In this model your app contains no payment logic** — it never holds the Secret API Key and never touches card data, so it **stays out of PCI scope**.

  Payment methods take effect as soon as they are enabled in the merchant dashboard, so **adding one requires no app release**; and because the checkout page is driven by the system browser, it automatically shares cookies and the complete web platform, so **wallets and 3DS are more compatible than in other containers**.
</Tip>

### Three Design Principles

1. **The session is created by your server.** The app never holds the Secret API Key; amounts and products are decided server-side, which prevents client-side tampering.
2. **The webhook is the single source of truth for order status.** Fulfillment and entitlement activation must rely on the webhook, **not on the return received by the app**. The return only triggers UI.
3. **You must use the system in-app browser.** It shares system cookies and supports Apple Pay / Google Pay. Do not host the checkout page in a bare WebView that allows JS injection.

## First, Choose an Integration Mode

There are two integration modes for the app scenario, and **their Apple Pay support differs**. Decide before you start development.

| Integration mode                                 | Apple Pay support                                                                  | Opens a new window | Requires your own host page |
| :----------------------------------------------- | :--------------------------------------------------------------------------------- | :----------------- | :-------------------------- |
| [**Hosted**](/en/integration/hosted)　Recommended | ✅ All iOS versions                                                                 | ✅ Yes              | No                          |
| [Embedded Form](/en/integration/embedded-form)   | ⚠️ **iOS 17 and above only**; on iOS below 17 the Apple Pay button does not render | ❌ No               | Yes                         |

<Warning>
  **Integration into native app screens is not supported.** None of the three Subotiz Checkout integration forms **can render a payment form or wallet button directly inside a native app screen**. Payment must be hosted by the system in-app browser.
</Warning>

<Tip>
  **For the app scenario, Hosted is the recommended mode across the board.**

  If any of your users are on devices below iOS 17 and Apple Pay is a primary payment method, **you must use Hosted** (Hosted supports all iOS versions).

  The Embedded Form additionally requires you to build your own host page and implement cross-window communication. And since Hosted already appears as an overlay inside the app without the user leaving it, the Embedded Form's "stay on your own page" advantage does not apply here.
</Tip>

## Supported Payment Methods

The table below reflects support in the in-app browser scenario. **The payment methods actually available are determined by your merchant configuration, so rely on the `payment_methods` returned by the create session API.**

| Payment method                                                      | Capability owner                                          | iOS | Android | Notes                                                                                                                                                           |
| :------------------------------------------------------------------ | :-------------------------------------------------------- | :-- | :------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Card                                                                | Subotiz Payments                                          | ✅   | ✅       | The supported card networks are determined by the `icon_list` returned by the create session API; common ones include Visa / Mastercard / Amex / JCB / UnionPay |
| **Apple Pay**                                                       | Subotiz Payments                                          | ✅   | —       |                                                                                                                                                                 |
| **Google Pay**                                                      | Subotiz Payments                                          | ✅   | ✅       |                                                                                                                                                                 |
| Affirm                                                              | Subotiz Payments<br />Depends on the acquiring channel    | ✅   | ✅       |                                                                                                                                                                 |
| Afterpay                                                            | Subotiz Payments<br />Depends on the acquiring channel    | ✅   | ✅       |                                                                                                                                                                 |
| Klarna                                                              | Subotiz Payments<br />Depends on the acquiring channel    | ✅   | ✅       |                                                                                                                                                                 |
| **PayPal**<br />Including PayPal balance / ACDC / BCDC / Google Pay | **Third-party channel**<br />Requires separate enablement | ✅   | ✅       |                                                                                                                                                                 |
| **PayPal · Apple Pay**                                              | **Third-party channel**<br />Requires separate enablement | ✅   | —       |                                                                                                                                                                 |

<Info>
  **Subotiz Payments runs on different underlying acquiring channels, and the available payment methods differ accordingly** — methods marked "Depends on the acquiring channel" are not available to every merchant. **Always rely on the `payment_methods` returned by the create session API; do not hardcode a payment method list from this table.**

  For other third-party channels (Airwallex, Checkout, Oceanpayment, and so on), please contact Subotiz to confirm in-app support.
</Info>

## Integration Steps

<Steps>
  <Step title="Server Setup">
    Configure the following environment variables on your server. **Do not bundle them into the app.**

    ```bash theme={null}
    SUBOTIZ_API_BASE=https://api.subotiz.com
    SUBOTIZ_SECRET_KEY={SECRET_KEY}
    SUBOTIZ_WEBHOOK_SECRET={WEBHOOK_SECRET}
    ```
  </Step>

  <Step title="Step 1: Create a Checkout Session (server)">
    When the customer taps buy in the app, **your server** calls the [Subotiz API](/en/api/introduction-1) to create a [Checkout Session](/en/api/v1-checkout-session-create-checkout-session). Set `return_url` and `cancel_url` to return addresses on your own domain.

    ```bash theme={null}
    curl --location 'https://api.subotiz.com/api/v1/session' \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer {your_api_key}' \
    --header 'Request-Id: 9913dca8-90f8-4e20-98bc-565f0222ffa8' \
    --data-raw '{
    		"access_no":       "77d52a21dc032b4",
    		"sub_merchant_id": "2816433",
    		"order_id":        "123e4567-zzzaa20daw11a",
        "payer_id": "customer_id_0012",
    		"line_items": [
    			{
    				"price_id": "543321366326164797",
    				"quantity": "1"
    			}
            ],
    		"email":           "zhangsan@subotiz.com",
        "integration_method": "hosted",
        "cancel_url": "https://www.subotiz.com/cancel",
        "return_url": "https://www.subotiz.com/success"
    	}'
    ```

    #### Key Parameters

    * `order_id`: Your platform's internal order ID, used for subsequent business data correlation
    * `integration_method`: Set to `hosted` to use the hosted page integration method
    * `return_url`: The page the customer is redirected to after a successful payment; in the app scenario this should point to your Universal Link / App Link
    * `cancel_url`: The page the customer is redirected to if they cancel payment

    **Observable result**: The API returns `session_url` (the checkout URL), in the form `https://checkout.subotiz.com/m/{mid}/checkout/{sessionId}`.

    <Warning>
      The Secret API Key and the webhook signing key **live on the server only and must never be bundled into the app**. Amounts and products must be decided server-side.
    </Warning>
  </Step>

  <Step title="Step 2: Open the Checkout Page In-App (app)">
    Once the app has the `session_url`, open it with the in-app browser container provided by the system.

    | Platform | Container to use                                                                        |
    | :------- | :-------------------------------------------------------------------------------------- |
    | iOS      | `SFSafariViewController` (presenting it as a `.sheet` on top of the app is recommended) |
    | Android  | Chrome Custom Tabs (requires `androidx.browser:browser`)                                |

    <Warning>
      **Do not use `WKWebView` / Android `WebView` to host the checkout page.** Reasons:

      1. `ApplePaySession` cannot be started inside a WKWebView because of security origin restrictions, so **Apple Pay is simply unavailable**;
      2. Redirect-based authentication such as 3DS and OAuth is not recommended inside a WebView, and engine fragmentation makes it prone to failure or hanging;
      3. The host can inject JS to steal payment input, which **widens your PCI scope**;
      4. The browser session is not shared, so **most payment methods do not support this scenario** — Link / PayPal / Klarna / Amazon Pay are all unavailable in an in-app WebView, and Google Pay requires additional Android WebView configuration before it can work at all.
    </Warning>

    **Observable result**: The checkout page appears as an overlay on top of the app, and the customer can see the list of payment methods.
  </Step>

  <Step title="Step 3: Receive the Return (app)">
    After payment completes, Subotiz redirects to the `return_url` (or `cancel_url`) you passed when creating the session; the in-app browser then closes and control returns to the app.

    * iOS: Implement `onOpenURL` on the view hosting the checkout page, and dismiss the Safari view once the return arrives.
    * Android: Register the Activity that receives the return in `AndroidManifest.xml` (`launchMode="singleTask"` plus an `intent-filter` with `autoVerify`).

    | Concern                    | Requirement                                                                                                                     |
    | :------------------------- | :------------------------------------------------------------------------------------------------------------------------------ |
    | **Return address format**  | Use a **Universal Link** (iOS) / **App Link** (Android) in production; custom schemes are for debugging only                    |
    | **Return address content** | Carry only `orderId` and a coarse-grained `result`; **it must not carry the amount or other sensitive information**             |
    | **How to use the return**  | **Do not trust the `result` in the return**; it only triggers UI. Use it as the cue to move to Step 4 and query the real status |
  </Step>

  <Step title="Step 4: Confirm the Order (server + app)">
    **Implement a webhook endpoint on your server.** This is the single source of truth for order status. Three things to get right:

    1. **Verify the signature** — validate `X-Signature` as described in [Webhook reliability verification](/en/webhook/introduction-2). Verification needs the **raw body**, so do not parse the JSON first;
    2. **Deduplicate idempotently** — deduplicate by event id, because the same event may be delivered more than once;
    3. **Return 200 quickly** — move slow business processing off the request path instead of blocking the response.

    **Implement polling on the app side**: once the return arrives, call your own server to query the order status until it reaches a terminal state or times out.

    <Tip>
      The webhook and the return are **two independent paths**. Even if the return never arrives (the user manually closed the browser, a network error, and so on), the webhook still marks the order as paid, and the user gets the correct status from polling the next time they open the app. **Polling is therefore not optional.**
    </Tip>
  </Step>
</Steps>

## Constraints

### Mandatory Requirements

| No. | Rule                                                    | Description                                                                                 |
| :-- | :------------------------------------------------------ | :------------------------------------------------------------------------------------------ |
| R1  | The system in-app browser must be used                  | `SFSafariViewController` / Chrome Custom Tabs; WKWebView and Android WebView are prohibited |
| R2  | HTTPS end to end                                        | Both the checkout page and the return address must use HTTPS                                |
| R3  | The session must be created by the server               | The Secret API Key must not be bundled into the app                                         |
| R4  | Order status is determined by the webhook               | Fulfillment and entitlement activation must not rely on the return received by the app      |
| R5  | The return address must not carry sensitive information | Only `orderId` and a coarse-grained `result`                                                |
| R6  | Use Universal Link / App Link in production             | Custom schemes are for debugging only                                                       |
| R7  | Order status polling must be implemented                | The return may be lost, and polling is the only fallback                                    |

### Recommendations and Notes

| No. | Recommendation or note                                                                 | Explanation and what to do                                                                                                                                                                                                             |
| :-- | :------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| L1  | **Apple Pay is not supported below iOS 17 with the Embedded Form**                     | Apple explicitly does not support it, and the button will not render. **What to do: switch to the Hosted integration** (Hosted supports all iOS versions)                                                                              |
| L2  | **Wallet buttons do not render when the amount is below the channel's minimum charge** | The button "disappears" rather than raising an error, so do not mistake it for an integration failure. **The exact threshold varies by acquiring channel and settlement currency**, so confirm the limit for your channel with Subotiz |
| L3  | Google Pay on iOS has no native path                                                   | Google does not provide a native iOS SDK, so this combination can only be hosted by the system browser                                                                                                                                 |
| L4  | No integration into native app screens                                                 | Payment must be hosted by the system in-app browser; a payment form or wallet button cannot be rendered directly inside a native app screen                                                                                            |
| L5  | For the PayPal channel's Apple Pay, an English merchant display name is recommended    | —                                                                                                                                                                                                                                      |

## Verification Checklist

Once the integration is complete, confirm each of the following:

* The server can successfully create a session and return `session_url`
* Opening the checkout page in-app presents it as an overlay, and the user does not leave the app
* The expected payment methods are visible on the checkout page (matching `payment_methods`)
* The Apple Pay button is visible and can be launched on a real iOS device (the test amount must be above your channel's minimum charge, see L2)
* After a successful payment the browser closes automatically and returns to the app
* The server receives the webhook, signature verification passes, and repeated deliveries do not cause duplicate fulfillment
* When **the browser is closed manually and no return is triggered**, the app still gets the correct order status through polling after being reopened
* After a cancelled payment the order status is correct and no entitlement is granted by mistake
