Skip to main content

Error codes

Every PurseError carries one PurseErrorCode. PurseErrorCode declares 33 codes, the same set as the Android SDK. The names match the web SDK's error codes exactly, and error.documentationLink points at the matching anchor of that page. This page lists what each code means on iOS and the action expected from your app.

PurseError is a protocol with five conforming types: PurseSessionError (decoding the session), PurseCheckoutError (the checkout's own rules), PartnerUIError (a partner's native step), PaymentAPIError (HTTP and transport) and, in the Klarna product, KlarnaAuthorizationDeclined. All five expose code, message, underlying and documentationLink. Several PurseCheckoutError cases take an explicit PurseErrorCode, so one case covers several codes.

Session​

CodeCaseMeaningExpected action
SESSION_MALFORMEDPurseSessionError.sessionMalformedThe widgetData string is not a client session, or an unknown shapeA bug in the hand-over from your backend. Do not show a checkout; log and fix
EXPIRED_SESSIONPurseCheckoutError.sessionExpiredThe session expired, or setSession was handed an expired one. sessionState is .expiredMint a new client session and call setSession(_:)
SUBMIT_BLOCKEDPurseCheckoutError.submitBlockedA submit is in flight, or the session already paid, was declined, expired (once the expiry watchdog has set .expired) or terminated. state says which. Native onlyDo nothing on .submitting; otherwise confirm the payment status on your backend and move on
CHECKOUT_TORN_DOWNPurseCheckoutError.checkoutTornDownA call that touches the network or changes the session after teardown(). Native onlyA lifecycle bug: the checkout outlived its screen. Build a new one

Payment​

CodeCaseMeaningExpected action
PAYMENT_NOT_FULFILLED_ON_SUBMITPurseCheckoutError.paymentNotFulfilledThe split does not cover the amount, or no primary is setGuard your Pay button with isPaymentFulfilled; highlight the missing amount
ELEMENT_NOT_FULFILLEDPurseCheckoutError.itemNotFulfilledA form in the split is incomplete or invalid (the card form, a CVV-only field)Same guard; focus the form, its fields show their own errors
METHOD_DISABLEDPurseCheckoutError.methodDisabledsetPrimary or setPrimaryToken on a disabled item, or paymentElement(for:) on a technically disabled one. disabled says whyRender the item as disabled from its disabled state; see Method states
METHOD_NOT_IMPLEMENTEDPurseCheckoutError.noPluginRegistered, .noSecondaryConfigNo plugin for the chosen method (usually a partner module that was never installed), or no config for a gift card issuerPluginRegistry.shared.install(…), or disabledMethods; register a SecondaryConfig
ON_BEFORE_VALIDATE_HOOK_FAILEDPurseCheckoutError.hookFailed(hook: "onBeforeValidate", …)Your onBeforeValidate threw; nothing was sent, the split is intactYour own failure is underlying; fix or show it, the shopper can submit again
POST_VALIDATE_FAILEDPurseCheckoutError.hookFailed(hook: "onRedirection", …), .postValidationFailedYour onRedirection threw and the redirect was not opened; or /validate answered and a plugin's postValidation then threwFor the hook: /validate already answered, so the session is spent and a new submit throws SUBMIT_BLOCKED; confirm the payment status on your backend. For postValidationFailed: the payment is real: result holds /validate's answer and the session is spent
VALIDATE_FAILEDPurseCheckoutError.validateFailedThe /validate call itself failed. The transport error is kept as underlyingIf the chain holds a PaymentAPIError with outcomeUnknown, confirm the payment status on your backend before showing anything; else show an error
PARTNER_UI_ERRORPartnerUIError, and KlarnaAuthorizationDeclinedA partner's native step failed: its SDK errored, a tokenization was refused, a coordinator was driven before it was prepared, a module is missing what only your app can give it. The partner's error is underlying. Native onlyShow an error for this method and let the shopper pick another
UI_VALIDATION_FAILED_ERRORPurseCheckoutError.hookFailed can carry itDeclared for parity with the web SDK; no iOS code path raises it

Gift cards (secondary methods)​

CodeCaseMeaningExpected action
SECONDARY_METHOD_WRONG_PAN_FORMATPurseCheckoutError.invalidSecondaryCardThe card number does not match the issuer's formatValidate with secondaryConfig(for:)'s validatePAN first; show a field error
SECONDARY_METHOD_WRONG_CVV_FORMAT.invalidSecondaryCardThe CVV does not match the issuer's formatSame with validateCVV
SECONDARY_METHOD_REQUIRED_CVV.invalidSecondaryCardThis card needs a CVV and none was givenAsk for it when the config's requiresCVV(pan) is true
FAILED_TO_INIT_SECONDARY_TOKENPurseCheckoutError.secondaryTokenizationFailedThe issuer refused the cardShow "card not accepted"; let the shopper correct it
SECONDARY_TOKEN_EMPTY_BALANCE.secondaryTokenizationFailedThe card has no balance, or a saved card's balance is not resolved yetShow the empty balance; for a saved card wait for balanceResolved
INVALID_TAKE_AMOUNTPurseCheckoutError.invalidTakeapplySecondary(_:amount:) with a negative amount, above the balance or above what is leftClamp the slice to min(balance, remainingAmountToPay)
MAX_AMOUNT_REACHED.invalidTakeThe slice exceeds the issuer's maximum per paymentClamp to the method's maxAmount
USAGE_LIMIT_ERROR.invalidTakeThe issuer's per-session usage limit is spent by the cards already in the splitTell the shopper no more cards of this issuer can be added

Wallet and saved instruments​

CodeCaseMeaningExpected action
MISSING_WALLET_SESSIONPurseCheckoutError.walletOperationFailedSaving, renaming, favoriting or deleting needs a wallet session the client session does not carryHide wallet features, or create the client session with a wallet session
MISSING_TOKEN.walletOperationFailedThe token is not in the wallet anymoreRefresh the list from paymentTokens
INVALID_TOKEN_NAME.walletOperationFailedThe label does not match ^[a-zA-Z0-9\s]{1,25}$Show a field error
TOKEN_NAME_EXISTS.walletOperationFailedAnother saved instrument carries that labelShow a field error
TOKEN_NOT_REGISTERABLE.walletOperationFailedregisterSecondary on a gift card the issuer does not let you keep, or on one that is already savedCheck canBeRegistered before offering to save
METHOD_NOT_REGISTERABLE.walletOperationFailedregister on a card method whose save_token_mode forbids savingCheck canBeRegistered before offering to save
SET_WALLET_SESSION_FAILED, FAILED_TO_DELETE_ALL_TOKENS.walletOperationFailed can carry eitherDeclared for parity with the web SDK; no iOS code path raises them. A failing wallet call surfaces as API_ERROR, and a wallet session that could not be attached is reported to monitoring only

Instalment simulation​

CodeCaseMeaningExpected action
METHOD_IS_NOT_SIMULABLEPurseCheckoutError.methodNotSimulablesimulateLoan on a method without the simulable labelOffer the simulation only when method.simulable
INVALID_LOAN_SIMULATIONPurseCheckoutError.loanSimulationFailedThe simulation API refused the request or returned no usable scheduleHide the schedule; the method stays payable

Transport​

CodeCaseMeaningExpected action
API_ERRORPaymentAPIErrorA Payment API call failed: transport failure (status == nil), non-2xx/3xx status, or an unusable response. errorCode is the API's own reason, path the request, requestSent whether any byte leftisSessionTerminal: new session. isRetryable: offer a retry. outcomeUnknown: confirm, never report as failed. Otherwise show an error
UNKNOWN_ERRORDeclared for parity with the web SDK. This SDK never raises an untyped error, and a meta-test in the repository keeps it the only uncovered code

Codes that exist only on the web​

BRAND_NOT_BELONGS_TO_DETECTED, ELEMENT_RENDER_FAILED, PAYMENT_TOKEN_NOT_FOUND_IN_SESSION, INVALID_INIT_PARAMS, FAILED_TO_ADD_TOKEN and TOKEN_ALREADY_EXISTS describe situations that cannot happen with a native element, and are not declared in PurseErrorCode at all. SUBMIT_BLOCKED, CHECKOUT_TORN_DOWN and PARTNER_UI_ERROR go the other way: the web SDK only logs them, or has no lifecycle end to report; both native SDKs raise them.