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

# iOS

> Mở hosted checkout của Dodo Payments từ ứng dụng iOS trong SFSafariViewController bằng Swift SDK chính thức và nhận kết quả checkout có kiểu dữ liệu từ một lần gọi.

> [!NOTE]
>
> Trang này trình bày iOS checkout SDK chính thức của Dodo Payments dành cho Swift. SDK mở hosted checkout của Dodo Payments trong chế độ xem trình duyệt gốc và trả về một kết quả có kiểu dữ liệu.

> **[Checkout Sessions API](/developer-resources/checkout-session)**
>
> Tạo `checkout_url` mà SDK này sẽ mở từ backend của bạn.

> **[Mobile Integration Guide](/developer-resources/mobile-integration)**
>
> Tìm hiểu cách SDK này tích hợp vào toàn bộ quy trình thanh toán trên thiết bị di động.

iOS SDK mở hosted checkout của Dodo Payments trong `SFSafariViewController` và trả về một `CheckoutResult` có kiểu dữ liệu khi khách hàng hoàn tất hoặc rời checkout. SDK không chứa API key và không có mã networking, vì vậy không bao giờ gọi Dodo Payments API. Checkout chạy trong chế độ xem trình duyệt. SDK hiển thị và đóng chế độ xem đó, đồng thời đọc kết quả từ return URL.

**Yêu cầu:** iOS 16 trở lên và Swift 6.2 trở lên (package khai báo `swift-tools-version: 6.2`). SDK không có dependency bên thứ ba.

## Cài đặt

**1. Add the Package**

Trong Xcode, đi đến **File → Add Package Dependencies** và nhập package URL:

```text theme={null}
https://github.com/dodopayments/dodopayments-mobile-sdk-ios
```

Chọn version 1.1.0 hoặc mới hơn. [Tùy chỉnh giao diện](#appearance-customization) yêu cầu version 1.1.0.

Để thêm package trong `Package.swift`, hãy thêm dependency này:

```swift Package.swift theme={null}
.package(url: "https://github.com/dodopayments/dodopayments-mobile-sdk-ios", from: "1.1.0")
```

Library product là `DodoCheckout`.

**2. Register a Callback URL Scheme**

Đăng ký URL scheme để iOS định tuyến return URL của checkout trở lại ứng dụng của bạn. Thêm một URL type vào `Info.plist`:

```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>
```

Bạn cũng có thể thêm URL type trong Xcode tại **Info → URL Types**.

Sử dụng scheme này trong `returnUrl` mà bạn truyền cho SDK, ví dụ `myapp://checkout/return`, và đặt cùng URL đó làm `return_url` của checkout session khi backend tạo session. SDK đối chiếu return URL theo scheme, host và path. URL không cần tải một trang thực.

## Cách sử dụng

`DodoCheckout.start` là một hàm `async` chạy trên main actor. Truyền `checkoutUrl` dưới dạng `URL` được tạo từ `checkout_url` mà backend trả về:

```swift theme={null}
import DodoCheckout

// showSuccess(_:), showFailure(), showExpired(), and reconcileAbandonedSession()
// are your own functions. See Abandoned Sessions for what to reconcile.
let result = try await DodoCheckout.start(
    checkoutUrl: checkoutUrl,   // from your backend's checkout session
    returnUrl: URL(string: "myapp://checkout/return")!,
    onEvent: { event in print(event.name) }  // logging only
)

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

`onEvent` nhận các event `.opened`, `.returnReceived` và `.closed`. Các giá trị `name` của chúng lần lượt là `checkout.opened`, `checkout.return_received` và `checkout.closed`. Chỉ sử dụng event để ghi log, không bao giờ dùng chúng để quyết định kết quả.

## Chuyển tiếp Return URL

`SFSafariViewController` không thể tự bắt return URL của nó, vì vậy iOS sẽ mở URL đó trong ứng dụng của bạn. Chuyển tiếp mọi URL đến `DodoCheckout.handleOpenURL(_:)`. Trong ứng dụng không sử dụng scenes, hãy gọi hàm này từ `application(_:open:options:)` của app delegate.

**SwiftUI**

```swift theme={null}
.onOpenURL { url in
    DodoCheckout.handleOpenURL(url)
}
```

**SceneDelegate**

```swift SceneDelegate.swift theme={null}
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    DodoCheckout.handleOpenURL(url)
}
```

> [!NOTE]
>
> Bạn có thể chuyển tiếp mọi URL. `handleOpenURL` chỉ hoạt động trên URL khớp với `returnUrl` của checkout đang diễn ra và trả về `true` cho URL đó. Với mọi URL khác, hàm trả về `false`, vì vậy hãy tự xử lý URL đó.

## Ý nghĩa của kết quả

SDK xây dựng `CheckoutResult` từ các query parameter trên return URL.

> [!WARNING]
>
> `result.status` là gợi ý UI, không phải bằng chứng thanh toán. Xác nhận mọi thanh toán từ backend bằng webhook `payment.succeeded` hoặc `subscription.active`.

**status** `CheckoutStatus` *required*

Một trong năm giá trị:

* `succeeded`: return URL có `status=succeeded` (thanh toán một lần) hoặc `status=active` (subscription).
* `failed`: thanh toán bị từ chối (`status=failed`).
* `cancelled`: khách hàng đóng sheet trước khi return URL xuất hiện. SDK không biết kết quả và thanh toán có thể đã thành công, vì vậy không hiển thị màn hình thất bại. Thay vào đó, hãy đối soát [abandoned session](#abandoned-sessions).
* `pending`: thanh toán được quyết toán sau (`status=processing` hoặc bất kỳ giá trị `requires_*` nào), hoặc parameter `status` bị thiếu hoặc không được nhận dạng. Hãy đối soát như với `cancelled`.
* `expired`: checkout session đã hết hạn (`status=expired`).

**paymentId** `String?`

Query parameter `payment_id`, khi return URL có parameter này. Hiển thị parameter này trong UI nhưng không dùng nó để cấp quyền truy cập. Xem [Xác minh thanh toán](#verify-the-payment).

**subscriptionId** `String?`

Query parameter `subscription_id`. Được thiết lập cho subscription checkout.

**licenseKeys** `[String]?`

Query parameter `license_key`. Được thiết lập khi checkout bao gồm các sản phẩm có license key.

**customerEmail** `String?`

Query parameter `email`. Được thiết lập khi checkout thu thập địa chỉ email.

**raw** `[String: String]`

Mọi query parameter từ return URL, được giữ nguyên.

## Xác minh thanh toán

> **[Webhooks](/developer-resources/webhooks)**
>
> Dodo Payments gọi backend của bạn khi thanh toán thành công hoặc subscription được kích hoạt.

> **[Get Payment Detail](/api-reference/payments/get-payments-1)**
>
> Tra cứu `paymentId` bằng secret key của bạn để kiểm tra trạng thái.

Chỉ cấp quyền truy cập sau khi một trong các phương thức này xác nhận thanh toán. Không chỉ dựa vào `result.status`.

## Tùy chỉnh giao diện

Để thay đổi nút đóng sheet, kiểu trình bày và bảng màu, hãy truyền một `BrowserCustomization` dưới dạng `customization` vào `start(...)`. Mọi field đều không bắt buộc. Với field `nil`, SDK không thiết lập tùy chọn đó và iOS áp dụng giá trị mặc định của riêng mình. Ngoại lệ là `presentationStyle`, trong đó `nil` có nghĩa là `pageSheet`.

**dismissButtonStyle** `DismissButtonStyle?`

Kiểu của nút đóng: `done`, `close` hoặc `cancel`. iOS quyết định hiển thị nút dưới dạng nhãn hay biểu tượng.

**presentationStyle** `PresentationStyle?`

`pageSheet` (mặc định) hiển thị một card mà khách hàng có thể vuốt xuống để đóng. `fullScreen` bao phủ toàn bộ màn hình và không có thao tác vuốt để đóng.

**barCollapsingEnabled** `Bool?`

Cho phép toolbar thu gọn khi trang được cuộn. Tùy chọn này chỉ có hiệu lực khi `presentationStyle` là `fullScreen`. Với `pageSheet`, các thanh luôn được ghim bất kể cài đặt này.

**colorScheme** `ColorScheme?`

`light` hoặc `dark` buộc giao diện đó được áp dụng bất kể cài đặt hệ thống của thiết bị. `system` tuân theo cài đặt hệ thống. Tùy chọn này chỉ áp dụng theme cho các control gốc xung quanh trang. Chế độ sáng hoặc tối của chính trang checkout lấy từ `customization.theme` trên checkout session, còn màu sắc lấy từ `customization.theme_config`.

iOS không có tùy chọn màu toolbar. Các thuộc tính tint của `SFSafariViewController` bên dưới đã bị deprecated kể từ iOS 26.

```swift theme={null}
let result = try await DodoCheckout.start(
    checkoutUrl: checkoutUrl,
    returnUrl: URL(string: "myapp://checkout/return")!,
    customization: BrowserCustomization(
        dismissButtonStyle: .close,
        presentationStyle: .fullScreen,
        colorScheme: .dark
    )
)
```

## Lỗi

`start` chỉ ném `CheckoutError` khi sử dụng sai hoặc xảy ra lỗi nền tảng. Đọc nguyên nhân từ `error.code`. Khách hàng hủy hoặc thanh toán bị từ chối luôn là một result, không phải lỗi được ném ra.

* `invalidCheckoutUrl` (`INVALID_CHECKOUT_URL`): `checkoutUrl` không phải là URL checkout session hợp lệ (path bắt đầu bằng `/session/`) trên `checkout.dodopayments.com` hoặc `test.checkout.dodopayments.com`.
* `invalidReturnUrl` (`INVALID_RETURN_URL`): `returnUrl` không phải là absolute URL có scheme và host.
* `alreadyInProgress` (`ALREADY_IN_PROGRESS`): một checkout khác đang chạy. Mỗi lần chỉ có thể chạy một checkout.
* `platformError` (`PLATFORM_ERROR`): lỗi nền tảng không mong đợi, chẳng hạn không có view controller để trình bày.

Sau khi lỗi được ném ra, hãy kiểm tra cả abandoned session. Nếu sheet không xác nhận rằng nó đã xuất hiện, SDK vẫn giữ session trong hồ sơ vì checkout có thể vẫn đang mở. Ngoại lệ là `alreadyInProgress`: record bạn tìm thấy khi đó thuộc về checkout vẫn đang chạy.

## Abandoned session

> [!NOTE]
>
> SDK ghi lại checkout session khi trình bày checkout và chỉ xóa record khi checkout kết thúc với `succeeded`, `failed` hoặc `expired`. Record vẫn còn khi ứng dụng bị đóng trong lúc checkout và sau result `cancelled` hoặc `pending`. Hãy kiểm tra record này ở lần khởi chạy tiếp theo và sau mỗi result `cancelled` hoặc `pending`.

```swift theme={null}
import DodoCheckout

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

`abandoned.sessionId` là ID của checkout session, bắt đầu bằng `cks_`. `abandoned.createdAt` là `Date` mà checkout đã bắt đầu. Backend của bạn có thể tra cứu session bằng [Get Checkout Session](/api-reference/checkout-sessions/get-checkouts), API này trả về `payment_id` và `payment_status` của session. Cho đến khi thanh toán đạt trạng thái cuối cùng, hãy xem thanh toán là đang chờ xử lý, không phải thất bại.

## Liên quan

> **[Mobile Integration Guide](/developer-resources/mobile-integration)**
>
> Cùng một contract cho Android, React Native và Flutter.

> **[React Native SDK](/developer-resources/sdks/react-native)**
>
> Bọc cùng Swift core này trên iOS.


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