Skip to main content
This page covers the official Dodo Payments iOS checkout SDK for Swift. It opens Dodo Payments hosted checkout in a native browser view and returns a typed result.

Checkout Sessions API

Create the checkout_url that this SDK opens, from your backend.

Mobile Integration Guide

See how this SDK fits into the full mobile payment flow.
The iOS SDK opens Dodo Payments hosted checkout in SFSafariViewController and returns a typed CheckoutResult when the customer finishes or leaves checkout. It holds no API key and contains no networking code, so it never calls the Dodo Payments API. Checkout runs in the browser view. The SDK presents and dismisses that view and reads the result from the return URL. Requirements: iOS 16 or later, and Swift 6.2 or later (the package declares swift-tools-version: 6.2). The SDK has no third-party dependencies.

Installation

1

Add the Package

In Xcode, go to File → Add Package Dependencies and enter the package URL:
Select version 1.1.0 or later. Appearance customization requires 1.1.0.To add the package in Package.swift instead, add this dependency:
Package.swift
The library product is DodoCheckout.
2

Register a Callback URL Scheme

Register a URL scheme so that iOS routes the checkout’s return URL back to your app. Add a URL type to your Info.plist:
Info.plist
You can also add the URL type in Xcode under Info → URL Types.Use this scheme in the returnUrl you pass to the SDK, for example myapp://checkout/return, and set the same URL as the checkout session’s return_url when your backend creates the session. The SDK matches the return URL on scheme, host, and path. The URL doesn’t need to load a real page.

Usage

DodoCheckout.start is an async function that runs on the main actor. Pass checkoutUrl as a URL built from the checkout_url your backend returns:
onEvent receives .opened, .returnReceived, and .closed events. Their name values are checkout.opened, checkout.return_received, and checkout.closed. Use events for logging only, never to decide the outcome.

Forwarding the Return URL

SFSafariViewController can’t catch its own return URL, so iOS opens the URL in your app instead. Forward every incoming URL to DodoCheckout.handleOpenURL(_:). In an app without scenes, call it from your app delegate’s application(_:open:options:).
You can forward every URL. handleOpenURL acts only on a URL that matches the returnUrl of the checkout in progress, and returns true for it. For any other URL, it returns false, so handle that URL yourself.

What the Result Means

The SDK builds CheckoutResult from the query parameters on the return URL.
result.status is a UI hint, not proof of payment. Confirm every payment from your backend, with the payment.succeeded or subscription.active webhook.
CheckoutStatus
required
One of five values:
  • succeeded: the return URL has status=succeeded (one-time payment) or status=active (subscription).
  • failed: the payment was declined (status=failed).
  • cancelled: the customer dismissed the sheet before the return URL arrived. The SDK doesn’t know the outcome, and the payment may have succeeded, so don’t show a failure screen. Reconcile the abandoned session instead.
  • pending: the payment settles later (status=processing or any requires_* value), or the status parameter was missing or unrecognized. Reconcile it like cancelled.
  • expired: the checkout session expired (status=expired).
String?
The payment_id query parameter, when the return URL includes one. Show it in your UI, but don’t use it to grant access. See Verify the Payment.
String?
The subscription_id query parameter. Set for subscription checkouts.
[String]?
The license_key query parameter. Set when the checkout includes license key products.
String?
The email query parameter. Set when checkout captures an email address.
[String: String]
Every query parameter from the return URL, verbatim.

Verify the Payment

Webhooks

Dodo Payments calls your backend when a payment succeeds or a subscription activates.

Get Payment Detail

Look up paymentId with your secret key to check its status.
Grant access only after one of these confirms the payment. Don’t rely on result.status alone.

Appearance Customization

To change the sheet’s dismiss button, presentation style, and color scheme, pass a BrowserCustomization as customization to start(...). Every field is optional. For a nil field, the SDK doesn’t set that option and iOS applies its own default. The exception is presentationStyle, where nil means pageSheet.
DismissButtonStyle?
Style of the dismiss button: done, close, or cancel. iOS decides whether it renders as a label or an icon.
PresentationStyle?
pageSheet (the default) presents a card that the customer can swipe down to dismiss. fullScreen covers the whole screen and has no dismiss gesture.
Bool?
Lets the toolbar collapse as the page scrolls. It has a visible effect only when presentationStyle is fullScreen. With pageSheet, the bars stay pinned regardless of this setting.
ColorScheme?
light or dark forces that appearance regardless of the device’s system setting. system follows the system setting. This option themes only the native controls around the page. The checkout page’s own light or dark mode comes from customization.theme on the checkout session, and its colors come from customization.theme_config.
iOS has no toolbar color option. The underlying SFSafariViewController tint properties are deprecated as of iOS 26.

Errors

start throws CheckoutError only for misuse or a platform failure. Read the reason from error.code. A customer who cancels, or a declined payment, is always a result, never a thrown error.
  • invalidCheckoutUrl (INVALID_CHECKOUT_URL): checkoutUrl isn’t an https checkout session URL (path starting with /session/) on checkout.dodopayments.com or test.checkout.dodopayments.com.
  • invalidReturnUrl (INVALID_RETURN_URL): returnUrl isn’t an absolute URL with a scheme and a host.
  • alreadyInProgress (ALREADY_IN_PROGRESS): another checkout is running. Only one checkout can run at a time.
  • platformError (PLATFORM_ERROR): an unexpected platform failure, such as no view controller to present from.
After a thrown error, check for an abandoned session too. If the sheet didn’t confirm that it appeared, the SDK keeps the session on record because checkout may still be open. The exception is alreadyInProgress: a record you find then belongs to the checkout that is still running.

Abandoned Sessions

The SDK records the checkout session when it presents checkout, and clears the record only when checkout ends with succeeded, failed, or expired. The record stays when the app is killed during checkout, and after a cancelled or pending result. Check for it on the next launch and after every cancelled or pending result.
abandoned.sessionId is the checkout session ID, which starts with cks_. abandoned.createdAt is the Date checkout started. Your backend can look up the session with Get Checkout Session, which returns its payment_id and payment_status. Until the payment reaches a final status, treat it as pending, not failed.

Mobile Integration Guide

The same contract for Android, React Native, and Flutter.

React Native SDK

Wraps this same Swift core on iOS.
Last modified on September 25, 2026