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

# Flutter

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

> [!NOTE]
>
> 本页面介绍官方 Dodo Payments Flutter package（位于 `dodopayments_checkout`，pub.dev）。此外还存在一个由社区构建的独立 package。请参阅
> [社区项目](/community/projects)。

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

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

`dodopayments_checkout` 会在 iOS 的 `SFSafariViewController` 和 Android 的 Custom Tab 中打开 Dodo Payments 托管结账，并返回类型化的 `CheckoutResult`。它使用与独立 [iOS](/developer-resources/sdks/ios) 和
[Android](/developer-resources/sdks/android) SDK 相同的原生代码，所有结账逻辑都位于该原生代码中。Dart 层通过类型化的
[Pigeon](https://pub.dev/packages/pigeon) channel 传递每次调用。该 package 不保存 API key，也不会调用 Dodo Payments API。

\*\*要求：\*\*Flutter 3.44 或更高版本、Dart 3.12 或更高版本、iOS 16 或更高版本，以及 Android `minSdk` 23。

## 安装

**1. Add the Dependency**

将 package 添加到 `pubspec.yaml`：

```yaml pubspec.yaml theme={null}
dependencies:
  dodopayments_checkout: ^1.1.0
```

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

Android plugin 默认针对 Android SDK 35 进行编译。如果其他 plugin 需要更高的 `compileSdk`，请在应用的 `gradle.properties` 中设置 `dodoCompileSdk`。

**2. Register a Callback URL Scheme**

注册一个 URL scheme，使操作系统将结账返回 URL 路由回你的应用。在传递给 SDK 的 `returnUrl` 中使用此 scheme，并在后端创建 session 时，将相同的 URL 设置为结账 session 的 `return_url`。此 URL 无需加载真实页面。

**iOS**

在 `ios/Runner/Info.plist` 中为你的 scheme 添加 URL 类型：

```xml ios/Runner/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>
```

`SFSafariViewController` 无法捕获自身的返回 URL，因此 iOS 会在你的应用中打开该 URL。将每个传入的 URL 转发给 SDK，例如通过
[`app_links`](https://pub.dev/packages/app_links)：

```dart theme={null}
import 'package:dodopayments_checkout/dodopayments_checkout.dart';

// url is the incoming link as a String. For a Uri from app_links, pass uri.toString().
DodoCheckout.instance.handleOpenURL(url);
```

> [!NOTE]
>
> 你可以转发每个 URL。`handleOpenURL` 仅处理与正在进行的结账的
> `returnUrl` 匹配的 URL，并为其解析 `true`。对于任何
> 其他 URL，它会解析 `false`。在 Android 上，它始终解析 `false`。

**Android**

在 `android/app/build.gradle.kts` 中将 callback scheme 设置为 Gradle manifest placeholder。同一行也适用于 Groovy `build.gradle` 文件：

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

> [!WARNING]
>
> 如果 `MainActivity` 设置了 `android:taskAffinity=""`（`flutter create` template 中的默认值），请移除该设置，或为 SDK 的 activities 指定相同的 affinity。否则，某些 OEM Android 构建可能会丢失正在进行的结账，并因 `PLATFORM_ERROR` 而失败，错误消息为“Checkout was launched without its parameters.”

## 使用

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

```dart theme={null}
import 'package:dodopayments_checkout/dodopayments_checkout.dart';

// showSuccess(), showFailure(), showExpired(), and reconcileAbandonedSession()
// are your own functions. See Abandoned Sessions for what to reconcile.
final result = await DodoCheckout.instance.start(
  CheckoutParams(
    checkoutUrl: Uri.parse(checkoutUrl), // from your backend's checkout session
    returnUrl: Uri.parse('myapp://checkout/return'), // scheme must be registered (see Installation)
    onEvent: (event) => print(event.type), // logging only
  ),
);

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

`onEvent` 接收 `type` 为 `CheckoutEventType.opened`、`returnReceived` 或 `closed` 的事件。仅将它们用于日志记录，切勿据此决定结果。

## 结果含义

SDK 根据返回 URL 中的查询参数构建 `CheckoutResult`。

> [!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` 查询参数时显示它，但不要使用它来授予访问权限。请参阅[验证付款](#verify-the-payment)。

**subscriptionId** `String?`

`subscription_id` 查询参数。用于订阅结账。

**licenseKeys** `List\<String\>?`

`license_key` 查询参数。在结账包含 license key 产品时设置。

**customerEmail** `String?`

`email` 查询参数。在结账捕获 email address 时设置。

**raw** `Map\<String, String\>`

返回 URL 中的每个查询参数，原样返回。

## 验证付款

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

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

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

## 外观自定义

要更改结账浏览器的工具栏、按钮和配色方案，请将 `BrowserCustomization` 作为 `customization` 传递给 `CheckoutParams`。Android Custom Tabs 和 iOS `SFSafariViewController` 提供不同的原生控件，因此选项分为 `AndroidBrowserOptions` 和 `IosBrowserOptions`。每个平台都会忽略另一个平台的选项。每个字段都是可选的，默认值为 `null`。对于值为 `null` 的字段，SDK 不会设置该选项，而是由平台应用自身的默认值。

> **Android — Custom Tab**
>
> **toolbarColor** `Color?`
>
> 工具栏背景颜色。
>
> **navigationBarColor** `Color?`
>
> 导航栏颜色。
>
> **navigationBarDividerColor** `Color?`
>
> 导航栏上方分隔线的颜色。
>
> **closeButtonStyle** `CloseButtonStyle?`
>
> `standard` 显示系统的“X”图标。`back` 显示由 SDK 绘制的返回箭头。
>
> **closeButtonPosition** `CloseButtonPosition?`
>
> 工具栏中关闭按钮出现的一侧：`start` 或 `end`。
>
> **shareButtonEnabled** `bool?`
>
> 显示工具栏的分享图标。`false` 会将其隐藏。
>
> **showTitleEnabled** `bool?`
>
> 在工具栏中 URL 下方显示页面标题。
>
> **urlBarHidingEnabled** `bool?`
>
> 页面滚动时自动隐藏工具栏。
>
> **bookmarksButtonEnabled** `bool?`
>
> 在溢出菜单中显示“将此页面加入书签”。
>
> **downloadsButtonEnabled** `bool?`
>
> 在溢出菜单中显示“下载页面”。
>
> **colorScheme** `BrowserColorScheme?`
>
> `light` 或 `dark` 会强制使用相应外观，而不考虑设备的系统设置。`system` 遵循系统设置。

> **iOS — SFSafariViewController**
>
> **dismissButtonStyle** `DismissButtonStyle?`
>
> 关闭按钮的样式：`done`、`close` 或 `cancel`。由 iOS 决定将其渲染为标签还是图标。
>
> **presentationStyle** `PresentationStyle?`
>
> `pageSheet`（在将此 `null` 留空时使用）会显示一张客户可以向下滑动关闭的卡片。`fullScreen` 会覆盖整个屏幕。
>
> **barCollapsingEnabled** `bool?`
>
> 允许工具栏在页面滚动时折叠。仅当 `presentationStyle` 为 `fullScreen` 时才有效。使用 `pageSheet` 时，无论此设置如何，工具栏都会保持固定。
>
> **colorScheme** `BrowserColorScheme?`
>
> `light` 或 `dark` 会强制使用相应外观，而不考虑设备的系统设置。`system` 遵循系统设置。

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

```dart theme={null}
import 'package:dodopayments_checkout/dodopayments_checkout.dart';
import 'package:flutter/material.dart' show Color;

final result = await DodoCheckout.instance.start(
  CheckoutParams(
    checkoutUrl: Uri.parse(checkoutUrl),
    returnUrl: Uri.parse('myapp://checkout/return'),
    customization: BrowserCustomization(
      android: AndroidBrowserOptions(
        toolbarColor: Color(0xFF6366F1),
        closeButtonStyle: CloseButtonStyle.back,
      ),
      ios: IosBrowserOptions(
        presentationStyle: PresentationStyle.fullScreen,
        colorScheme: BrowserColorScheme.dark,
      ),
    ),
  ),
);
```

## 错误

`start` 仅在误用或平台故障时抛出 `CheckoutException`。请从 `code`（一个 `CheckoutErrorCode`）中读取原因。原生代码字符串位于 `nativeCode`。
客户取消或付款被拒绝始终是结果，而不是 exception。

* `invalidCheckoutUrl`（`INVALID_CHECKOUT_URL`）：`checkoutUrl` 不是 `checkout.dodopayments.com` 或 `test.checkout.dodopayments.com` 上的 `https` 结账 session URL（路径以 `/session/` 开头）。
* `invalidReturnUrl`（`INVALID_RETURN_URL`）：`returnUrl` 不是包含 scheme 和 host 的绝对 URL。
* `alreadyInProgress`（`ALREADY_IN_PROGRESS`）：另一个结账正在运行。一次只能运行一个结账。
* `platformError`（`PLATFORM_ERROR`）：意外的平台故障。未知的原生错误也会映射到此 code。

## 已放弃的 Session

> [!NOTE]
>
> 原生 SDK 会在结账开始时记录结账 session，并仅在结账以 `succeeded`、`failed` 或 `expired` 结束时清除该记录。如果应用在结账期间被终止，或结果为 `cancelled` 或 `pending`，该记录会保留。请在下次启动时，以及每次出现 `cancelled` 或 `pending` 结果后检查该记录。

```dart theme={null}
import 'package:dodopayments_checkout/dodopayments_checkout.dart';

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

`abandoned.sessionId` 是结账 session ID，以 `cks_` 开头。`abandoned.createdAt` 是结账开始时的 `DateTime`。你的后端可以使用 [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 和 React Native 使用相同的契约。

> **[Community Projects](/community/projects)**
>
> 此外还存在一个由社区构建的独立 Flutter package。


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