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

> Buka hosted checkout Dodo Payments dari aplikasi iOS di SFSafariViewController dengan Swift SDK resmi, dan dapatkan hasil checkout bertipe dari satu panggilan.

<Info>
  Halaman ini membahas SDK checkout iOS resmi Dodo Payments untuk Swift. SDK ini membuka hosted checkout Dodo Payments dalam tampilan browser native dan mengembalikan hasil bertipe.
</Info>

<CardGroup cols={2}>
  <Card title="Checkout Sessions API" icon="cart-shopping" href="/developer-resources/checkout-session">
    Buat `checkout_url` yang dibuka SDK ini dari backend Anda.
  </Card>

  <Card title="Mobile Integration Guide" icon="mobile" href="/developer-resources/mobile-integration">
    Lihat bagaimana SDK ini sesuai dengan alur pembayaran mobile secara lengkap.
  </Card>
</CardGroup>

SDK iOS membuka hosted checkout Dodo Payments di `SFSafariViewController` dan mengembalikan `CheckoutResult` bertipe ketika customer menyelesaikan atau meninggalkan checkout. SDK ini tidak menyimpan API key dan tidak berisi kode networking, sehingga tidak pernah memanggil API Dodo Payments. Checkout berjalan di browser view. SDK menampilkan dan menutup view tersebut serta membaca hasil dari return URL.

**Persyaratan:** iOS 16 atau lebih baru, dan Swift 6.2 atau lebih baru (package mendeklarasikan `swift-tools-version: 6.2`). SDK ini tidak memiliki dependency pihak ketiga.

## Instalasi

<Steps>
  <Step title="Add the Package">
    Di Xcode, buka **File → Add Package Dependencies** dan masukkan URL package:

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

    Pilih versi 1.1.0 atau lebih baru. [Kustomisasi tampilan](#appearance-customization) memerlukan versi 1.1.0.

    Untuk menambahkan package di `Package.swift`, tambahkan dependency ini:

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

    Produk library-nya adalah `DodoCheckout`.
  </Step>

  <Step title="Register a Callback URL Scheme">
    Daftarkan URL scheme agar iOS mengarahkan return URL checkout kembali ke aplikasi Anda. Tambahkan URL type ke `Info.plist` Anda:

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

    Anda juga dapat menambahkan URL type di Xcode melalui **Info → URL Types**.

    Gunakan scheme ini di `returnUrl` yang Anda teruskan ke SDK, misalnya `myapp://checkout/return`, dan tetapkan URL yang sama sebagai `return_url` pada checkout session ketika backend Anda membuat session tersebut. SDK mencocokkan return URL berdasarkan scheme, host, dan path. URL tersebut tidak perlu memuat halaman nyata.
  </Step>
</Steps>

## Penggunaan

`DodoCheckout.start` adalah fungsi `async` yang berjalan pada main actor. Teruskan `checkoutUrl` sebagai `URL` yang dibuat dari `checkout_url` yang dikembalikan backend Anda:

```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` menerima event `.opened`, `.returnReceived`, dan `.closed`. Nilai `name` miliknya adalah `checkout.opened`, `checkout.return_received`, dan `checkout.closed`. Gunakan event hanya untuk logging, bukan untuk menentukan hasil.

## Meneruskan Return URL

`SFSafariViewController` tidak dapat menangkap return URL-nya sendiri, sehingga iOS membuka URL tersebut di aplikasi Anda. Teruskan setiap URL masuk ke `DodoCheckout.handleOpenURL(_:)`. Dalam aplikasi tanpa scene, panggil fungsi tersebut dari `application(_:open:options:)` milik app delegate Anda.

<Tabs>
  <Tab title="SwiftUI">
    ```swift theme={null}
    .onOpenURL { url in
        DodoCheckout.handleOpenURL(url)
    }
    ```
  </Tab>

  <Tab title="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)
    }
    ```
  </Tab>
</Tabs>

<Note>
  Anda dapat meneruskan setiap URL. `handleOpenURL` hanya bekerja pada URL yang cocok dengan `returnUrl` checkout yang sedang berlangsung, dan mengembalikan `true` untuk URL tersebut. Untuk URL lainnya, fungsi ini mengembalikan `false`, jadi tangani URL tersebut sendiri.
</Note>

## Arti Hasil

SDK membuat `CheckoutResult` dari query parameter pada return URL.

<Warning>
  `result.status` adalah petunjuk UI, bukan bukti pembayaran. Konfirmasikan setiap pembayaran dari backend Anda, melalui webhook `payment.succeeded` atau `subscription.active`.
</Warning>

<ParamField body="status" type="CheckoutStatus" required>
  Salah satu dari lima nilai:

  * `succeeded`: return URL memiliki `status=succeeded` (pembayaran satu kali) atau `status=active` (subscription).
  * `failed`: pembayaran ditolak (`status=failed`).
  * `cancelled`: customer menutup sheet sebelum return URL diterima. SDK tidak mengetahui hasilnya, dan pembayaran mungkin berhasil, jadi jangan tampilkan layar kegagalan. Rekonsiliasikan [abandoned session](#abandoned-sessions) sebagai gantinya.
  * `pending`: pembayaran diselesaikan kemudian (`status=processing` atau nilai `requires_*` lainnya), atau parameter `status` tidak ada atau tidak dikenali. Rekonsiliasikan seperti `cancelled`.
  * `expired`: checkout session kedaluwarsa (`status=expired`).
</ParamField>

<ParamField body="paymentId" type="String?">
  Query parameter `payment_id`, jika return URL menyertakannya. Tampilkan di UI Anda, tetapi jangan gunakan untuk memberikan akses. Lihat [Verifikasi Pembayaran](#verify-the-payment).
</ParamField>

<ParamField body="subscriptionId" type="String?">
  Query parameter `subscription_id`. Ditetapkan untuk checkout subscription.
</ParamField>

<ParamField body="licenseKeys" type="[String]?">
  Query parameter `license_key`. Ditetapkan ketika checkout mencakup produk license key.
</ParamField>

<ParamField body="customerEmail" type="String?">
  Query parameter `email`. Ditetapkan ketika checkout mengambil alamat email.
</ParamField>

<ParamField body="raw" type="[String: String]">
  Setiap query parameter dari return URL, apa adanya.
</ParamField>

## Verifikasi Pembayaran

<CardGroup cols={2}>
  <Card title="Webhooks" icon="webhook" href="/developer-resources/webhooks">
    Dodo Payments memanggil backend Anda ketika pembayaran berhasil atau subscription diaktifkan.
  </Card>

  <Card title="Get Payment Detail" icon="magnifying-glass" href="/api-reference/payments/get-payments-1">
    Cari `paymentId` menggunakan secret key Anda untuk memeriksa statusnya.
  </Card>
</CardGroup>

Berikan akses hanya setelah salah satu hal ini mengonfirmasi pembayaran. Jangan hanya mengandalkan `result.status`.

## Kustomisasi Tampilan

Untuk mengubah tombol dismiss sheet, gaya presentasi, dan skema warna, teruskan `BrowserCustomization` sebagai `customization` ke `start(...)`. Setiap field bersifat opsional. Untuk field `nil`, SDK tidak menetapkan opsi tersebut dan iOS menerapkan default-nya sendiri. Pengecualiannya adalah `presentationStyle`, yang mana `nil` berarti `pageSheet`.

<ParamField body="dismissButtonStyle" type="DismissButtonStyle?">
  Gaya tombol dismiss: `done`, `close`, atau `cancel`. iOS menentukan apakah tombol tersebut ditampilkan sebagai label atau ikon.
</ParamField>

<ParamField body="presentationStyle" type="PresentationStyle?">
  `pageSheet` (default) menampilkan card yang dapat di-swipe ke bawah oleh customer untuk ditutup. `fullScreen` menutupi seluruh layar dan tidak memiliki gesture dismiss.
</ParamField>

<ParamField body="barCollapsingEnabled" type="Bool?">
  Memungkinkan toolbar diciutkan saat halaman di-scroll. Opsi ini hanya terlihat efeknya ketika `presentationStyle` bernilai `fullScreen`. Dengan `pageSheet`, bar tetap tertambat terlepas dari pengaturan ini.
</ParamField>

<ParamField body="colorScheme" type="ColorScheme?">
  `light` atau `dark` memaksa tampilan tersebut terlepas dari pengaturan sistem perangkat. `system` mengikuti pengaturan sistem. Opsi ini hanya menerapkan tema pada kontrol native di sekitar halaman. Mode terang atau gelap halaman checkout ditentukan oleh `customization.theme` pada checkout session, sedangkan warnanya berasal dari `customization.theme_config`.
</ParamField>

iOS tidak memiliki opsi warna toolbar. Properti tint `SFSafariViewController` yang mendasarinya tidak digunakan lagi sejak 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
    )
)
```

## Error

`start` hanya melempar `CheckoutError` jika terjadi kesalahan penggunaan atau kegagalan platform. Baca alasannya dari `error.code`. Customer yang membatalkan atau pembayaran yang ditolak selalu menjadi hasil, bukan error yang dilempar.

* `invalidCheckoutUrl` (`INVALID_CHECKOUT_URL`): `checkoutUrl` bukan URL checkout session `https` (path yang diawali `/session/`) di `checkout.dodopayments.com` atau `test.checkout.dodopayments.com`.
* `invalidReturnUrl` (`INVALID_RETURN_URL`): `returnUrl` bukan URL absolut dengan scheme dan host.
* `alreadyInProgress` (`ALREADY_IN_PROGRESS`): checkout lain sedang berjalan. Hanya satu checkout yang dapat berjalan pada satu waktu.
* `platformError` (`PLATFORM_ERROR`): kegagalan platform yang tidak terduga, seperti tidak adanya view controller untuk melakukan present.

Setelah error dilempar, periksa juga abandoned session. Jika sheet tidak mengonfirmasi bahwa sheet tersebut telah muncul, SDK tetap menyimpan session karena checkout mungkin masih terbuka. Pengecualiannya adalah `alreadyInProgress`: record yang Anda temukan kemudian merupakan milik checkout yang masih berjalan.

## Abandoned Session

<Info>
  SDK mencatat checkout session saat menampilkan checkout, dan menghapus record hanya ketika checkout berakhir dengan `succeeded`, `failed`, atau `expired`. Record tetap tersimpan ketika aplikasi dihentikan selama checkout, serta setelah hasil `cancelled` atau `pending`. Periksa record tersebut saat peluncuran berikutnya dan setelah setiap hasil `cancelled` atau `pending`.
</Info>

```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` adalah ID checkout session, yang diawali dengan `cks_`. `abandoned.createdAt` adalah waktu checkout `Date` dimulai. Backend Anda dapat mencari session tersebut menggunakan [Dapatkan Checkout Session](/api-reference/checkout-sessions/get-checkouts), yang mengembalikan `payment_id` dan `payment_status`. Sampai pembayaran mencapai status final, perlakukan sebagai pending, bukan gagal.

## Terkait

<CardGroup cols={2}>
  <Card title="Mobile Integration Guide" icon="mobile" href="/developer-resources/mobile-integration">
    Kontrak yang sama untuk Android, React Native, dan Flutter.
  </Card>

  <Card title="React Native SDK" icon="react" href="/developer-resources/sdks/react-native">
    Membungkus Swift core yang sama di iOS.
  </Card>
</CardGroup>


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