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

# Handle the payment result

> Receive and handle checkout outcomes in SwiftUI and UIKit

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

Every checkout ends in one of three outcomes: the payment **succeeds**, it **fails**, or the user **dismisses** the flow. This guide shows how to receive and react to that outcome in both SwiftUI and UIKit.

The outcome is always delivered as a [`PrimerCheckoutState`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-state) value:

| Case         | Associated value                                                           | Meaning                                                    |
| ------------ | -------------------------------------------------------------------------- | ---------------------------------------------------------- |
| `.success`   | [`PaymentResult`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-state) | The payment completed.                                     |
| `.failure`   | [`PrimerError`](/docs/sdk/ios-checkout/v3.0.0-beta/api/common/primer-error)     | The payment or checkout failed.                            |
| `.dismissed` | —                                                                          | The user closed the checkout without completing a payment. |

<Note>
  The same `PrimerCheckoutState` also has lifecycle cases (`.initializing`, `.ready`) that you do not need to handle when reacting to the final outcome. Always include a `default` case when switching, so future enum additions stay source-compatible.
</Note>

## SwiftUI

### Using PrimerCheckout

The prebuilt [`PrimerCheckout`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout) view accepts an `onCompletion` closure that fires once with the final state.

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

struct CheckoutView: View {
  let clientToken: String
  @State private var paymentResult: PaymentResult?

  var body: some View {
    if let paymentResult {
      ConfirmationView(result: paymentResult)
    } else {
      PrimerCheckout(clientToken: clientToken) { state in
        switch state {
        case let .success(result):
          paymentResult = result
        case let .failure(error):
          print("Checkout failed: \(error.errorId) — \(error.diagnosticsId)")
        case .dismissed:
          break
        default:
          break
        }
      }
    }
  }
}
```

### Using the .primerCheckoutSession modifier

When you embed the composable views (for example [`PrimerCardForm`](/docs/sdk/ios-checkout/v3.0.0-beta/api/card-form/primer-card-form)) inline in your own layout, hold a [`PrimerCheckoutSession`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-session) as a `@StateObject` and receive the outcome through the `onCompletion` parameter of the `.primerCheckoutSession(_:onCompletion:)` modifier.

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

struct InlineCheckoutView: View {
  @StateObject private var session: PrimerCheckoutSession
  @State private var paymentResult: PaymentResult?

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

  var body: some View {
    Group {
      if let paymentResult {
        ConfirmationView(result: paymentResult)
      } else {
        ScrollView {
          PrimerCardForm()
        }
      }
    }
    .primerCheckoutSession(session) { state in
      switch state {
      case let .success(result):
        paymentResult = result
      case let .failure(error):
        print("Checkout failed: \(error.errorId) — \(error.diagnosticsId)")
      case .dismissed:
        break
      default:
        break
      }
    }
  }
}
```

<Info>
  `onCompletion` is delivered exactly once. The session latches the first terminal state, so any later `.dismissed` produced by a view-lifecycle teardown is ignored.
</Info>

### Navigating on success

Use the `PaymentResult` to drive navigation. `PaymentResult` is `Equatable` but not `Hashable`, so it cannot be appended to a `NavigationPath` or used with `navigationDestination(for:)` directly. Instead, store the result in `@State` and present the confirmation screen with `navigationDestination(isPresented:)`.

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

struct CheckoutFlowView: View {
  let clientToken: String
  @State private var paymentResult: PaymentResult?

  private var showsConfirmation: Binding<Bool> {
    Binding(
      get: { paymentResult != nil },
      set: { if !$0 { paymentResult = nil } }
    )
  }

  var body: some View {
    NavigationStack {
      PrimerCheckout(clientToken: clientToken) { state in
        if case let .success(result) = state {
          paymentResult = result
        }
      }
      .navigationDestination(isPresented: showsConfirmation) {
        if let paymentResult {
          ConfirmationView(result: paymentResult)
        }
      }
    }
  }
}
```

## UIKit

In UIKit, present the checkout with [`PrimerCheckoutPresenter`](/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-presenter) and adopt `PrimerCheckoutPresenterDelegate` to receive the outcome. The presenter dismisses its own modal before calling the relevant delegate method.

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

final class CheckoutViewController: UIViewController {
  let clientToken: String

  init(clientToken: String) {
    self.clientToken = clientToken
    super.init(nibName: nil, bundle: nil)
  }

  required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }

  func startCheckout() {
    PrimerCheckoutPresenter.shared.delegate = self
    PrimerCheckoutPresenter.presentCheckout(clientToken: clientToken, from: self)
  }
}

extension CheckoutViewController: PrimerCheckoutPresenterDelegate {
  func primerCheckoutPresenterDidCompleteWithSuccess(_ result: PaymentResult) {
    let confirmation = ConfirmationViewController(result: result)
    navigationController?.pushViewController(confirmation, animated: true)
  }

  func primerCheckoutPresenterDidFailWithError(_ error: PrimerError) {
    print("Checkout failed: \(error.errorId) — \(error.diagnosticsId)")
  }

  func primerCheckoutPresenterDidDismiss() {
    // User closed the checkout without completing a payment.
  }
}
```

<Tip>
  Set the `delegate` before calling `presentCheckout`. If no delegate is set when checkout completes, the outcome is logged and otherwise dropped.
</Tip>

The delegate maps one-to-one onto the SwiftUI `PrimerCheckoutState` cases:

| Delegate method                                     | Equivalent state          |
| --------------------------------------------------- | ------------------------- |
| `primerCheckoutPresenterDidCompleteWithSuccess(_:)` | `.success(PaymentResult)` |
| `primerCheckoutPresenterDidFailWithError(_:)`       | `.failure(PrimerError)`   |
| `primerCheckoutPresenterDidDismiss()`               | `.dismissed`              |

## Reading the PaymentResult

On success you receive a `PaymentResult`. Read its properties to build your confirmation UI, send analytics, or reconcile against your backend.

| Property            | Type            | Description                                                   |
| ------------------- | --------------- | ------------------------------------------------------------- |
| `paymentId`         | `String`        | The Primer payment ID.                                        |
| `status`            | `PaymentStatus` | The payment status: `.pending`, `.success`, or `.failed`.     |
| `token`             | `String?`       | The payment method token, when available.                     |
| `redirectUrl`       | `String?`       | A redirect URL, when the flow requires one.                   |
| `errorMessage`      | `String?`       | A human-readable error message, when present.                 |
| `amount`            | `Int?`          | The amount in minor units (for example, cents for USD).       |
| `currencyCode`      | `String?`       | The ISO 4217 currency code (for example, `"USD"`).            |
| `paymentMethodType` | `String?`       | The payment method type used (for example, `"PAYMENT_CARD"`). |

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

struct ConfirmationView: View {
  let result: PaymentResult

  var body: some View {
    VStack(spacing: 12) {
      Text("Payment confirmed")
        .font(.title2.bold())
      Text("Payment ID: \(result.paymentId)")
      if let amount = result.amount, let currency = result.currencyCode {
        Text("Charged: \(amount) \(currency)")
      }
    }
  }
}
```

<Note>
  `status` is usually `.success` for a `.success(PaymentResult)` outcome. In [manual payment handling](/docs/checkout/client-session), the payment may still be `.pending` until your backend confirms it.
</Note>

## Handling the PrimerError

On failure you receive a [`PrimerError`](/docs/sdk/ios-checkout/v3.0.0-beta/api/common/primer-error). The SDK already surfaces failures inside the checkout UI, so you typically use the error for logging, analytics, and support workflows rather than for displaying a message yourself.

| Property             | Type      | Description                                                                  |
| -------------------- | --------- | ---------------------------------------------------------------------------- |
| `errorId`            | `String`  | A stable, machine-readable identifier for the error category.                |
| `diagnosticsId`      | `String`  | A unique ID for this specific occurrence — include it in support requests.   |
| `errorDescription`   | `String?` | A formatted description combining the error ID, message, and diagnostics ID. |
| `recoverySuggestion` | `String?` | A suggested recovery action, when one is available.                          |

```swift theme={"dark"}
func report(_ error: PrimerError) {
  analytics.track("checkout_failed", properties: [
    "error_id": error.errorId,
    "diagnostics_id": error.diagnosticsId
  ])

  if let suggestion = error.recoverySuggestion {
    print("Recovery: \(suggestion)")
  }
}
```

<Warning>
  Always log `diagnosticsId`. Primer support uses it to trace the exact failed request when you raise an issue.
</Warning>

## See also

<CardGroup cols={2}>
  <Card title="PrimerCheckoutState" icon="diagram-project" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-state">
    The full state enum and the PaymentResult model.
  </Card>

  <Card title="PrimerError" icon="triangle-exclamation" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/common/primer-error">
    Error categories, IDs, and diagnostics.
  </Card>

  <Card title="PrimerCheckoutPresenter" icon="swift" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout-presenter">
    The UIKit entry point and delegate callbacks.
  </Card>

  <Card title="PrimerCheckout" icon="cart-shopping" href="/docs/sdk/ios-checkout/v3.0.0-beta/api/primer-checkout">
    The prebuilt SwiftUI checkout view.
  </Card>
</CardGroup>
