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

# PrimerSelectionSession

> Observable payment-method selection session backing the list and vaulted views

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

`PrimerSelectionSession` is the observable session that drives payment-method selection. It publishes the selection `state` (available methods, current selection and saved-method state) and the customer's saved `vaultedPaymentMethods`, and provides the methods that act on them. The SDK raises the CVV recapture screen when a saved card needs one.

A single `PrimerSelectionSession` backs **both** [`PrimerPaymentMethods`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/primer-payment-methods) (the list of available methods) and [`PrimerVaultedPaymentMethods`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/vaulted-payment-methods) (saved methods). Both views resolve the same session from the environment supplied by the `.primerCheckoutSession(_:)` modifier, so you rarely construct or hold it yourself — you read it from the `header` and `emptyState` slots those views hand you.

> [!NOTE]
>
> `PrimerSelectionSession` is a `@MainActor`, `ObservableObject` final class available on iOS 15.0 and later (`@available(iOS 15.0, *)`). Observe it with `@ObservedObject` and call its methods from the main actor.

## Declaration

```swift theme={"dark"}
@available(iOS 15.0, *)
@MainActor
public final class PrimerSelectionSession: ObservableObject {
  @Published public private(set) var state: PrimerPaymentMethodSelectionState
  @Published public private(set) var vaultedPaymentMethods: [PrimerHeadlessUniversalCheckout.VaultedPaymentMethod]
}
```

The session is created and injected by the SDK, so you do not initialize it yourself. The `header` slot of `PrimerPaymentMethods` and of `PrimerVaultedPaymentMethods`, and the `emptyState` slot of `PrimerPaymentMethods`, receive it directly. The row and submit slots do not receive it. To use the session in those slots, read it with `@Environment(\.primerSelectionSession)`.

## Properties

| Property | Type | Description |
| - | - | - |
| `state` | `PrimerPaymentMethodSelectionState` | The latest selection state, bridged from the underlying selection scope. Read-only (`@Published public private(set)`) — drive UI from it and call the session's methods to change it. |
| `vaultedPaymentMethods` | `[PrimerHeadlessUniversalCheckout.VaultedPaymentMethod]` | Read-only (`@Published public private(set)`). The customer's saved (vaulted) payment methods. Published, so a list you build yourself re-renders when the set changes, after `delete(_:)` or when the SDK's own saved-methods screen deletes one. Empty when none are available. |

> [!NOTE]
>
> `state` is the snapshot you render from; `vaultedPaymentMethods` is the list of saved cards. Which card the SDK's own screens act on is reflected back through `state.selectedVaultedPaymentMethod`. Keep the highlight in your own view state.

## Methods

| Method | Description |
| - | - |
| `select(_ method: CheckoutPaymentMethod)` | Selects a payment method, starting its flow (card form, redirect, native wallet, etc.). |
| `cancel()` | Ends the checkout. `onCompletion` receives `.dismissed`. |
| `selectVaulted(_ method: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod)` | Pays with a saved payment method. Call it from your pay button, not from a row tap. |
| `delete(_ method:) async throws` | Deletes a saved payment method, then refreshes `vaultedPaymentMethods`. |
| `showAll()` | Presents the SDK's saved-payment-methods screen, where the customer can pick a different method or delete one. |

### select(\_:)

```swift theme={"dark"}
public func select(_ method: CheckoutPaymentMethod)
```

Selects a payment method to begin its payment flow. Call this from a method row's tap handler. The selected method is reflected in `state.selectedPaymentMethod`.

| Parameter | Type | Description |
| - | - | - |
| `method` | `CheckoutPaymentMethod` | The payment method the user chose. Typically the value passed into the `method` slot of `PrimerPaymentMethods`. |

### cancel()

```swift theme={"dark"}
public func cancel()
```

Ends the checkout, as if the customer dismissed it. The checkout state moves to `.dismissed`. The `onCompletion` of `.primerCheckoutSession(_:theme:onCompletion:)` or `PrimerCheckout` receives `.dismissed` once, and nothing is delivered after that. Do not use it as a back or deselect action.

### selectVaulted(\_:)

```swift theme={"dark"}
public func selectVaulted(_ method: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod)
```

Pays with a saved payment method. This is the pay verb, so call it from your pay button rather than from a row tap. A card that needs CVV recapture raises the CVV recapture screen first, and that screen finishes the payment. The outcome arrives through `.primerCheckoutSession(_:theme:onCompletion:)`.

Keep which row looks selected in your own view state. Returns immediately; the payment runs on. A call made while `state.isVaultPaymentLoading` is `true` does not start another payment.

When the payment starts, it first passes through your [`onBeforePaymentCreate`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-session) handler, as for every other payment method. If the handler aborts, the session reports a failure through `onCompletion`.

> [!WARNING]
>
> This method starts a payment. A row tap wired to it will charge the customer.

| Parameter | Type | Description |
| - | - | - |
| `method` | `PrimerHeadlessUniversalCheckout.VaultedPaymentMethod` | The saved method to pay with. |

### delete(\_:)

```swift theme={"dark"}
public func delete(_ method: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod) async throws
```

Deletes a saved payment method. Once the deletion succeeds, the method leaves `vaultedPaymentMethods` at once. If it was the selected one, `state.selectedVaultedPaymentMethod` moves to the first remaining method. The SDK then re-fetches the saved methods. If that re-fetch fails, the deleted method still stays out of the list. Throws if the deletion itself fails, leaving the list unchanged.

| Parameter | Type | Description |
| - | - | - |
| `method` | `PrimerHeadlessUniversalCheckout.VaultedPaymentMethod` | The saved method to remove. |

> [!WARNING]
>
> Deletion is immediate — there is no confirmation step. Add your own if you want one. (The SDK's own saved-methods screen, reached via `showAll()`, does confirm before it deletes.)

```swift theme={"dark"}
Button("Delete", role: .destructive) {
    Task {
        do { try await session.delete(method) }
        catch { showError(error) }
    }
}
```

### showAll()

```swift theme={"dark"}
public func showAll()
```

Presents the SDK's saved-payment-methods screen: the full list, where the customer can select a different method for payment or delete one. Works in both the managed `PrimerCheckout` modal and the inline integration.

> [!TIP]
>
> `PrimerVaultedPaymentMethods` already wires this into its default header, so you only need to call it directly if you have replaced the header slot or built your own saved-methods UI.

## PrimerPaymentMethodSelectionState

`PrimerPaymentMethodSelectionState` is the immutable snapshot published by `state`. It conforms to `Equatable`. Every stored property is `public internal(set)` — readable from your code but mutated only by the SDK, so treat it as read-only and call the session's methods to change selection. The public initializer exists for previews and tests.

```swift theme={"dark"}
@available(iOS 15.0, *)
public struct PrimerPaymentMethodSelectionState: Equatable {
  public internal(set) var paymentMethods: [CheckoutPaymentMethod] = []
  public internal(set) var isLoading: Bool = false
  public internal(set) var selectedPaymentMethod: CheckoutPaymentMethod?
  public internal(set) var searchQuery: String = ""
  public internal(set) var filteredPaymentMethods: [CheckoutPaymentMethod] = []
  public internal(set) var error: String?
  public internal(set) var selectedVaultedPaymentMethod: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod?
  public internal(set) var isVaultPaymentLoading: Bool = false
  public internal(set) var isPaymentMethodsExpanded: Bool = true

  public init(
    paymentMethods: [CheckoutPaymentMethod] = [],
    isLoading: Bool = false,
    selectedPaymentMethod: CheckoutPaymentMethod? = nil,
    searchQuery: String = "",
    filteredPaymentMethods: [CheckoutPaymentMethod] = [],
    error: String? = nil,
    selectedVaultedPaymentMethod: PrimerHeadlessUniversalCheckout.VaultedPaymentMethod? = nil,
    isVaultPaymentLoading: Bool = false,
    isPaymentMethodsExpanded: Bool = true
  )
}
```

### Properties

| Property | Type | Description |
| - | - | - |
| `paymentMethods` | `[CheckoutPaymentMethod]` | All available payment methods for the current session. |
| `isLoading` | `Bool` | Reserved. The SDK never sets it, so the state the session publishes keeps it `false`. Do not drive a loading indicator from it. For a loading placeholder, use the checkout session's `phase` (`.initializing`). The selection session exists only once `phase` is `.ready`, and `paymentMethods` fills right after. |
| `selectedPaymentMethod` | `CheckoutPaymentMethod?` | The currently selected available method, or `nil`. |
| `searchQuery` | `String` | Reserved. No public API sets a search query, so it is always empty. |
| `filteredPaymentMethods` | `[CheckoutPaymentMethod]` | The same list as `paymentMethods`, because no search can be applied. To filter, filter `paymentMethods` in your own view. |
| `error` | `String?` | An error message for the selection flow, or `nil`. |
| `selectedVaultedPaymentMethod` | `PrimerHeadlessUniversalCheckout.VaultedPaymentMethod?` | The saved method the SDK's own screens act on. Once saved methods load, the SDK selects the first one. It changes when the customer picks another on the saved-methods screen or you call `selectVaulted(_:)`. If the selected method is deleted, it moves to the first remaining method. `nil` only while saved methods are loading or when there are none. |
| `isVaultPaymentLoading` | `Bool` | Whether a payment with the selected vaulted method is in progress. |
| `isPaymentMethodsExpanded` | `Bool` | Whether the payment methods section is expanded (showing all methods). Defaults to `true`. Becomes `false` when the customer picks a saved method on the SDK's saved-methods screen (`showAll()`). Becomes `true` again when they tap the SDK's "Show other ways to pay" button. `selectVaulted(_:)` does not change it. |

> [!TIP]
>
> Drive your custom rows and empty states directly from these properties rather than caching copies.

## CheckoutPaymentMethod

`paymentMethods` and `filteredPaymentMethods` are arrays of `CheckoutPaymentMethod`, the model describing a single selectable method. It is documented in full on the [`PrimerPaymentMethods`](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/primer-payment-methods) page; the key fields are `id`, `type`, `name`, `icon`, and the surcharge and styling properties.

## Usage

### Reading state and selecting a method

`PrimerPaymentMethods` hands each `method` slot a `CheckoutPaymentMethod` and an `onSelect` closure that calls `select(_:)` for you. To select outside a slot — for example from a custom header — call `select(_:)` on the session directly.

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

struct CheckoutScreen: View {
  @StateObject private var session: PrimerCheckoutSession

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

  var body: some View {
    ScrollView {
      PrimerPaymentMethods(
        method: { paymentMethod, onSelect in
          Button(action: onSelect) {
            HStack {
              if let icon = paymentMethod.icon {
                Image(uiImage: icon)
                  .resizable()
                  .scaledToFit()
                  .frame(width: 28, height: 28)
              }
              Text(paymentMethod.buttonText ?? paymentMethod.name)
              Spacer()
              if let surcharge = paymentMethod.formattedSurcharge {
                Text(surcharge).foregroundColor(.secondary)
              }
            }
            .padding()
          }
          .buttonStyle(.plain)
        }
      )
    }
    .primerCheckoutSession(session) { state in
      handle(state)
    }
  }
}
```

### Working with vaulted methods and CVV recapture

When rendering saved methods, iterate `vaultedPaymentMethods`, keep the highlight in your own view state, and call `selectVaulted(_:)` from your pay button. A card that needs CVV recapture raises the CVV recapture screen on submit, and that screen finishes the payment.

```swift theme={"dark"}
struct SavedMethods: View {
  @ObservedObject var session: PrimerSelectionSession
  @State private var highlighted: String?

  var body: some View {
    VStack(alignment: .leading, spacing: 8) {
      ForEach(session.vaultedPaymentMethods, id: \.id) { method in
        HStack {
          Text(method.paymentMethodType)
          Spacer()
          Button("Select") { highlighted = method.id }
          Button("Delete", role: .destructive) {
            Task { try? await session.delete(method) }
          }
        }
      }

      Button("Pay") {
        guard let method = session.vaultedPaymentMethods
          .first(where: { $0.id == highlighted }) else { return }
        session.selectVaulted(method)
      }
      .disabled(session.state.isVaultPaymentLoading)
    }
  }
}
```

> [!WARNING]
>
> Never mutate `state` directly — every property is `internal(set)`. Change selection only through `select(_:)`, `selectVaulted(_:)`, `delete(_:)`, and `showAll()`.

## See also

> [card] [PrimerPaymentMethods](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/primer-payment-methods.md)
>
> The slot-based view that renders the available payment methods.

> [card] [PrimerVaultedPaymentMethods](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/vaulted-payment-methods.md)
>
> Render and select saved (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 bodies used by the payment-method views.

> [card] [PrimerCheckoutSession](https://primer.io/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-session.md)
>
> The top-level session injected via .primerCheckoutSession(\_:).


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