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

# React Native

> 从 React Native 或 Expo 应用在系统浏览器视图中打开 Dodo Payments 托管结账，并通过一次调用获取类型化的结账结果。

> [!NOTE]
>
> 此页面介绍官方 Dodo Payments React Native 结账 SDK，`@dodopayments/react-native-checkout`。它会在原生浏览器视图中打开 Dodo Payments 托管结账，并返回类型化结果。较旧的软件包 `dodopayments-react-native-sdk`（未限定作用域）使用不同的 API。本页面仅介绍限定作用域的软件包。

> **[Checkout Sessions API](/developer-resources/checkout-session)**
>
> 从后端创建此 SDK 打开的 `checkout_url`。

> **[Mobile Integration Guide](/developer-resources/mobile-integration)**
>
> 了解此 SDK 如何融入完整的移动支付流程。

React Native SDK 是一个 Turbo Module，用于封装原生的 [iOS](/developer-resources/sdks/ios) 和 [Android](/developer-resources/sdks/android) 结账 SDK。它在 iOS 上打开 `SFSafariViewController`，在 Android 上打开 Custom Tab。它不持有 API key，也没有自己的结账逻辑，因此不会调用 Dodo Payments API。结账在浏览器视图中运行。SDK 负责呈现和关闭该视图，并从返回 URL 中读取结果。

> [!WARNING]
>
> 此 SDK 仅支持 **New Architecture**。它要求 React Native 0.77 或更高版本、iOS 16 或更高版本，以及 Android `minSdk` 24。你的 Android 应用必须使用 `compileSdk` 34 或更高版本构建。

## 安装

**1. Install the Package**

**Android**

该软件包会自动链接，并从 Maven Central 拉取 `com.dodopayments.api:checkout-android`。

```sh theme={null}
npm i @dodopayments/react-native-checkout
```

原生依赖会自动解析，因此不需要其他安装步骤。

**iOS**

```sh theme={null}
npm i @dodopayments/react-native-checkout
cd ios && pod install
```

该软件包包含 Swift core，并由 CocoaPods 安装。

**Expo**

SDK 仅适用于 development builds，不适用于 Expo Go。

```sh theme={null}
npm i @dodopayments/react-native-checkout
```

然后按照下一步中的说明，在 `app.json` 中配置插件。

[外观自定义](#appearance-customization)要求版本 1.2.0 或更高版本。

**2. Register a Callback URL Scheme**

注册 URL scheme，以便操作系统将结账的返回 URL 路由回你的应用。

**Android (Gradle)**

在 `android/app/build.gradle` 中将 scheme 设置为 manifest placeholder：

```groovy android/app/build.gradle theme={null}
android {
    defaultConfig {
        manifestPlaceholders["dodoCallbackScheme"] = "myapp"
    }
}
```

将 `"myapp"` 替换为应用的 scheme。

**iOS (Info.plist)**

向 `ios/YourApp/Info.plist` 添加 URL type：

```xml Info.plist theme={null}
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>myapp</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>
```

你也可以在 Xcode 的 **Info → URL Types** 中添加 URL type。

**Expo (both platforms)**

软件包的 config plugin 会在 `prebuild` 期间为两个平台注册 scheme。在 `app.json` 中，将 scheme 作为插件的 `scheme` 选项传入：

```json app.json theme={null}
{
  "expo": {
    "scheme": "myapp",
    "plugins": [
      [
        "@dodopayments/react-native-checkout",
        { "scheme": "myappcheckout" }
      ]
    ]
  }
}
```

插件的 `scheme` 必须与所传入 `returnUrl` 的 scheme 匹配，例如 `myappcheckout://return`。请使用不同于 `expo.scheme` 的 scheme。Expo 已在 `MainActivity` 上注册 `expo.scheme`，因此重复使用它可能会将结账返回路由到 Android 上错误的 activity，插件也会对此发出警告。插件会拒绝 `http`、`https` 以及其他系统 scheme。

编辑 `app.json` 后，重新构建原生项目：

```sh theme={null}
npx expo prebuild --clean
```

该插件仅适用于 development builds，不适用于 Expo Go。在 iOS 上，你仍需要在[转发返回 URL](#forwarding-the-return-url)中使用 `Linking` listener。

如果 `android/app/build.gradle` 没有 `defaultConfig { }` block，插件会在 `prebuild` 期间因错误而停止。在这种情况下，请按照 Android 选项卡中的说明手动添加 placeholder。

在每个平台上，当后端创建 session 时，将与结账 session 的 `return_url` 设置为相同的 URL。SDK 会根据 scheme、host 和 path 匹配返回 URL。该 URL 不需要加载真实页面。

## 使用

使用后端返回的 `checkout_url` 调用 `DodoCheckout.start`：

```typescript theme={null}
import { Linking } from 'react-native';
import { DodoCheckout } from '@dodopayments/react-native-checkout';

// Required for iOS's return-URL handling.
Linking.addEventListener('url', ({ url }) => DodoCheckout.handleOpenURL(url));

// showSuccess(), showFailure(), showExpired(), and reconcileAbandonedSession()
// are your own functions. See Abandoned Sessions for what to reconcile.
const result = await DodoCheckout.start({
  checkoutUrl,                          // from your backend's checkout session
  returnUrl: 'myapp://checkout/return', // scheme must be registered (see Installation)
  onEvent: (e) => console.log(e.type),  // logging only
});

switch (result.status) {
  case 'succeeded': showSuccess(result.paymentId); break;
  case 'failed':    showFailure(); break;
  case 'cancelled': await reconcileAbandonedSession(); break; // outcome unknown, not a failure
  case 'pending':   await reconcileAbandonedSession(); break;
  case 'expired':   showExpired(); break;
}
```

`onEvent` 会接收事件，其 `type` 为 `checkout.opened`、`checkout.return_received` 或 `checkout.closed`。仅将它们用于日志记录，绝不要据此判断结果。

## 转发返回 URL

iOS 需要 `Linking` listener 来处理返回 URL，因为 `SFSafariViewController` 无法捕获自己的返回 URL。在 Android 上，`handleOpenURL` 不执行任何操作并解析为 `false`，因为 Android SDK 会在原生层捕获其重定向。你可以在两个平台上都注册该 listener。

```typescript theme={null}
import { Linking } from 'react-native';
import { DodoCheckout } from '@dodopayments/react-native-checkout';

Linking.addEventListener('url', ({ url }) => {
  DodoCheckout.handleOpenURL(url);
});
```

在 iOS 上，当 URL 属于正在进行的结账时，`handleOpenURL` 会解析为 `true`；对于任何其他 URL，则解析为 `false`。

## 结果含义

SDK 会根据返回 URL 中的 query parameters 构建结果。

> [!WARNING]
>
> `result.status` 是 UI 提示，而不是付款凭证。请通过 `payment.succeeded` 或 `subscription.active` webhook 从后端确认每笔付款。

**status** `CheckoutStatus` *required*

五个值中的一个：

* `succeeded`：返回 URL 包含 `status=succeeded`（一次性付款）或 `status=active`（订阅）。
* `failed`：付款被拒绝（`status=failed`）。
* `cancelled`：客户在返回 URL 到达前关闭了浏览器视图。SDK 不知道结果，付款可能已经成功，因此不要显示失败页面。请改为对[已放弃的 session](#abandoned-sessions)进行对账。
* `pending`：付款稍后结算（`status=processing` 或任何 `requires_*` 值），或者 `status` 参数缺失或无法识别。请按照 `cancelled` 的方式进行对账。
* `expired`：结账 session 已过期（`status=expired`）。

**paymentId** `string`

当返回 URL 包含 `payment_id` query parameter 时，该参数会出现在结果中。在 UI 中显示它，但不要使用它来授予访问权限。请参阅[验证付款](#verify-the-payment)。

**subscriptionId** `string`

`subscription_id` query parameter。用于订阅结账。

**licenseKeys** `string[]`

`license_key` query parameter。当结账包含 license key 产品时设置。

**customerEmail** `string`

`email` query parameter。当结账收集电子邮件地址时设置。

**raw** `Record\<string, string\>`

返回 URL 中的每个 query parameter，原样保留。

## 验证付款

> **[Webhooks](/developer-resources/webhooks)**
>
> 付款成功或订阅激活时，Dodo Payments 会调用你的后端。

> **[Get Payment Detail](/api-reference/payments/get-payments-1)**
>
> 使用 secret key 查询 `paymentId`，以检查其状态。

只有在以下任一项确认付款后，才能授予访问权限。不要仅依赖 `result.status`。

## 外观自定义

要更改结账浏览器的工具栏、按钮和配色方案，请将 `customization` 传递给 `start(...)`。Android Custom Tabs 和 iOS `SFSafariViewController` 提供的原生控件不同，因此选项分为 `android` object 和 `ios` object。每个平台只读取自己的 object。所有字段都是可选的。省略字段时，平台会使用自己的默认值。

> **Android — Custom Tab**
>
> **toolbarColor** `string`
>
> 工具栏背景色，使用十六进制字符串表示：`"#RRGGBB"` 或 `"#AARRGGBB"`。
>
> **navigationBarColor** `string`
>
> 导航栏颜色，使用十六进制字符串表示。
>
> **navigationBarDividerColor** `string`
>
> 导航栏上方分隔线的颜色，使用十六进制字符串表示。
>
> **closeButtonStyle** `'default' | 'back'`
>
> `default` 显示系统的“X”图标。`back` 显示由 SDK 绘制的返回箭头。
>
> **closeButtonPosition** `'start' | 'end'`
>
> 关闭按钮在工具栏中出现的一侧。
>
> **shareButtonEnabled** `boolean`
>
> 显示工具栏的分享图标。`false` 会将其隐藏。
>
> **showTitleEnabled** `boolean`
>
> 在工具栏中 URL 下方显示页面标题。
>
> **urlBarHidingEnabled** `boolean`
>
> 页面滚动时自动隐藏工具栏。
>
> **bookmarksButtonEnabled** `boolean`
>
> 在溢出菜单中显示“将此页面加入书签”。
>
> **downloadsButtonEnabled** `boolean`
>
> 在溢出菜单中显示“下载页面”。
>
> **colorScheme** `'system' | 'light' | 'dark'`
>
> `light` 或 `dark` 会强制使用相应外观，而不受设备系统设置影响。`system` 遵循系统设置。

> **iOS — SFSafariViewController**
>
> **dismissButtonStyle** `'done' | 'close' | 'cancel'`
>
> 关闭按钮的样式。iOS 决定将其渲染为标签还是图标。
>
> **presentationStyle** `'pageSheet' | 'fullScreen'`
>
> `pageSheet`（默认值）会显示一个客户可以向下滑动以关闭的卡片。`fullScreen` 会覆盖整个屏幕。
>
> **barCollapsingEnabled** `boolean`
>
> 允许工具栏在页面滚动时折叠。只有当 `presentationStyle` 为 `fullScreen` 时才会产生可见效果。使用 `pageSheet` 时，无论此设置如何，工具栏都会保持固定。
>
> **colorScheme** `'system' | 'light' | 'dark'`
>
> `light` 或 `dark` 会强制使用相应外观，而不受设备系统设置影响。`system` 遵循系统设置。

iOS 没有工具栏颜色选项，因为底层 `SFSafariViewController` tint properties 自 iOS 26 起已弃用。

```typescript theme={null}
const result = await DodoCheckout.start({
  checkoutUrl,
  returnUrl: 'myapp://checkout/return',
  customization: {
    android: { toolbarColor: '#6366F1', closeButtonStyle: 'back' },
    ios: { presentationStyle: 'fullScreen', colorScheme: 'dark' },
  },
});
```

## 错误

`start` 仅在使用方式错误或平台故障时才会以 `CheckoutError` 拒绝。请从 `error.code` 中读取原因。客户取消操作或付款被拒绝时，始终会返回结果，而不是拒绝。

* `INVALID_CHECKOUT_URL`：`checkoutUrl` 不是 `https` checkout session URL（路径以 `/session/` 开头），且不位于 `checkout.dodopayments.com` 或 `test.checkout.dodopayments.com` 上。
* `INVALID_RETURN_URL`：`returnUrl` 不是包含 scheme 和 host 的绝对 URL。
* `ALREADY_IN_PROGRESS`：另一个结账正在运行。一次只能运行一个结账。
* `PLATFORM_ERROR`：意外的平台故障。SDK 也会使用此代码报告任何无法识别的原生错误。

## 已放弃的 Session

原生 SDK 会在结账开始时记录结账 session，并且仅当结账以 `succeeded`、`failed` 或 `expired` 结束时清除记录。如果应用或 JavaScript bundle 在结账期间被终止，记录会保留，此时 `start` promise 会丢失；在 `cancelled` 或 `pending` 结果之后，记录也会保留。请在下一次 mount 时，以及每次出现 `cancelled` 或 `pending` 结果后检查记录：

```typescript theme={null}
import { DodoCheckout } from '@dodopayments/react-native-checkout';

const abandoned = await DodoCheckout.getAbandonedSession();
if (abandoned) {
  // Ask your backend for the outcome of abandoned.sessionId, and show it.
  // Clear the record only after the outcome is final:
  await DodoCheckout.clearAbandonedSession();
}
```

`abandoned.sessionId` 是结账 session ID，以 `cks_` 开头。`abandoned.createdAt` 是 `Date` 结账开始的时间。你的后端可以使用 [Get Checkout Session](/api-reference/checkout-sessions/get-checkouts) 查询 session，该接口会返回其 `payment_id` 和 `payment_status`。在付款达到最终状态之前，将其视为 pending，而不是 failed。

## 相关内容

> **[Mobile Integration Guide](/developer-resources/mobile-integration)**
>
> 适用于 Android、iOS 和 Flutter 的相同契约。

> **[Expo Boilerplate](/developer-resources/expo-boilerplate)**
>
> 包含结账集成的完整 Expo 示例。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.