> ## 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.

# PrimerVaultedPaymentMethods

> Saved (vaulted) payment method shortcut composable with header, row and submit slots

> [!WARNING]
>
> Primer Checkout iOS SDK is currently in **beta** (v3.0.0-beta.8).
> The API is subject to change before the stable release.

`PrimerVaultedPaymentMethods` is the returning-customer shortcut: it renders the **selected** saved payment method — the first saved one until the customer picks another — with a header and a submit button that pays with it. It is composed from three slots: header, item row, and submit button.

The header's "Show all" action opens the SDK's saved-payment-methods screen, which lists every saved method and is the only place a customer can delete one.

> [!NOTE]
>
> This is a one-row shortcut, not a full list. If you want every saved method visible inline, build that yourself — iterate [`PrimerSelectionSession.vaultedPaymentMethods`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session), keep the highlight in your own view state, and call `selectVaulted(_:)` from your pay button and `delete(_:)` to remove one.

The view resolves its [`PrimerSelectionSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session) from the environment. Paying and deletion are driven through that session — `PrimerVaultedPaymentMethods` only composes the UI. CVV recapture is a screen the SDK raises for itself.

> [!NOTE]
>
> This view reads its session from the environment injected by the `.primerCheckoutSession(_:theme:onCompletion:)` modifier. Place it inside a view hierarchy that applies that modifier. When no session is present it renders an empty placeholder, and when the customer has **no** saved methods it renders nothing at all — so you do not need to hide it yourself.

## PrimerVaultedPaymentMethods

```swift theme={"dark"}
@available(iOS 15.0, *)
public struct PrimerVaultedPaymentMethods: View {

  public typealias VaultedMethod = PrimerHeadlessUniversalCheckout.VaultedPaymentMethod

  public init(
    header: @escaping (PrimerSelectionSession) -> AnyView
      = { AnyView(VaultedPaymentMethodsDefaults.header($0)) },
    item: @escaping (VaultedMethod, _ isSelected: Bool, _ onSelect: @escaping () -> Void) -> AnyView
      = { AnyView(VaultedPaymentMethodsDefaults.item($0, isSelected: $1, onSelect: $2)) },
    submitButton: @escaping (_ isLoading: Bool, _ isEnabled: Bool, _ onSubmit: @escaping () -> Void) -> AnyView
      = { AnyView(VaultedPaymentMethodsDefaults.submitButton(isLoading: $0, isEnabled: $1, onSubmit: $2)) }
  )
}
```

| Parameter | Type | Default | Description |
| - | - | - | - |
| `header` | `(PrimerSelectionSession) -> AnyView` | `VaultedPaymentMethodsDefaults.header($0)` | Header shown above the row. The default renders the section title plus a "Show all" button wired to `session.showAll()`. Receives the session so custom headers can read state or trigger session actions — **if you replace this slot, call `showAll()` yourself or the customer loses the route to deleting a saved method.** |
| `item` | `(VaultedMethod, _ isSelected: Bool, _ onSelect: @escaping () -> Void) -> AnyView` | `VaultedPaymentMethodsDefaults.item(...)` | The row for the selected method. Called once, with `isSelected: true`. |
| `submitButton` | `(_ isLoading: Bool, _ isEnabled: Bool, _ onSubmit: @escaping () -> Void) -> AnyView` | `VaultedPaymentMethodsDefaults.submitButton(...)` | Submit button below the row. Receives loading state, enabled state (`false` while a saved-method payment is in flight), and a callback that pays with the selected method. |

> [!NOTE]
>
> Slots are type-erased to `AnyView` rather than using opaque return types. The three-argument `item` and `submitButton` builders hit Swift's generic-default inference limits, so this view trades the opaque-return ergonomics of [`PrimerCardForm`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form) for guaranteed composition. Wrap any custom slot content in `AnyView`.

### VaultedMethod typealias

```swift theme={"dark"}
public typealias VaultedMethod = PrimerHeadlessUniversalCheckout.VaultedPaymentMethod
```

`VaultedMethod` is a convenience alias for `PrimerHeadlessUniversalCheckout.VaultedPaymentMethod`, the model passed to the `item` slot.

```swift theme={"dark"}
public final class VaultedPaymentMethod: Codable {
  public let id: String
  public let paymentMethodType: String
  public let paymentInstrumentType: PaymentInstrumentType
  public let paymentInstrumentData: Response.Body.Tokenization.PaymentInstrumentData
  public let analyticsId: String
}
```

| Property | Type | Description |
| - | - | - |
| `id` | `String` | Unique identifier for this vaulted method. Use it to drive selection and deletion. |
| `paymentMethodType` | `String` | Payment method type (e.g. `"PAYMENT_CARD"`, `"PAYPAL"`). |
| `paymentInstrumentType` | `PaymentInstrumentType` | Type of payment instrument (e.g. card, bank account). |
| `paymentInstrumentData` | `Response.Body.Tokenization.PaymentInstrumentData` | Detailed instrument data: card network, masked digits, expiry, external payer info, BIN data. |
| `analyticsId` | `String` | Identifier used for analytics tracking. |

## Usage

The default slots produce a complete, themed saved-method shortcut — no slot arguments are required:

```swift theme={"dark"}
import SwiftUI
import PrimerSDK

@available(iOS 15.0, *)
struct SavedMethodsView: View {
  var body: some View {
    PrimerVaultedPaymentMethods()
  }
}
```

Wrap the view in a hierarchy that injects a checkout session:

```swift theme={"dark"}
import SwiftUI
import PrimerSDK

@available(iOS 15.0, *)
struct CheckoutScreen: View {
  @StateObject private var session: PrimerCheckoutSession

  init(clientToken: String) {
    _session = StateObject(wrappedValue: PrimerCheckoutSession(clientToken: clientToken))
  }

  var body: some View {
    ScrollView {
      PrimerVaultedPaymentMethods()
        .padding()
    }
    .primerCheckoutSession(session) { state in
      // Handle the checkout state (.success, .failure, .dismissed)
    }
  }
}
```

## Customizing slots

Provide any combination of slots; omitted slots fall back to their defaults. Custom content must be wrapped in `AnyView`.

```swift theme={"dark"}
import SwiftUI
import PrimerSDK

@available(iOS 15.0, *)
struct CustomVaultedMethods: View {
  var body: some View {
    PrimerVaultedPaymentMethods(
      header: { session in
        AnyView(
          HStack {
            Text("Pay faster with a saved card")
              .font(.headline)
            Spacer()
            // Keep a route to the SDK's saved-methods screen — it is where
            // the customer switches methods or deletes one.
            Button("Manage") { session.showAll() }
          }
        )
      },
      item: { method, isSelected, onSelect in
        AnyView(
          Button(action: onSelect) {
            HStack {
              Text(method.paymentMethodType)
              Spacer()
              if isSelected {
                Image(systemName: "checkmark.circle.fill")
              }
            }
            .padding()
          }
          .buttonStyle(.plain)
        )
      }
    )
  }
}
```

> [!TIP]
>
> The `item` slot renders the currently selected method, so it always receives `isSelected: true`. Use it to keep the same selection affordances (checkmarks, borders, highlights) as your other rows. Calling `onSelect` keeps this same method as the selected one. It does not switch to another method and does not pay. To let the customer change which saved method is used, keep a route to `session.showAll()`, which opens the SDK's saved-methods screen. Or build your own list from `session.vaultedPaymentMethods` and keep the highlighted method in your own view state. Then call `session.selectVaulted(_:)` with that method from your pay button. `selectVaulted(_:)` pays immediately, so don't call it from a row tap.

## Driving payment and deletion

`PrimerVaultedPaymentMethods` delegates all behavior to [`PrimerSelectionSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session). Read `vaultedPaymentMethods` and call the session methods directly from custom slots.

| Member | Signature | Description |
| - | - | - |
| `vaultedPaymentMethods` | `@Published public private(set) var vaultedPaymentMethods: [PrimerHeadlessUniversalCheckout.VaultedPaymentMethod]` | Saved methods for the current customer. Published, so a list you build yourself re-renders when the set changes. Empty while loading or when none exist. |
| `selectVaulted(_:)` | `func selectVaulted(_ method: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod)` | Pays with a saved method. It returns at once and the payment keeps running. As with every other payment method, your [`onBeforePaymentCreate`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-session) handler runs before the payment is created and can abort it. For a card that needs CVV recapture, that happens once the customer submits the CVV screen. The outcome arrives through the `onCompletion` of `.primerCheckoutSession(_:theme:onCompletion:)`. A repeat call while a payment is in flight is ignored. Backs the `submitButton` slot's `onSubmit`, not the `item` slot's `onSelect`. |
| `delete(_:)` | `func delete(_ method: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod) async throws` | Deletes a saved method immediately, with no confirmation (add your own if you want one). Once the delete succeeds, the method is removed from `vaultedPaymentMethods` and from the selection right away, even if the follow-up refresh of saved methods fails. |
| `showAll()` | `func showAll()` | Presents the SDK's saved-payment-methods screen, where the customer can pick a different method or delete one. Backs the default header's "Show all". |

The selection state read by the slots lives on `session.state` (`PrimerPaymentMethodSelectionState`):

| Property | Type | Description |
| - | - | - |
| `selectedVaultedPaymentMethod` | `PrimerHeadlessUniversalCheckout.VaultedPaymentMethod?` | The currently selected vaulted method, or `nil`. Compare its `id` to highlight the selected row. |
| `isVaultPaymentLoading` | `Bool` | `true` while a vaulted payment is in flight. Backs the submit slot's `isLoading`. |

> [!NOTE]
>
> CVV recapture is handled entirely by the SDK. When the saved card requires a CVV, `selectVaulted(_:)` raises the CVV recapture screen, and that screen finishes the payment. Nothing is inserted into your layout, so a merchant-owned pay button needs no field and no disabled state for it.

## VaultedPaymentMethodsDefaults

The default slot content. Because `PrimerVaultedPaymentMethods` erases its slots to `AnyView`, these helpers return `some View`. Compose them inside custom slots to keep the default look while overriding only part of the layout.

```swift theme={"dark"}
@available(iOS 15.0, *)
public enum VaultedPaymentMethodsDefaults {

  public static func header(_ session: PrimerSelectionSession) -> some View

  public static func item(
    _ method: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod,
    isSelected: Bool,
    onSelect: @escaping () -> Void
  ) -> some View

  public static func submitButton(
    isLoading: Bool,
    isEnabled: Bool,
    onSubmit: @escaping () -> Void
  ) -> some View

  public static func unavailable() -> some View
}
```

| Method | Description |
| - | - |
| `header(_:)` | Default section header: the title "Saved payment methods" plus a "Show all" button that calls `session.showAll()`. |
| `item(_:isSelected:onSelect:)` | Default saved-method row: brand icon and the masked value (or the brand name when none is available), inside a rounded border. When selected, the row shows a checkmark and a thicker border, both in `primerColorBorderOutlinedSelected`. Otherwise the border uses `primerColorBorderOutlinedDefault` at the default width. VoiceOver announces the selected row as selected. |
| `submitButton(isLoading:isEnabled:onSubmit:)` | Default "Pay" button, styled like the card form's pay button (brand fill, medium radius, 44pt minimum height). While `isLoading` it keeps its colors, shows a spinner and ignores taps. When disabled it uses the theme's disabled colors. |
| `unavailable()` | Empty placeholder rendered when no [`PrimerSelectionSession`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session) is available in the environment. |

Reuse a default while customizing a sibling slot:

```swift theme={"dark"}
PrimerVaultedPaymentMethods(
  submitButton: { isLoading, isEnabled, onSubmit in
    AnyView(VaultedPaymentMethodsDefaults.submitButton(
      isLoading: isLoading,
      isEnabled: isEnabled,
      onSubmit: onSubmit
    ))
  }
)
```

## See also

> [card] [PrimerSelectionSession](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-session.md)
>
> Paying and deletion actions for payment methods and vaulted methods.

> [card] [PrimerPaymentMethods](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/primer-payment-methods.md)
>
> Slot-based list of available (non-vaulted) payment methods.

> [card] [PaymentMethodsDefaults](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/payment-methods-defaults.md)
>
> Default slot content for the payment methods list.

> [card] [PrimerCardForm](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form.md)
>
> Slot-based card entry form for new cards.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.