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

# PrimerCheckoutSession

> Session owner for composable, inline checkout with the .primerCheckoutSession modifier

<Warning>
  Primer Checkout iOS SDK is currently in **beta** (v3.0.0-beta.1).
  The API is subject to change before the stable release.
</Warning>

`PrimerCheckoutSession` is the owner of a CheckoutComponents session. You hold it (typically as a `@StateObject`) and wire it into your SwiftUI view hierarchy with the [`.primerCheckoutSession(_:onCompletion:)`](#view-modifier) modifier. The session builds the SDK, runs client-token initialization, creates the checkout scope, and bridges per-method scopes into observable session objects that the composable views (such as [`PrimerCardForm`](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form) and [`PrimerPaymentMethods`](/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/primer-payment-methods)) consume from the environment.

The same session powers both the managed modal [`PrimerCheckout`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout) and fully inline embedding: any Primer composable view placed under the modifier resolves its session from the environment. Overlay screens for 3DS challenges and redirect flows are rendered automatically while the session is `.ready`.

<Info>
  This is the iOS analog of the Android `PrimerCheckoutHost`. Where Android wraps your layout in a composable host, iOS wires the session into the environment with a view modifier — child composable views read their per-feature session from `EnvironmentValues`.
</Info>

## Declaration

```swift theme={"dark"}
@available(iOS 15.0, *)
@MainActor
public final class PrimerCheckoutSession: ObservableObject
```

## Initializer

```swift theme={"dark"}
public init(
    clientToken: String,
    settings: PrimerSettings = PrimerSettings(),
    theme: PrimerCheckoutTheme = PrimerCheckoutTheme(),
    idempotencyKey: @escaping @Sendable () -> String? = { nil }
)
```

| Parameter        | Type                                                                            | Default                 | Description                                                                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clientToken`    | `String`                                                                        | Required                | The client token from your `POST /client-session` call. See [Client session](/docs/checkout/client-session).                                                                               |
| `settings`       | [`PrimerSettings`](/docs/sdk/ios-checkout/v3.0.0-beta/api/common/primer-settings)    | `PrimerSettings()`      | SDK configuration applied to this session.                                                                                                                                            |
| `theme`          | [`PrimerCheckoutTheme`](/docs/sdk/ios-checkout/v3.0.0-beta/api/theming/primer-theme) | `PrimerCheckoutTheme()` | Design tokens applied to all SDK components within the session.                                                                                                                       |
| `idempotencyKey` | `@Sendable () -> String?`                                                       | `{ nil }`               | Declarative idempotency-key provider, invoked once per payment attempt just before the SDK creates the payment. Return `nil` to opt out. Ignored when `onBeforePaymentCreate` is set. |

## Phase

The lifecycle phase of the session. Terminal outcomes (success, failure, dismissed) are **not** delivered here — they are delivered through the modifier's `onCompletion`.

```swift theme={"dark"}
public enum Phase: Equatable {
    case initializing
    case ready
}
```

| Case           | Description                                                                                                                  |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `initializing` | The session is building the SDK and running client-token initialization. Child sessions (`cardForm`, `selection`) are `nil`. |
| `ready`        | Initialization completed. Child sessions resolve and the composable views render.                                            |

## Published properties

| Property | Type    | Description                                                                                                           |
| -------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `phase`  | `Phase` | The current lifecycle phase. Declared `@Published public private(set)` — observe it, but only the session mutates it. |

## Properties

| Property                | Type                                                                                      | Description                                                                                                                                                                                                                                                                                           |
| ----------------------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onBeforePaymentCreate` | `BeforePaymentCreateHandler?`                                                             | Called before a payment is created. Use the decision handler to provide an idempotency key or abort payment creation. Mutations are forwarded to the checkout scope immediately, so assigning after the session reaches `.ready` still takes effect for the next payment attempt.                     |
| `idempotencyKey`        | `@Sendable () -> String?`                                                                 | Declarative idempotency-key provider, invoked once per payment attempt just before the SDK creates the payment. Return `nil` (default) to opt out. Ignored when `onBeforePaymentCreate` is set. Mutations are forwarded to the checkout scope immediately, so post-`.ready` assignment still applies. |
| `cardForm`              | [`PrimerCardFormSession?`](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/card-form-session) | The card-form sub-session, lazily created and cached. Non-`nil` only once `phase == .ready`.                                                                                                                                                                                                          |
| `selection`             | `PrimerSelectionSession?`                                                                 | The payment-method-selection sub-session, lazily created and cached. Non-`nil` only once `phase == .ready`.                                                                                                                                                                                           |

<Note>
  `onBeforePaymentCreate` uses the `BeforePaymentCreateHandler` type:

  ```swift theme={"dark"}
  public typealias BeforePaymentCreateHandler = @Sendable (
      _ data: PrimerCheckoutPaymentMethodData,
      _ decisionHandler: @escaping (PrimerPaymentCreationDecision) -> Void
  ) -> Void
  ```

  If you set `onBeforePaymentCreate`, the declarative `idempotencyKey` provider is ignored — use the decision handler to supply the key instead.
</Note>

## Methods

| Method            | Description                                                                                                                                                                                                                     |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `start() async`   | Builds the SDK and drives the session to `.ready`. Idempotent — repeated calls are ignored once initialization has started. The modifier calls this for you on appear.                                                          |
| `refresh() async` | Re-fetches configuration and payment methods from the backend and returns the session to `.ready`. No-op unless the session has finished initializing; concurrent calls are ignored. Failures are delivered via `onCompletion`. |
| `cancel()`        | Tears the session down: dismisses the checkout scope, clears resources, drops cached sub-sessions, and resets to a restartable state. Idempotent. The modifier wires this to `onDisappear`.                                     |

<Tip>
  You normally never call `start()` or `cancel()` yourself — the `.primerCheckoutSession` modifier handles the lifecycle. Call `refresh()` when you need to reload after a client-session update (for example, an amount or currency change).
</Tip>

## View modifier

The `View.primerCheckoutSession(_:onCompletion:)` modifier wires a `PrimerCheckoutSession` into the SwiftUI environment, bootstraps it on appear, and tears it down on disappear. Apply it once around any Primer composable views — whether presented modally via [`PrimerCheckout`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout) or embedded inline in your own layout.

```swift theme={"dark"}
@available(iOS 15.0, *)
public extension View {
    func primerCheckoutSession(
        _ session: PrimerCheckoutSession,
        onCompletion: ((PrimerCheckoutState) -> Void)? = nil
    ) -> some View
}
```

| Parameter      | Type                               | Default  | Description                                                                                                                       |
| -------------- | ---------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `session`      | `PrimerCheckoutSession`            | Required | The session injected into the environment for child composable views to resolve.                                                  |
| `onCompletion` | `((PrimerCheckoutState) -> Void)?` | `nil`    | Receives the terminal outcome exactly once. See [`PrimerCheckoutState`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-state). |

### What the modifier provides

1. **Environment injection** — exposes the `PrimerCheckoutSession`, the derived `PrimerCardFormSession`, and the `PrimerSelectionSession` through `EnvironmentValues`, so child views like `PrimerCardForm` and `PrimerPaymentMethods` resolve their per-feature session automatically.
2. **Lifecycle bootstrapping** — calls `start()` on appear and `cancel()` on disappear.
3. **Overlay management** — renders overlays for 3DS challenges and redirect flows automatically once the session is `.ready`.

<Warning>
  Without the `.primerCheckoutSession` modifier above them in the hierarchy, composable views such as `PrimerCardForm` and `PrimerPaymentMethods` cannot resolve a session and will not function.
</Warning>

## Usage

Hold the session as a `@StateObject`, place the composable views in a `ScrollView`, and attach the modifier with your completion handler.

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

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

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

    var body: some View {
        ScrollView {
            VStack(spacing: 24) {
                PrimerCardForm()
                PrimerPaymentMethods()
            }
            .padding()
        }
        .primerCheckoutSession(session) { state in
            switch state {
            case .success(let data):
                print("Payment complete: \(data)")
            case .failure(let error):
                print("Payment failed: \(error)")
            case .dismissed:
                print("Checkout dismissed")
            default:
                break
            }
        }
    }
}
```

The child composable views resolve their per-feature session from the environment — you do not pass a session into them. Behind the scenes the modifier injects `PrimerCardFormSession` and `PrimerSelectionSession`, which `PrimerCardForm` and `PrimerPaymentMethods` bind to once the session reaches `.ready`.

### Supplying an idempotency key

Provide the declarative `idempotencyKey` provider at construction, or assign `onBeforePaymentCreate` for full control over the payment-creation decision. Either may be assigned after `.ready` — the change applies to the next payment attempt.

```swift theme={"dark"}
let session = PrimerCheckoutSession(
    clientToken: token,
    idempotencyKey: { UUID().uuidString }
)
```

## See also

<CardGroup cols={2}>
  <Card title="PrimerCheckout" icon="window-maximize" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout">
    The managed modal entry point powered by the same session.
  </Card>

  <Card title="PrimerCardForm" icon="credit-card" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form">
    Composable card form that resolves its session from the environment.
  </Card>

  <Card title="PrimerPaymentMethods" icon="list" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/payment-methods/primer-payment-methods">
    Composable payment-method selection list for inline layouts.
  </Card>

  <Card title="PrimerCheckoutState" icon="circle-check" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-state">
    The terminal outcome delivered to onCompletion.
  </Card>
</CardGroup>
