Skip to main content

Saved cards and accounts

paymentTokens carries the session's saved instruments: cards on file and PayPal accounts the shopper vaulted earlier. They pay like a primary method, and they are not in paymentMethods: a list built from that alone misses every one of them.

Setup​

ProductPurseHeadlessCheckout; PurseCheckoutPayPal to pay with a saved PayPal account
Client sessionCreated with a wallet session (wallet_session in the widget data) for the shopper. The server-side setup is the same as on the web: Saved payment methods
Dashboardsave_token_mode on the card method decides whether new cards can be saved
Project, keysNone

Pay with a saved instrument​

ForEach(checkout.paymentTokens, id: \.id) { token in
SavedRow(token: token, onSelect: { select(.paymentToken(token)) })
}
struct SavedTokenSection: View {
@ObservedObject var checkout: PurseHeadlessCheckout
let token: PaymentToken
/// checkout.paymentElement(for: .paymentToken(token), listener: listener, redirect: presenter)
@ObservedObject var element: PaymentElement

var body: some View {
VStack(spacing: 16) {
if element.hasUI { PaymentElementView(element) } // CVV-only field, when required
Button("Pay") { element.submit() }
.disabled(!element.isReady)
}
.task {
try? checkout.setPrimaryToken(token)
await element.prepare()
}
}
}
TokenhasUIGate
Saved card, requiresCVV == trueA CVV-only secure fieldisPaymentFulfilled waits for a valid CVV, like a fresh card
Saved card, paysWithoutCVV == true (network token, or the PSP does not ask)NothingPayable at selection
Saved PayPal account, isVaultedCard == falseNothingPayable at selection; submit() runs its own /init and captures without an approval step

name, label, brand, maskedPAN, holderName and favorite are there to render the row; checkout.iconURL(for: .paymentToken(token)) gives the brand's badge as an SVG URL on Purse's CDN (schemeAssets(for:) takes a PrimaryMethod, not a token). 3-D Secure may still follow a saved card payment: handle returnedFromRedirect as for any card, which means passing a RedirectPresenter.

Manage the wallet​

All async throws, called from the main actor like the rest of the checkout.

try await checkout.editToken(token, name: "work card")
try await checkout.setTokenFavorite(token) // toggles; reverted locally if the call fails
try await checkout.deleteToken(token)
try await checkout.deleteAllTokens()

paymentTokens and savedSecondaryTokens reflect the change without restarting the checkout; deleting the token that was primary clears the split. deleteAllTokens() removes every wallet token, saved gift cards included. Failures are PurseCheckoutError.walletOperationFailed:

CodeMeaning
MISSING_WALLET_SESSIONThe client session carries no wallet session
MISSING_TOKENThe token is not in the wallet anymore
INVALID_TOKEN_NAMEThe label is not 1–25 alphanumeric or space characters
TOKEN_NAME_EXISTSAnother saved instrument already uses that label
TOKEN_NOT_REGISTERABLE, METHOD_NOT_REGISTERABLESaving is not allowed for this token or method

editToken also has an overload for a SavedSecondaryToken. There is no deleteToken or setTokenFavorite overload for one: see Gift cards.

Save a new card​

Saving a fresh card is decided on the card method before submit: Card, save the card. Saving a gift card: Gift cards, keep the gift card. Creating a PayPal token from the app is not available.

Cards outside the Purse vault

A saved card from a pci_proxy vault has no card plugin on iOS at all: paymentElement(for:) reports METHOD_NOT_IMPLEMENTED rather than rendering a CVV form. Cards vaulted by Purse are fully supported.

Troubleshooting​

SymptomCause
No saved cards appearThe client session was created without a wallet_session
The Pay button never enables on a saved cardrequiresCVV is true and the CVV form is not valid yet, or prepare() was never called
The row renders with no label and no masked PANA vaulted card can arrive with neither. Fall back through name, maskedPAN, brand
setTokenFavorite flips the star then flips it backThe wallet call failed; the optimistic update is reverted and the error rethrown
A saved PayPal account shows a CVV fieldIt should not: isVaultedCard is false for one, so its element has no UI. A CVV form means the token is a card