SDK reference
This page is the map of the SDK's public surface; names are the Swift ones. The public declarations carry DocC comments your IDE shows on Option-click, but the SDK does not yet publish a generated DocC reference the way the Android SDK publishes KDoc.
Products
| Product | Holds |
|---|---|
PurseHeadlessCheckout | PurseHeadlessCheckout, PaymentElement and its listener, the payment model, the card and redirection plugins, the gift card configs, PluginRegistry, PurseTheme, PurseAssets, the error types |
PurseCheckoutApplePay | ApplePayModule, ApplePayPlugin, ApplePayCoordinator, ApplePayConfiguration |
PurseCheckoutPayPal | PayPalModule, PayPalPlugin, PayPalCoordinator |
PurseCheckoutKlarna | KlarnaModule, KlarnaPlugin, KlarnaCoordinator, KlarnaAuthorizationDeclined |
Since 1.1.0, a release tag resolves PurseHeadlessCheckout, PurseCheckoutApplePay and PurseCheckoutKlarna to signed XCFrameworks; PurseCheckoutPayPal is built from source. Because the SDK is binary, its public enums are not frozen: an exhaustive switch over one needs an @unknown default. The three partner products are optional: a card-only merchant links no PassKit, no PayPal SDK and no Klarna SDK. Requirements are iOS 16+, Swift 6 and Xcode 16+.
Creation
public convenience init(
widgetData: String,
lockedEnvironment: PurseEnvironment? = nil,
disabledMethods: Set<MethodKey> = [],
hooks: PurseCheckoutHooks? = nil,
registry: PluginRegistry = .shared,
secondaryConfigs: SecondaryConfigs = .shared,
instanceId: String = UUID().uuidString,
urlSession: URLSession = .shared,
walletAPIRootOverride: String? = nil,
monitoringAPIRootOverride: String? = nil,
restored: Bool = false,
options: PluginOptions = PluginOptions()
) throws
public static func restore(
_ state: CheckoutSavedState,
hooks: PurseCheckoutHooks? = nil,
registry: PluginRegistry = .shared,
secondaryConfigs: SecondaryConfigs = .shared,
urlSession: URLSession = .shared,
options: PluginOptions = PluginOptions()
) throws -> PurseHeadlessCheckout
| Parameter | Meaning |
|---|---|
widgetData | The base64 widget.data string of the client session |
lockedEnvironment | Overrides the environment the session declares; release builds should pass .production |
disabledMethods | MethodKey pairs this build never lists, whatever the session offers; scoped to the instance |
hooks | onBeforeValidate, onAfterValidate, onRedirection |
registry, secondaryConfigs | Substitute a registry or issuer table; both default to the shared singleton |
options | PluginOptions(locale:theme:cardFormTexts:), handed to every plugin the checkout builds; not part of a saved state, so pass it to restore again |
state | A CheckoutSavedState from saveState(), or CheckoutSavedState.fromJSON(_:) |
SDKVersion.value is the SDK version stamped on every monitoring event; checkout.instanceId identifies one checkout in Purse's monitoring. The class is @MainActor and an ObservableObject.
Published state
| Property | Type | Changes when |
|---|---|---|
session | PaymentSession | The session in force; emits again after setSession(_:) |
sessionState | SessionState | .ready → .submitting → .submitted / .error; .expired, .terminated |
paymentMethods | [PaymentItem] | Primaries and gift card methods, each with its live disabled state |
paymentTokens | [PaymentToken] | Saved cards and saved PayPal accounts |
savedSecondaryTokens | [SavedSecondaryToken] | Saved gift cards, balances resolving in the background |
remainingAmountToPay | Decimal | After setPrimary, applySecondary, removeSecondary |
amountSplit | [PaymentSplit] | The slices submitPayment() will send |
isPaymentFulfilled | Bool | The split covers the amount and every form in it is valid |
itemGenerations | [String: Int] | Bumped by setSession(_:) for items that were rebuilt; key your PaymentElementView on it |
isTornDown | Bool | After teardown() |
Plain properties: instanceId, params, disabledMethods, options, onPluginEvent, paymentAPIRoot, currency, isInformationRequest, currentSessionId, secondaryMethods, appliedSecondaries, unfulfilledItemId.
Functions
| Group | Functions |
|---|---|
| Split | setPrimary(_:), setPrimaryToken(_:), clearPrimarySplit(), applySecondary(_:amount:), removeSecondary(_:) (both for SecondaryToken and SavedSecondaryToken) |
| Gift cards | secondaryConfig(for:), getSecondaryToken(method:pan:cvv:) (async), registerSecondary(_:registered:name:), registrationOf(_:) |
| Saving | register(_:registered:name:), registrationOf(_:) |
Wallet (async) | editToken(_:name:), setTokenFavorite(_:), deleteToken(_:), deleteAllTokens(); editToken also takes a SavedSecondaryToken |
| Submit | submitPayment(skipOnBeforeValidate:) (async), pendingRedirect(for:), presentRedirectIfNeeded(…) (async) |
BNPL (async) | simulateLoan(_:product:), simulateLoans(_:product:) |
| Session | setSession(_:) (synchronous, throws), saveState(), teardown(), syncServerClock() (async) |
| Badges | iconURL(for:), schemeAssets(for:) |
| Elements | paymentElement(for:listener:redirect:appReturnURL:), host(for:) for a coordinator you drive yourself, setCollected(_:_:), setFulfilled(_:_:) |
Badges
public func iconURL(for item: PaymentItem) -> String
public func schemeAssets(for method: PrimaryMethod) -> [PurseAssets.CardSchemeAsset]
CDN URL builders resolved for the session's environment, ported from web's @upstreampay/assets. The SDK ships no images: loading and rendering the SVGs is yours. A card brand with no badge is left out rather than pointed at a URL that would 404. PurseAssets also exposes cdnBase(_:), badgeURL(_:badgeName:), resolveIconURL(partner:method:isSecondary:environment:) and buildCardSchemeAssets(brands:environment:) directly.
Payment element
public func paymentElement(
for item: PaymentItem,
listener: any PaymentElementListener,
redirect: (any RedirectPresenter)? = nil,
appReturnURL: URL? = nil
) -> PaymentElement
Build an element when the shopper selects an item, not for every row: building runs the partner's prepare, which for Klarna and Apple Pay is a POST /init. A redirection method's /init runs later, at submit.
PaymentElement exposes item, hasUI, selfSubmitting, the published isReady, makeView() -> UIView?, prepare() (idempotent, async) and submit(), which returns the Task it runs on. PaymentElementView is the UIViewRepresentable that renders it in SwiftUI; give it an .id that changes with the item's generation.
PaymentElementListener is a @MainActor protocol with onReady(_:), onOutcome(_:), onRequiresAction() and onPaymentId(_:), every member defaulted.
public enum PaymentOutcome {
case completed(ValidationResult) // status: PaymentStatus, isSuccess
case returnedFromRedirect(url: URL?) // not an outcome: confirm the status on your backend
case canceled
case failed(any PurseError) // error.code: PurseErrorCode
}
Coordinators, for an app that draws its own wallet button or drives a native step itself: CardCoordinator and the redirect presenters in the core product, ApplePayCoordinator, PayPalCoordinator and KlarnaCoordinator in their own. Each takes a PluginHost from checkout.host(for:).
Plugins and partner modules
PluginRegistry.shared.install(ApplePayModule(configuration: applePayConfiguration))
PluginRegistry.shared.install(PayPalModule())
PluginRegistry.shared.install(KlarnaModule())
Swift has no ServiceLoader, so a partner product is installed explicitly, never discovered. PluginRegistry resolves a plugin in the same order as web and Android (vault vendor, template, redirection integration mode, exact (partner, method), method fallback) through registerVault(vendor:builder:), registerGeneric(template:builder:), register(partner:method:builder:) and registerFallback(method:builder:). modules lists what is installed, in order; installing the same module twice is a no-op. Card and redirection are built in.
PartnerModule is the registration entry point (id, register(into:), renders(_:), surface(for:request:)); PartnerUIPlugin is the payment contract (preValidation, postValidation, onSessionSet, onAmountUpdate, validateUI, ensureClientCompatibility, destroyClient), and PluginHost is what a coordinator asks of the checkout. SecondaryConfigs.register adds a gift card issuer through the SecondaryConfig protocol.
Plugin events
public var onPluginEvent: ((CheckoutPluginEvent) -> Void)?
public struct CheckoutPluginEvent {
public let itemId: String
public let event: PluginEvent
}
public enum PluginEvent {
case ready
case redirect
case initFailed(message: String?)
case clientError(message: String?)
case partnerError(message: String?, recoverable: Bool)
case cancelled
case endOfSession
}
A coordinator reports its partner's native step through PluginHost.dispatch(_:). The checkout reacts first, then calls onPluginEvent: initFailed and clientError disable the method with PARTNER_UI_FATAL_ERROR, partnerError with PARTNER_UI_RECOVERABLE_ERROR or PARTNER_UI_FATAL_ERROR depending on recoverable, ready clears a recoverable state, and endOfSession sets sessionState to .terminated. PluginEvent.code is the wire name ("InitFailed", …) shared with Android, sent on HEADLESS_CHECKOUT_PARTNER_UI_EVENT. See Method states.
Types
| Type | Notes |
|---|---|
PaymentItem (enum) | primaryMethod, secondaryMethod, paymentToken, savedSecondaryToken; id, partner, method, configuration, disabled, isSecondary, minAmount, maxAmount |
PrimaryMethod | canBeRegistered, saveTokenMode, simulable, integrationType, supportedBrands |
PaymentToken | isVaultedCard, requiresCVV, paysWithoutCVV, label, brand, maskedPAN, holderName, favorite, tokenSource, methodId |
SecondaryToken | pan, cvv, balance, takePolicy, isSaved, canBeRegistered |
SavedSecondaryToken | pan, name, balance, totalBalance, balanceResolved, favorite, disabled |
DisabledState (enum) | code, message, isFiltered, isTechnicallyDisabled, requiresUIClear, isRecoverable, isRecomputable; see Method states |
PaymentSplit, SplitSource, PrimarySource | amount, source; what submitPayment() sends |
MethodKey | partner + method; id is the item id ("paypal-wallet") |
Partner (enum) | .purse, .aci, .adyen, .paypal, .illicado, .klarna, and .other(wireValue) for everything else; wireValue, fromWire(_:) |
PaymentMethod (enum) | .creditCard, .applePay, .wallet, .giftCard, .bnpl, .installments, and .other(wireValue); wireValue, fromWire(_:) |
PaymentSession, SessionState | id, amount, currency, expirationDate, meansOfPayment; .ready, .expired, .submitting, .submitted, .terminated, .error |
CheckoutSavedState | Snapshot from saveState(); toJSON(), fromJSON(_:), savedAt. Holds no card data |
LoanProduct, LoanSimulation | Input and output of BNPL simulation |
ValidationResult | globalStatus, status: PaymentStatus, isSuccess, redirection, raw |
PaymentStatus (enum) | .success, .failed, .error, .uncertain, .waiting, .unknown: the API's global_status |
RedirectAction (enum) | .get, .postForm, .postHTML, each with a returnURLPrefix; what onRedirection receives |
RedirectPresenter, WebAuthRedirectPresenter, RedirectOutcome, RedirectPolicy | Presenting a 3-D Secure or PSP page, and what came back |
PurseEnvironment (enum) | .development, .test, .sandbox, .production, each with its API roots |
PurseError (protocol) | code: PurseErrorCode, message, underlying, documentationLink; conformers in Error codes |
PurseCheckoutHooks | onBeforeValidate, onAfterValidate, onRedirection |
CheckoutEventCode, EventLevel, CheckoutMonitoringEvent | The monitoring vocabulary reported to Purse |
Presentation
PurseTheme maps native UIColor and UIFont onto Secure Fields' own style: input, inputFocus, inputInvalid, inputValid, label, labelInvalid, labelValid, helperText, helperTextInvalid, gap, plus global* fallbacks, with PurseTheme.default and resolve(). It travels with CardFormTexts inside PluginOptions(locale:theme:cardFormTexts:).
Pass PluginOptions to PurseHeadlessCheckout(widgetData:…, options:) (and to restore(_:…, options:)): every plugin the checkout builds receives it, so a card form from paymentElement(for:) is themed, and its locale is also the one sent in a 3-D Secure step's browser data. A CardCoordinator(host:tenantId:supportedBrands:token:options:) you drive yourself takes its own.