> ## 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.

# APP 内接入指南

Subotiz 支持在自有 iOS / Android App 内完成支付。本页介绍 App 内接入的整体方案、集成模式选择、支付方式支持范围、集成步骤与限制规则。

<Info>
  **适用对象**：需要在自有 iOS / Android App 内集成 Subotiz 支付的开发者（客户端 + 服务端）。

  **您将完成**：在 App 内唤起 Subotiz 支付页、接收支付结果、以 webhook 确认订单。

  **关键前置**：已开通 Subotiz 商户账户并获取 Secret API Key 与 Webhook 签名密钥；具备可接收 webhook 的服务端环境。

  **范围**：App 内支付集成。不含 Web / H5 集成，不含支付方式的商户后台配置。
</Info>

## 方案概述

App 内支付采用 **link-to-checkout** 模式：您的服务端创建 Checkout Session，App 在**应用内**用系统浏览器打开 Subotiz 支付页。支付页以浮层形式呈现于 App 之上，**用户全程不离开您的 App**。支付完成后浏览器关闭并把控制权交回 App，订单状态以 webhook 为准。

```mermaid theme={null}
sequenceDiagram
    actor U as 顾客
    participant A as 您的 App
    participant Y as 您的服务端
    participant S as Subotiz
    participant P as in-app 浏览器

    U->>A: 点击购买
    A->>Y: 请求创建订单
    Y->>S: 创建 Checkout Session
    S-->>Y: session_id 与 session_url
    Y-->>A: session_url
    A->>P: 打开 session_url
    U->>P: 在支付页完成支付
    P->>A: 重定向到 return_url 浏览器关闭
    S-->>Y: webhook 推送支付结果 权威
    A->>Y: 查询订单状态
    Y-->>A: 订单状态
    A->>U: 更新界面
```

整个集成由四步组成，各端职责如下。

| 步骤 | 动作                  | 由谁实现   | 产出            |
| :- | :------------------ | :----- | :------------ |
| 1  | 创建 Checkout Session | 您的服务端  | `session_url` |
| 2  | 在应用内打开支付页           | 您的 App | 顾客看到支付页       |
| 3  | 接收返回                | 您的 App | 浏览器关闭，回到 App  |
| 4  | 确认订单                | 您的服务端  | 权威的订单状态       |

<Tip>
  **该模式下您的 App 不包含任何支付逻辑**——不持有 Secret API Key、不接触卡数据，**不进入 PCI 合规范围**。

  支付方式由商户后台开通即生效，**新增支付方式无需 App 发版**；支付页由系统浏览器驱动，自动共享 Cookie 与完整 Web 平台能力，**钱包与 3DS 的兼容性优于其他容器**。
</Tip>

### 三条设计原则

1. **Session 由服务端创建。** App 不持有 Secret API Key；金额与商品由服务端决定，防止客户端篡改。
2. **Webhook 为订单状态唯一权威来源。** 发货与开通权益只依据 webhook，**不依据 App 收到的返回**。返回仅用于触发 UI。
3. **必须使用系统 in-app 浏览器。** 共享系统 Cookie、支持 Apple Pay / Google Pay；不要用可注入 JS 的裸 WebView 承载支付页。

## 先做选择：集成模式

App 场景有两种集成模式，**两者的 Apple Pay 支持范围不同**，请在开发前确定。

| 集成模式                                                  | Apple Pay 支持范围                                | 是否打开新窗口 | 需自建宿主页 |
| :---------------------------------------------------- | :-------------------------------------------- | :------ | :----- |
| [**托管式（Hosted）**](/zh/integration/hosted)　推荐          | ✅ 支持全部 iOS 版本                                 | ✅ 是     | 否      |
| [嵌入式表单（Embedded Form）](/zh/integration/embedded-form) | ⚠️ **仅 iOS 17 及以上**；iOS 17 以下 Apple Pay 按钮不渲染 | ❌ 否     | 是      |

<Warning>
  **不支持 App 原生页面集成。** Subotiz Checkout 的三种集成形态均**不提供在 App 原生页面内直接渲染支付表单或钱包按钮的能力**，支付必须经由系统 in-app 浏览器承载。
</Warning>

<Tip>
  **App 场景统一推荐托管式。**

  若您的用户中存在 iOS 17 以下设备，且 Apple Pay 是主要支付方式，**必须使用托管式**。

  嵌入式还需您自建宿主页并实现跨窗口通信；而 App 内托管式本就以浮层呈现、用户不离开 App，嵌入式「不跳出自有页面」的价值在此场景并不成立。
</Tip>

## 支持的支付方式

下表为 App 内浏览器场景的支持情况。**实际可用的支付方式由您的商户配置决定，请以创建 session 接口返回的 `payment_methods` 为准。**

| 支付方式                                                   | 能力归属                | iOS | Android | 说明                                                                                      |
| :----------------------------------------------------- | :------------------ | :-- | :------ | :-------------------------------------------------------------------------------------- |
| Card（卡）                                                | 自有支付                | ✅   | ✅       | 支持的卡组织以创建 session 接口返回的 `icon_list` 为准，常见包括 Visa / Mastercard / Amex / JCB / UnionPay 等 |
| **Apple Pay**                                          | 自有支付                | ✅   | —       |                                                                                         |
| **Google Pay**                                         | 自有支付                | ✅   | ✅       |                                                                                         |
| Affirm                                                 | 自有支付<br />视通道而定     | ✅   | ✅       |                                                                                         |
| Afterpay                                               | 自有支付<br />视通道而定     | ✅   | ✅       |                                                                                         |
| Klarna                                                 | 自有支付<br />视通道而定     | ✅   | ✅       |                                                                                         |
| **PayPal**<br />含 PayPal 余额 / ACDC / BCDC / Google Pay | **三方渠道**<br />需单独开通 | ✅   | ✅       |                                                                                         |
| **PayPal · Apple Pay**                                 | **三方渠道**<br />需单独开通 | ✅   | —       |                                                                                         |

<Info>
  **自有支付的底层清算通道不同，可用支付方式也不同**——标注「视通道而定」的支付方式并非所有商户都可用。**请务必以创建 session 接口返回的 `payment_methods` 为准，不要按本表硬编码支付方式列表。**

  其他三方渠道（Airwallex、Checkout、Oceanpayment 等），App 内支持情况请联系 Subotiz 确认。
</Info>

## 集成步骤

<Steps>
  <Step title="服务端准备">
    在服务端配置以下环境变量，**不要打包进 App**。

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

  <Step title="第 1 步：创建 Checkout Session（服务端）">
    App 点击购买时，由**您的服务端**调用 [Subotiz API](/zh/api/introduction-1) 创建 [Checkout Session](/zh/api/v1-checkout-session-create-checkout-session)。`return_url` 与 `cancel_url` 请设为您自有域名下的返回地址。

    ```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"
    	}'
    ```

    #### 关键参数

    * `order_id`：为接入方订单 ID，用于后续关联业务数据
    * `integration_method`：设置为 `hosted`，表示使用托管式页面模式接入
    * `return_url`：顾客支付成功之后跳转的页面，App 场景下应指向您的 Universal Link / App Link
    * `cancel_url`：顾客取消支付时跳转的页面

    **可观察结果**：接口返回 `session_url`（即 `checkoutUrl`），形如 `https://checkout.subotiz.com/m/{mid}/checkout/{sessionId}`。

    <Warning>
      Secret API Key 与 Webhook 签名密钥**仅存于服务端，绝不打包进 App**。金额与商品必须由服务端决定。
    </Warning>
  </Step>

  <Step title="第 2 步：在应用内打开支付页（App）">
    App 拿到 `session_url` 后，用系统提供的 in-app 浏览器容器打开。

    | 平台      | 使用的容器                                                |
    | :------ | :--------------------------------------------------- |
    | iOS     | `SFSafariViewController`（建议以 `.sheet` 呈现，覆盖在 App 之上） |
    | Android | Chrome Custom Tabs（依赖 `androidx.browser:browser`）    |

    <Warning>
      **禁止使用 `WKWebView` / Android `WebView` 承载支付页。** 原因：

      1. `ApplePaySession` 在 WKWebView 中因安全源（security origin）限制无法发起，**Apple Pay 直接不可用**；
      2. 3DS、OAuth 等重定向类认证在 WebView 内不被推荐，受内核碎片化影响易失败或卡死；
      3. 宿主可注入 JS 窃取支付输入，**会扩大您的 PCI 合规范围**；
      4. 不共享浏览器会话，**多数支付方式不支持该场景**——Link / PayPal / Klarna / Amazon Pay 在应用内 WebView 下均不可用，Google Pay 需额外完成 Android WebView 配置才可能支持。
    </Warning>

    **可观察结果**：支付页以浮层覆盖在 App 之上，用户可见支付方式列表。
  </Step>

  <Step title="第 3 步：接收返回（App）">
    支付完成后，Subotiz 会重定向到您创建 Session 时传入的 `return_url`（或 `cancel_url`），in-app 浏览器随即关闭、控制权回到 App。

    * iOS：在承载支付页的视图上实现 `onOpenURL`，收到返回后关闭 Safari 视图。
    * Android：在 `AndroidManifest.xml` 注册接收返回的 Activity（`launchMode="singleTask"` + `autoVerify` 的 `intent-filter`）。

    | 关注点        | 要求                                                                         |
    | :--------- | :------------------------------------------------------------------------- |
    | **返回地址形式** | 生产环境请使用 **Universal Link**（iOS）/ **App Link**（Android）；custom scheme 仅用于调试 |
    | **返回地址内容** | 仅携带 `orderId` 与粗粒度 `result`，**不得携带金额等敏感信息**                                |
    | **如何使用返回** | **不要信任返回里的 `result`**，它只用于触发 UI；请据此进入第 4 步查询真实状态                           |
  </Step>

  <Step title="第 4 步：确认订单（服务端 + App）">
    **服务端实现 webhook 端点**，这是订单状态的唯一权威来源。处理要点三项：

    1. **验签**——按 [Webhook 可靠性验证](/zh/webhook/introduction-2)校验 `X-Signature`，验签需要**原始 body**，不要先做 JSON 解析；
    2. **幂等去重**——按事件 id 去重，同一事件可能重复推送；
    3. **快速返回 200**——业务处理耗时请异步化，不要阻塞响应。

    **App 侧实现轮询**：收到返回后调用您的服务端接口查询订单状态，直到状态为终态或超时。

    <Tip>
      webhook 与返回是**两条独立路径**。即便返回未能触达（用户手动关闭浏览器、网络异常等），webhook 仍会把订单置为已支付，用户下次进入 App 时轮询即可获得正确状态。**因此轮询不是可选项。**
    </Tip>
  </Step>
</Steps>

## 限制规则

### 强制要求

| 序  | 规则                               | 说明                                                                           |
| :- | :------------------------------- | :--------------------------------------------------------------------------- |
| R1 | 必须使用系统 in-app 浏览器                | `SFSafariViewController` / Chrome Custom Tabs；禁止 WKWebView 与 Android WebView |
| R2 | 全程 HTTPS                         | 支付页与返回地址均需 HTTPS                                                             |
| R3 | Session 必须由服务端创建                 | Secret API Key 不得打包进 App                                                     |
| R4 | 订单状态以 webhook 为准                 | 发货与开通权益不得依据 App 收到的返回                                                        |
| R5 | 返回地址不得携带敏感信息                     | 仅 `orderId` 与粗粒度 `result`                                                    |
| R6 | 生产环境使用 Universal Link / App Link | custom scheme 仅用于调试                                                          |
| R7 | 必须实现订单状态轮询                       | 返回可能丢失，轮询是唯一兜底                                                               |

### 建议与说明

| 序  | 建议与说明                             | 说明与应对                                                              |
| :- | :-------------------------------- | :----------------------------------------------------------------- |
| L1 | **嵌入式表单下 iOS 17 以下不支持 Apple Pay** | Apple 官方明确不支持，按钮不会渲染。**应对：改用托管式接入**（托管式支持全部 iOS 版本）                |
| L2 | **金额低于渠道最小扣款额时，钱包按钮不渲染**          | 表现为按钮「消失」而非报错，不要误判为集成故障。**具体阈值因清算通道与结算币种而异**，请与 Subotiz 确认您所在通道的限额 |
| L3 | iOS 的 Google Pay 无原生路径            | Google 不提供 iOS 原生 SDK，该组合只能经系统浏览器承载                                |
| L4 | 不支持 App 原生页面集成                    | 支付必须经系统 in-app 浏览器承载，无法在 App 原生页面内直接渲染支付表单或钱包按钮                    |
| L5 | PayPal 渠道的 Apple Pay 建议商家展示名使用英文  | —                                                                  |

## 完成验证

集成完成后，请逐项确认：

* 服务端能成功创建 Session 并返回 `session_url`
* App 内打开支付页时以浮层呈现，用户未跳出 App
* 支付页上可见预期的支付方式（与 `payment_methods` 一致）
* iOS 真机上 Apple Pay 按钮可见并可唤起（测试金额需高于所在通道的最小扣款额，见 L2）
* 支付成功后浏览器自动关闭并回到 App
* 服务端能收到 webhook，验签通过，重复推送不产生重复发货
* **手动关闭浏览器不触发返回**时，App 重新进入后仍能通过轮询获得正确订单状态
* 取消支付后订单状态正确，未误开权益
