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

> Open Dodo Payments' hosted checkout from an iOS app in SFSafariViewController and get a typed result back in one call.

<Info>
  This is the official Dodo Payments iOS checkout SDK for Swift. It opens Dodo's hosted checkout in a native browser view and returns a typed result.
</Info>

<CardGroup cols={2}>
  <Card title="Checkout Sessions API" icon="cart-shopping" href="/developer-resources/checkout-session">
    Create the checkout\_url this SDK opens, from your backend.
  </Card>

  <Card title="Mobile Integration Guide" icon="mobile" href="/developer-resources/mobile-integration">
    See how this fits into the full mobile payment flow.
  </Card>
</CardGroup>

The iOS SDK opens Dodo's hosted checkout in `SFSafariViewController`, holds no API key, and never calls the Dodo API directly. All checkout logic runs in the browser; the SDK simply manages the view lifecycle and captures the return URL.

Requires iOS 16+, Swift 6.

## Installation

<Steps>
  <Step title="Add the Package">
    In Xcode, go to **File → Add Package Dependencies** and enter:

    ```
    https://github.com/dodopayments/dodopayments-mobile-sdk-ios
    ```

    Select version 1.0.0 or later.

    Alternatively, add to your `Package.swift`:

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

  <Step title="Register a Callback URL Scheme">
    Your app must register a URL scheme to receive the return URL from the checkout. Add this to your `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>
    ```

    You can also add this via Xcode's **Info → URL Types** UI.
  </Step>
</Steps>

## Usage

```swift theme={null}
import DodoCheckout

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: dismiss()
case .pending:   showPending()
case .expired:   showExpired()
}
```

## Forwarding the Return URL

`SFSafariViewController` has no in-process way to catch its own return URL. Your app must forward incoming URLs into the SDK.

<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>
  It's safe to forward every URL here. `handleOpenURL` only acts on URLs matching your registered `returnUrl` and returns `false` for anything else.
</Note>

## What the Result Means

<Warning>
  `result.status` is a UI hint, not proof of payment. Confirm every payment from your backend, via the `payment.succeeded` / `subscription.active` webhook.
</Warning>

<ParamField body="status" type="CheckoutStatus" required>
  One of `succeeded`, `failed`, `cancelled`, `pending`, `expired`.
</ParamField>

<ParamField body="paymentId" type="String?">
  Set when the return URL included one. Display it in the UI, don't use it to grant access. See Verify the Payment below.
</ParamField>

<ParamField body="subscriptionId" type="String?">
  Set for subscription checkouts.
</ParamField>

<ParamField body="licenseKeys" type="[String]?">
  Set when the checkout includes license key products.
</ParamField>

<ParamField body="customerEmail" type="String?">
  Set when the checkout captures an email.
</ParamField>

<ParamField body="raw" type="[String: String]">
  Every query parameter from the return URL, verbatim.
</ParamField>

## Verify the Payment

<CardGroup cols={2}>
  <Card title="Webhooks" icon="webhook" href="/developer-resources/webhooks">
    Dodo Payments calls your backend when a payment succeeds or a subscription activates.
  </Card>

  <Card title="Get Payment Detail" icon="magnifying-glass" href="/api-reference/payments/get-payments-1">
    Look up `paymentId` with your secret key to check its status directly.
  </Card>
</CardGroup>

Grant access after one of these confirms the payment, never from `result.status` alone.

## Errors

`start` throws `CheckoutError` only for misuse or a platform failure. A cancelled or declined payment is always a result, never an exception.

* `invalidCheckoutUrl` (`INVALID_CHECKOUT_URL`): not a `checkout.dodopayments.com` session URL.
* `invalidReturnUrl` (`INVALID_RETURN_URL`): not a valid absolute URL.
* `alreadyInProgress` (`ALREADY_IN_PROGRESS`): a checkout is already running.
* `platformError` (`PLATFORM_ERROR`): unexpected platform failure.

## Abandoned Sessions

<Info>
  If the app is killed mid-checkout, recover the session on next launch and reconcile it with your backend.
</Info>

```swift theme={null}
import DodoCheckout

if let abandoned = DodoCheckout.getAbandonedSession() {
    // reconcile abandoned.sessionId with your backend, then:
    DodoCheckout.clearAbandonedSession()
}
```

## Related

<CardGroup cols={2}>
  <Card title="Mobile Integration Guide" icon="mobile" href="/developer-resources/mobile-integration">
    The same contract for Android, React Native, and Flutter.
  </Card>

  <Card title="React Native SDK" icon="react" href="/developer-resources/sdks/react-native">
    Wraps this same Swift core on iOS.
  </Card>
</CardGroup>
