> ## Documentation Index
> Fetch the complete documentation index at: https://primer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Display saved payment methods

> Show saved payment methods count and list saved cards.

Display the number of saved payment methods or list saved cards for returning customers.

## Recipe: Show saved methods count

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    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` : '';
    });
    ```
  </Tab>

  <Tab title="Android">
    Use `PrimerVaultedPaymentMethods` to render saved methods:

    ```kotlin theme={"dark"}
    val checkout = rememberPrimerCheckoutController(clientToken)
    val vaultedController = rememberVaultedPaymentMethodsController(checkout)

    PrimerVaultedPaymentMethods(controller = vaultedController)
    ```

    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.
  </Tab>

  <Tab title="iOS">
    ```swift theme={"dark"}
    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")
          }
        }
      }
    )
    ```
  </Tab>
</Tabs>

## Recipe: List saved cards

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    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('');
    });
    ```
  </Tab>

  <Tab title="Android">
    For full control over how saved methods are displayed, observe the controller's state directly:

    ```kotlin theme={"dark"}
    @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,
                            )
                        }
                    }
                }
            }
        }
    }
    ```
  </Tab>

  <Tab title="iOS">
    Replace the payment method item to show vaulted methods with custom UI:

    ```swift theme={"dark"}
    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)
      )
    }
    ```
  </Tab>
</Tabs>

## How it works

<Tabs>
  <Tab title="Web">
    1. Listen for the `primer:vault-methods-update` DOM event
    2. Access `vaultedPayments` from the event detail
    3. Use `.size()` to get the count or `.toArray()` to iterate over methods
    4. Each method contains `paymentMethodType` and `paymentInstrumentData` with card details
  </Tab>

  <Tab title="Android">
    1. Create a vaulted payment methods controller using `rememberVaultedPaymentMethodsController()`
    2. Use `PrimerVaultedPaymentMethods` for the default rendering, or observe the controller's state for a custom UI
    3. To save new payment methods, call `cardFormController.setVaultOnSuccess(true)` before payment

    <Note>
      Vaulting requires a `customerId` in your client session. Pass it when creating the session on your server.
    </Note>
  </Tab>

  <Tab title="iOS">
    1. Access `checkoutScope.paymentMethodSelection` in the `scope` closure
    2. Observe `state` via `for await` to get `PrimerPaymentMethodSelectionState`
    3. Read `state.paymentMethods` for the list of saved methods
    4. Use `payWithVaultedPaymentMethod()` or `payWithVaultedPaymentMethodAndCvv(_:)` to pay

    <Note>
      Vaulting requires a `customerId` in your client session. Pass it when creating the session on your server.
    </Note>
  </Tab>
</Tabs>

## Variations

### Save and display methods

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    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('');
    });
    ```
  </Tab>

  <Tab title="Android">
    To save a new payment method after a successful payment, call `setVaultOnSuccess()` on the card form controller:

    ```kotlin theme={"dark"}
    val cardFormController = rememberCardFormController(checkout)

    LaunchedEffect(cardFormController) {
        cardFormController.setVaultOnSuccess(true)
    }
    ```
  </Tab>

  <Tab title="iOS">
    Pay with a saved method using `payWithVaultedPaymentMethod()`:

    ```swift theme={"dark"}
    // Pay with vaulted method (no CVV required)
    await checkoutScope.paymentMethodSelection.payWithVaultedPaymentMethod()

    // Pay with CVV verification
    await checkoutScope.paymentMethodSelection.payWithVaultedPaymentMethodAndCvv("123")
    ```
  </Tab>
</Tabs>

### Inline saved methods with card form

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    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}`,
          );
        });
    });
    ```
  </Tab>

  <Tab title="Android">
    This example shows saved methods above the card form, letting the customer choose between a saved method or entering new card details:

    ```kotlin theme={"dark"}
    @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)
                    }
                }
            }
        }
    }
    ```
  </Tab>

  <Tab title="iOS">
    Show saved methods alongside the card form, letting the customer choose between a saved method or entering new card details:

    ```swift theme={"dark"}
    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()
      }
    )
    ```
  </Tab>
</Tabs>

### Conditional UI based on saved methods

<Tabs>
  <Tab title="Web">
    ```javascript theme={"dark"}
    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';
    });
    ```
  </Tab>

  <Tab title="Android">
    The bottom sheet shows vaulted methods automatically when they are available. No extra configuration is needed beyond providing a `customerId` in the client session:

    ```kotlin theme={"dark"}
    @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 = { },
                )
            }
        }
    }
    ```
  </Tab>

  <Tab title="iOS">
    The checkout shows vaulted methods automatically when they are available. No extra configuration is needed beyond providing a `customerId` in the client session:

    ```swift theme={"dark"}
    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")
          }
        }
      }
    )
    ```
  </Tab>
</Tabs>

## See also

<CardGroup cols={2}>
  <Card title="Vault manager component" icon="bookmark" href="/sdk/primer-checkout-web/components/primer-vault-manager">
    SDK reference for saved payment methods
  </Card>

  <Card title="Events guide" icon="bolt" href="/checkout/primer-checkout/configuration/events">
    Handle payment lifecycle events
  </Card>
</CardGroup>
