Card (Purse vault)
The card method renders the Android Secure Fields form: card number, expiry, CVV and holder. Card data is tokenized by the Purse vault and never reaches your code.
Setup
| Artifact | eu.purse:headless-checkout-android, nothing else |
| Dashboard | A card method whose vault is Purse (vault.vendor = purse) activated on the entity, with any acquiring partner |
| Manifest | Nothing. 3-D Secure opens in an Activity the SDK declares |
| Keys | None in the app. The vault tenant travels in the client session |
A card method hosted by a PSP's own form (Adyen, Stripe, Checkout.com, a pci_proxy vault…) has no native form and is not rendered by this SDK. Configure the method in redirection mode to offer it through the redirection guide.
Render and pay
val element = rememberPaymentElement(checkout, cardMethod, listener)
LaunchedEffect(cardMethod.id) { checkout.setPrimary(cardMethod) }
PaymentElementUi(element, Modifier.fillMaxWidth())
Button(onClick = { element.submit() }, enabled = element.ready) { Text("Pay") }
An empty or invalid form is never payable: element.ready and isPaymentFulfilled stay false until every field validates, and submit() on an invalid form is a no-op, not an error (the fields show their own messages). submit() tokenizes the card, runs /init and /validate, then follows 3-D Secure in the SDK's Activity.
checkout.schemeAssets(cardMethod) gives the accepted schemes with icon and label, to show above the form. Co-badged cards default to the first scheme the BIN lookup matches.
Outcomes
| Outcome | When |
|---|---|
Completed | /validate answered without a challenge. status is the API's verdict |
ReturnedFromRedirect | The shopper came back from 3-D Secure. Not an outcome. Show a processing state; your backend confirms the payment |
Canceled | The 3-D Secure page was dismissed |
Failed | Tokenization refused, /init or /validate failed. error.code says which |
Save the card
When the method allows it (save_token_mode PREFER or PREFER_NO in the dashboard), canBeRegistered is true and the shopper can keep the card for the next session:
if (cardMethod.canBeRegistered) {
checkout.register(cardMethod, registered = true, name = "holiday card")
}
val decision = checkout.registrationOf(cardMethod) // observable before and after submit
register throws WalletOperationFailed with METHOD_NOT_REGISTERABLE when the method cannot save cards, INVALID_TOKEN_NAME or TOKEN_NAME_EXISTS on a bad label. Saving needs a wallet session in the client session: see Saved cards and accounts.
Customize the form
Locale, texts and theme come from PluginOptions at init: Customization.
Troubleshooting
| Symptom | Cause |
|---|---|
| The Pay button never enables | A field is invalid. The form shows which; element.ready mirrors it |
| The form loses its input on recomposition | You built your own coordinator without remembering the host. Use rememberPaymentElement, or remember(method.id) { checkout.host(method) } |
Failed with API_ERROR on submit, message creditcard init not successful | The /init was refused for this item. Check the method's configuration on the entity |
| The form does not initialize at all | The session carries no vault tenant: the card method is not vaulted by Purse |
| A 3-D Secure page refuses to render | Some issuers block embedded WebViews. Report the issuer to Purse support |