Skip to main content

Method states

A method the session offers but that cannot be used right now stays a PaymentItem with a non-nil disabled: DisabledState?. The state has a code, a displayable message, and five boolean behaviours that tell you how to render the method and whether it will come back. The vocabulary is the web SDK's DisabledState, so all three platforms react the same way.

guard let state = item.disabled else {
renderSelectable(item)
return
}
if state.isFiltered {
return // never in paymentMethods: you will not see it
} else if state.isTechnicallyDisabled {
hideOrGreyOut(state.message) // no UI can be mounted
} else if state.isRecoverable {
dim(state.message) // keep the element alive, it comes back
} else {
greyOut(state.message) // amount-driven or submitting, lifts by itself
}

The five behaviours​

BehaviourPropertyMeaningExpected reaction
FilteredisFilteredThe item is removed from paymentMethods, paymentTokens and savedSecondaryTokens altogetherNothing to render. The method never existed for this session
Technically disabledisTechnicallyDisabledA hard runtime failure. paymentElement(for:) builds an element with no UI that reports .failed(methodDisabled) on submit()Grey the row out with message, or hide it. Final for the session
Requires UI clearrequiresUIClearAny mounted UI for the item should be torn downDrop your PaymentElement and stop rendering its PaymentElementView
RecoverableisRecoverableBroken-for-now, not broken-for-goodDim the row, keep the element alive. It is re-evaluated on the next recompute
RecomputableisRecomputableRe-evaluated whenever the amount left to pay changesNothing: the state lifts by itself when the split moves. Everything outside this group is final

The codes​

DisabledState.Code declares 11 codes. The web SDK has fourteen; the last three describe DOM situations and do not exist natively, so they are absent here as they are on Android.

CodeCauseFilteredTechnicalClear UIRecoverableRecomputableExpected action
METHOD_UNSTABLEThe platform suspended the method for this session (configurations.stable == false)✓None: not listed
AMOUNT_CONDITION_UNREACHABLEpayment_min_amount exceeds the whole order: no split could ever enable it✓None: not listed
PLUGIN_INCOMPATIBLEA plugin's ensureClientCompatibility rejected this client or session✓None: the method disappears from the list. Set at creation when a plugin refuses the device, e.g. Apple Pay where PKPaymentAuthorizationController.canMakePayments() is false
FATAL_ERROR_BEFORE_RENDERA plugin failed before it could mount any UI✓None: not listed. Declared, and not set by the SDK today
TOKEN_INIT_FAILEDA saved gift card's balance could not be read from the issuer this session✓✓Show the saved card as unavailable; offer to enter it again
PARTNER_UI_FATAL_ERRORA partner reported an unrecoverable failure while mounted✓✓Grey out or hide the method with message; offer the others
PARTNER_UI_RECOVERABLE_ERRORA partner reported a failure it expects to come back from✓✓Dim the method, keep its element alive; a later ready event from the partner clears it
MIN_AMOUNT_NOT_METWhat is left for the primary to pay is under the method's minimum (a gift card took too much)✓✓Grey out with message; removing a gift card lifts it
MAX_AMOUNT_EXCEEDEDWhat is left to pay is over the method's maximum. Never applies to a secondary: a gift card may hold more than the amount owed✓✓Grey out; applying a gift card lifts it
PAYMENT_FULFILLEDGift cards cover the whole amount: no primary is needed✓✓Grey out every primary; show a Pay button that calls submitPayment()
SUBMITTINGA payment is in flight: every otherwise enabled method is locked until submitPayment returnsShow a progress indicator; lifted whatever the outcome

The amount-driven states carry their context: .minAmountNotMet(minAmount:restToPay:), .maxAmountExceeded(maxAmount:restToPay:), .amountConditionUnreachable(minAmount:maxAmount:orderAmount:). .partnerUIFatalError(reason:), .partnerUIRecoverableError(reason:) and .fatalErrorBeforeRender(reason:) carry the partner's reason.

Where states come from​

MomentStates set
Session build (init, setSession)METHOD_UNSTABLE and AMOUNT_CONDITION_UNREACHABLE from MethodAvailability.buildTimeDisabled, then the amount-driven ones; filtered states are dropped before you see the list
Every change of remainingAmountToPayMIN_AMOUNT_NOT_MET, MAX_AMOUNT_EXCEEDED, PAYMENT_FULFILLED recomputed. An item whose current state is not isRecomputable is left untouched
A saved gift card's background /initTOKEN_INIT_FAILED on that SavedSecondaryToken
Checkout creation, per installed pluginPLUGIN_INCOMPATIBLE when the plugin's ensureClientCompatibility refuses this device or session
A partner's plugin event (onPluginEvent)initFailed or clientError → PARTNER_UI_FATAL_ERROR; partnerError → PARTNER_UI_RECOVERABLE_ERROR if recoverable, else PARTNER_UI_FATAL_ERROR; ready clears a recoverable state
submitPayment entered and returnedSUBMITTING overlaid on every otherwise enabled item, then lifted. It is a published overlay only, so a real state underneath is never erased
Partner failures

A partner failure moves the item into a PARTNER_UI_* state, which onPluginEvent announces; if it happens during a payment, the element's listener also gets a PaymentOutcome.failed (Events and monitoring). Only FATAL_ERROR_BEFORE_RENDER is declared and never set by the SDK today; handle it anyway, since the behaviour flags are what your rendering should branch on.

Every transition into a new state is reported to Purse's monitoring as HEADLESS_CHECKOUT_METHOD_DISABLED, at warning level for a technical failure (isTechnicallyDisabled, or FATAL_ERROR_BEFORE_RENDER) and at info level for a nominal one. Only the state's code is sent, never its message, which can quote amounts.

Selecting a disabled method

setPrimary(_:) and setPrimaryToken(_:) refuse a disabled item with PurseCheckoutError.methodDisabled (METHOD_DISABLED), whose disabled associated value is this state. Rendering the row from item.disabled avoids the error altogether.