Recipe: Show saved methods count
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:vault-methods-update', (event) => {
const { vaultedPayments } = event.detail;
const count = vaultedPayments.size();
document.getElementById('saved-methods-badge').textContent =
count > 0 ? `${count} saved` : '';
});
Use This renders a list of saved payment methods with card brand icons, masked card numbers, and expiry dates. The customer can select a saved method to pay with it.
PrimerVaultedPaymentMethods to render saved methods:Copy
Ask AI
val checkout = rememberPrimerCheckoutController(clientToken)
val vaultedController = rememberVaultedPaymentMethodsController(checkout)
PrimerVaultedPaymentMethods(controller = vaultedController)
Copy
Ask AI
PrimerCheckout(
clientToken: clientToken,
scope: { checkoutScope in
Task {
for await state in checkoutScope.paymentMethodSelection.state {
let count = state.paymentMethods.count
print(count > 0 ? "\(count) saved" : "No saved methods")
}
}
}
)
Recipe: List saved cards
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:vault-methods-update', (event) => {
const methods = event.detail.vaultedPayments.toArray();
const container = document.getElementById('saved-cards');
container.innerHTML = methods
.filter((m) => m.paymentMethodType === 'PAYMENT_CARD')
.map((m) => `
<div class="saved-card">
${m.paymentInstrumentData.network} •••• ${m.paymentInstrumentData.last4Digits}
</div>
`)
.join('');
});
For full control over how saved methods are displayed, observe the controller’s state directly:
Copy
Ask AI
@Composable
fun CustomVaultedMethods(
checkout: PrimerCheckoutController,
) {
val vaultedController = rememberVaultedPaymentMethodsController(checkout)
val vaultedState by vaultedController.state.collectAsStateWithLifecycle()
Column {
vaultedState.paymentMethods.forEach { method ->
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp),
onClick = { vaultedController.select(method) },
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column(Modifier.weight(1f)) {
Text(
text = method.paymentMethodType,
style = MaterialTheme.typography.bodyLarge,
)
Text(
text = method.description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
if (vaultedState.selectedMethod == method) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Selected",
tint = MaterialTheme.colorScheme.primary,
)
}
}
}
}
}
}
Replace the payment method item to show vaulted methods with custom UI:
Copy
Ask AI
checkoutScope.paymentMethodSelection.paymentMethodItem = { paymentMethod in
AnyView(
HStack {
if let icon = paymentMethod.icon {
Image(uiImage: icon)
.resizable()
.frame(width: 40, height: 28)
}
VStack(alignment: .leading) {
Text(paymentMethod.name)
.font(.body)
if paymentMethod.type == "PAYMENT_CARD" {
Text("Saved card")
.font(.caption)
.foregroundColor(.secondary)
}
}
Spacer()
}
.padding(.vertical, 10)
.padding(.horizontal, 16)
)
}
How it works
- Web
- Android
- iOS
- Listen for the
primer:vault-methods-updateDOM event - Access
vaultedPaymentsfrom the event detail - Use
.size()to get the count or.toArray()to iterate over methods - Each method contains
paymentMethodTypeandpaymentInstrumentDatawith card details
- Create a vaulted payment methods controller using
rememberVaultedPaymentMethodsController() - Use
PrimerVaultedPaymentMethodsfor the default rendering, or observe the controller’s state for a custom UI - To save new payment methods, call
cardFormController.setVaultOnSuccess(true)before payment
Vaulting requires a
customerId in your client session. Pass it when creating the session on your server.- Access
checkoutScope.paymentMethodSelectionin thescopeclosure - Observe
stateviafor awaitto getPrimerPaymentMethodSelectionState - Read
state.paymentMethodsfor the list of saved methods - Use
payWithVaultedPaymentMethod()orpayWithVaultedPaymentMethodAndCvv(_:)to pay
Vaulting requires a
customerId in your client session. Pass it when creating the session on your server.Variations
Save and display methods
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:vault-methods-update', (event) => {
const methods = event.detail.vaultedPayments.toArray();
const container = document.getElementById('saved-methods');
container.innerHTML = methods
.map((method) => {
if (method.paymentMethodType === 'PAYMENT_CARD') {
const { network, last4Digits } = method.paymentInstrumentData;
return `
<div class="saved-method">
<img src="/icons/${network.toLowerCase()}.svg" alt="${network}">
<span>•••• ${last4Digits}</span>
</div>
`;
}
return `
<div class="saved-method">
<img src="/icons/${method.paymentMethodType.toLowerCase()}.svg" alt="${method.paymentMethodType}">
<span>${method.paymentMethodType}</span>
</div>
`;
})
.join('');
});
To save a new payment method after a successful payment, call
setVaultOnSuccess() on the card form controller:Copy
Ask AI
val cardFormController = rememberCardFormController(checkout)
LaunchedEffect(cardFormController) {
cardFormController.setVaultOnSuccess(true)
}
Pay with a saved method using
payWithVaultedPaymentMethod():Copy
Ask AI
// Pay with vaulted method (no CVV required)
await checkoutScope.paymentMethodSelection.payWithVaultedPaymentMethod()
// Pay with CVV verification
await checkoutScope.paymentMethodSelection.payWithVaultedPaymentMethodAndCvv("123")
Inline saved methods with card form
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:vault-methods-update', (event) => {
const methods = event.detail.vaultedPayments.toArray();
methods
.filter((m) => m.paymentMethodType === 'PAYMENT_CARD')
.forEach((method) => {
const { network, last4Digits, expirationMonth, expirationYear } =
method.paymentInstrumentData;
console.log(
`${network} •••• ${last4Digits} - Expires ${expirationMonth}/${expirationYear}`,
);
});
});
This example shows saved methods above the card form, letting the customer choose between a saved method or entering new card details:
Copy
Ask AI
@Composable
fun InlineCheckoutWithVault(clientToken: String) {
val checkout = rememberPrimerCheckoutController(clientToken)
val state by checkout.state.collectAsStateWithLifecycle()
when (state) {
is PrimerCheckoutState.Loading -> {
CircularProgressIndicator()
}
is PrimerCheckoutState.Ready -> {
PrimerCheckoutHost(
checkout = checkout,
onEvent = { event ->
when (event) {
is PrimerCheckoutEvent.Success -> { }
is PrimerCheckoutEvent.Failure -> { }
}
},
) {
val vaultedController = rememberVaultedPaymentMethodsController(checkout)
val cardFormController = rememberCardFormController(checkout)
LaunchedEffect(cardFormController) {
cardFormController.setVaultOnSuccess(true)
}
Column(Modifier.padding(16.dp)) {
Text(
text = "Saved payment methods",
style = MaterialTheme.typography.titleMedium,
)
Spacer(Modifier.height(12.dp))
PrimerVaultedPaymentMethods(controller = vaultedController)
Spacer(Modifier.height(24.dp))
HorizontalDivider()
Spacer(Modifier.height(24.dp))
Text(
text = "Or pay with a new card",
style = MaterialTheme.typography.titleMedium,
)
Spacer(Modifier.height(12.dp))
PrimerCardForm(controller = cardFormController)
}
}
}
}
}
Show saved methods alongside the card form, letting the customer choose between a saved method or entering new card details:
Copy
Ask AI
PrimerCheckout(
clientToken: clientToken,
scope: { checkoutScope in
// Show all saved payment methods
checkoutScope.paymentMethodSelection.showAllVaultedPaymentMethods()
// Show other ways to pay (card form, etc.)
checkoutScope.paymentMethodSelection.showOtherWaysToPay()
}
)
Conditional UI based on saved methods
- Web
- Android
- iOS
Copy
Ask AI
document.addEventListener('primer:vault-methods-update', (event) => {
const { vaultedPayments } = event.detail;
const hasSavedMethods = vaultedPayments.size() > 0;
document.getElementById('saved-methods-section').style.display = hasSavedMethods
? 'block'
: 'none';
document.getElementById('new-payment-section').querySelector('h3').textContent =
hasSavedMethods ? 'Or pay with a new method' : 'Payment method';
});
The bottom sheet shows vaulted methods automatically when they are available. No extra configuration is needed beyond providing a
customerId in the client session:Copy
Ask AI
@Composable
fun CheckoutWithSavedMethods(clientToken: String) {
val checkout = rememberPrimerCheckoutController(clientToken)
val state by checkout.state.collectAsStateWithLifecycle()
when (state) {
is PrimerCheckoutState.Loading -> {
CircularProgressIndicator()
}
is PrimerCheckoutState.Ready -> {
PrimerCheckoutSheet(
checkout = checkout,
onEvent = { event ->
when (event) {
is PrimerCheckoutEvent.Success -> { }
is PrimerCheckoutEvent.Failure -> {
Log.e("Checkout", "Failed: ${event.error.diagnosticsId}")
}
}
},
onDismiss = { },
)
}
}
}
The checkout shows vaulted methods automatically when they are available. No extra configuration is needed beyond providing a
customerId in the client session:Copy
Ask AI
PrimerCheckout(
clientToken: clientToken,
scope: { checkoutScope in
Task {
for await state in checkoutScope.paymentMethodSelection.state {
let hasSaved = !state.paymentMethods.isEmpty
print(hasSaved ? "Has saved methods" : "No saved methods")
}
}
}
)
See also
Vault manager component
SDK reference for saved payment methods
Events guide
Handle payment lifecycle events