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

# Troubleshooting — iOS

> Solutions for common Primer Checkout iOS issues

## Quick diagnosis

| Symptom                               | Likely Cause                                                      | Solution                                                                                       |
| ------------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Error screen with retry button        | Invalid or expired client token                                   | Generate a fresh `clientToken` from your server for each session                               |
| No payment methods shown              | Dashboard misconfiguration or unsupported currency/country        | Verify Dashboard settings and `currencyCode`/`countryCode` in client session                   |
| `getPaymentMethodScope()` returns nil | Checkout not yet in `.ready` state                                | Observe `state` stream and access scopes only after `.ready`                                   |
| State observation stops unexpectedly  | SwiftUI `.task` cancelled on view disappear                       | Keep `PrimerCheckout` in a stable view (e.g., `.sheet`) that isn't recreated                   |
| Theme or settings not applied         | `PrimerCheckoutTheme`/`PrimerSettings` created inside view `body` | Define as constants outside the view body                                                      |
| Delegate callbacks never fire (UIKit) | `PrimerCheckoutPresenterDelegate` not set or deallocated          | Set `delegate` on `.shared` before calling `presentCheckout` and retain the delegate           |
| Apple Pay button not visible          | Simulator, no cards in Wallet, or merchant ID mismatch            | Test on a real device with cards in Wallet; verify Apple Developer & Dashboard config          |
| Web redirect stays in `.polling`      | User cancelled in external browser or app not installed           | Handle `.failure` state; check `paymentMethodType` for app-specific requirements (e.g., Vipps) |
| BLIK/MBWay form fields not appearing  | `getPaymentMethodScope` called before `.ready` state              | Observe `checkoutScope.state` and access scopes only after `.ready`                            |
| QR code image not displaying          | `qrCodeImageData` is nil before `.displaying` status              | Check `state.status == .displaying` before using `qrCodeImageData`                             |
| Build error on iOS 14                 | Minimum deployment target not met                                 | Set deployment target to iOS 15.0+                                                             |

## Configuration and initialization

### Error screen after initialization

**Cause:** Invalid, expired, or malformed client token. The SDK shows an error screen with a retry button — not a blank screen.

**Solution:** Generate a fresh `clientToken` from your server for each checkout session. Client tokens are single-use and expire.

```swift theme={"dark"}
PrimerCheckout(
  clientToken: freshClientToken, // Always fetch from your server
  onCompletion: { state in
    if case .failure(let error) = state {
      print("[Primer] Error: \(error.errorId)")
      print("[Primer] Description: \(error.errorDescription ?? "N/A")")
      print("[Primer] Diagnostics: \(error.diagnosticsId)")
    }
  }
)
```

<Info>
  If you see `invalid-client-token` in the error, your token has expired or was already consumed. Generate a new one from your server.
</Info>

### Payment methods not showing

**Cause:** Multiple possible causes:

* No payment methods configured in the [Primer Dashboard](https://dashboard.primer.io) for the given currency/country
* The SDK only renders payment methods it supports (card, Apple Pay, PayPal, Klarna, ACH, etc.) — unsupported types are silently filtered out
* Client session missing required `currencyCode` or `countryCode` fields

**Solution:** Check the Xcode console for log messages like `"Filtering out unregistered payment method: TYPE"`. Verify your Dashboard configuration matches the currency and country in your client session.

## SwiftUI integration

### Payment method scope returns nil

**Cause:** `getPaymentMethodScope()` returns `nil` before the checkout reaches `.ready` state because scopes aren't configured yet.

<Warning>
  Calling `getPaymentMethodScope()` before `.ready` always returns `nil`. This is the most common cause of "scope is nil" issues.
</Warning>

**Solution:** Observe the state stream and access scopes only after `.ready`:

```swift theme={"dark"}
PrimerCheckout(
  clientToken: clientToken,
  scope: { checkoutScope in
    Task {
      for await state in checkoutScope.state {
        if case .ready = state {
          // Safe to access scopes now
          if let cardScope = checkoutScope.getPaymentMethodScope(DefaultCardFormScope.self) {
            // Configure card form
          }
        }
      }
    }
  }
)
```

### State observation stops unexpectedly

**Cause:** SwiftUI's `.task` modifier cancels the `Task` when the view disappears. If `PrimerCheckout` is inside a `NavigationLink` destination, each push/pop recreates it and cancels the scope Task.

**Solution:** Keep `PrimerCheckout` in a stable container that isn't destroyed by navigation:

```swift theme={"dark"}
// WRONG: Recreated on every navigation push
NavigationStack {
  NavigationLink("Checkout") {
    PrimerCheckout(clientToken: clientToken)
  }
}

// CORRECT: Stable container using .sheet
struct CheckoutContainer: View {
  let clientToken: String
  @State private var showCheckout = false

  var body: some View {
    Button("Checkout") { showCheckout = true }
      .sheet(isPresented: $showCheckout) {
        PrimerCheckout(clientToken: clientToken)
      }
  }
}
```

### Theme or settings not applied

**Cause:** Creating `PrimerCheckoutTheme()` or `PrimerSettings()` inside the SwiftUI `body` property. SwiftUI re-evaluates `body` frequently, creating new objects each time.

**Solution:** Define configuration as constants outside the view body:

```swift theme={"dark"}
private let primerTheme = PrimerCheckoutTheme(
  colors: ColorOverrides(primerColorBrand: .blue)
)
private let primerSettings = PrimerSettings(paymentHandling: .auto)

struct CheckoutView: View {
  let clientToken: String

  var body: some View {
    PrimerCheckout(
      clientToken: clientToken,
      primerSettings: primerSettings,
      primerTheme: primerTheme
    )
  }
}
```

See [Best Practices](/checkout/primer-checkout/configuration/best-practices) for more SwiftUI performance tips.

## UIKit integration

### Delegate callbacks never fire

**Cause:** `PrimerCheckoutPresenterDelegate` not set on `PrimerCheckoutPresenter.shared` before calling `presentCheckout`, or the delegate object was deallocated.

<Warning>
  The delegate is a `weak` reference. Ensure the delegate object (typically your view controller) is retained for the lifetime of the checkout presentation. If the delegate is deallocated, callbacks silently stop.
</Warning>

**Solution:** Set the delegate before presenting and implement all required methods:

```swift theme={"dark"}
class CheckoutViewController: UIViewController, PrimerCheckoutPresenterDelegate {

  func showCheckout() {
    // Set delegate BEFORE presenting
    PrimerCheckoutPresenter.shared.delegate = self

    PrimerCheckoutPresenter.presentCheckout(
      clientToken: clientToken,
      from: self
    )
  }

  // Required delegate methods
  func primerCheckoutPresenterDidCompleteWithSuccess(_ result: PaymentResult) {
    // Handle successful payment
  }

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

  func primerCheckoutPresenterDidDismiss() {
    // Handle checkout dismissed
  }
}
```

### Checkout not presented

**Cause:** Calling `presentCheckout` while already presenting — the call is silently ignored with no visible feedback.

**Solution:** Check `PrimerCheckoutPresenter.isPresenting` before calling:

```swift theme={"dark"}
guard !PrimerCheckoutPresenter.isPresenting else {
  print("Checkout already visible")
  return
}
PrimerCheckoutPresenter.presentCheckout(clientToken: clientToken, from: self)
```

## Card form

### Form shows invalid despite all visible fields being filled

**Cause:** The form validation considers all configured fields, including billing address fields that may not be visible on screen. Backend configuration controls which billing fields are required.

**Solution:** Observe `fieldErrors` to identify which specific fields are failing validation:

```swift theme={"dark"}
if let cardScope = checkoutScope.getPaymentMethodScope(DefaultCardFormScope.self) {
  Task {
    for await state in cardScope.state {
      for error in state.fieldErrors {
        print("[Primer] Field error - \(error.fieldType): \(error.message)")
      }
    }
  }
}
```

## Apple Pay

### Apple Pay not available

**Cause:** Multiple possible causes:

* Running on the iOS Simulator (Apple Pay requires a real device)
* No payment cards added to the user's Wallet
* Apple Pay not configured in the Primer Dashboard
* Merchant identifier mismatch between Apple Developer portal and Primer Dashboard

**Solution:** Test on a physical device with a card added to Wallet. Verify Apple Pay configuration in both the Apple Developer portal and Primer Dashboard. Check if `DefaultApplePayScope` is returned from `getPaymentMethodScope()` — if `nil`, Apple Pay is not available for the current session.

## Installation

### SPM package not found

**Cause:** Build fails with "No such module 'PrimerSDK'".

**Solution:** Re-add the SPM package:

1. In Xcode, go to **File > Add Package Dependencies**
2. Enter the Primer SDK repository URL
3. Select the correct version and add to your target

### CocoaPods installation fails

**Cause:** `pod install` fails or doesn't include the Primer SDK.

**Solution:** Verify your Podfile and reinstall:

```ruby theme={"dark"}
# Podfile
pod 'PrimerSDK'
```

```bash theme={"dark"}
pod install --repo-update
```

### Minimum deployment target

**Cause:** Crash or build error on iOS 14 or earlier.

**Solution:** The Primer Checkout iOS SDK requires iOS 15.0 or later. Update your deployment target:

```
// In Xcode: General > Deployment Info > iOS 15.0
```

## Validation vs payment errors

Understanding the difference helps with proper error handling:

| Error Type            | When It Occurs                                        | How It's Handled                                                    |
| --------------------- | ----------------------------------------------------- | ------------------------------------------------------------------- |
| **Validation errors** | During input (invalid format, missing fields)         | Handled automatically by input components; prevents form submission |
| **Payment failures**  | After form submission (declined card, network issues) | Requires explicit handling with error container or custom code      |

<Warning>
  Don't confuse these two error types. Validation errors prevent form submission and are shown inline. Payment failures occur after the form is submitted and require explicit handling.
</Warning>

## Debugging tips

### Log all state changes

```swift theme={"dark"}
PrimerCheckout(
  clientToken: clientToken,
  scope: { checkoutScope in
    Task {
      for await state in checkoutScope.state {
        print("[Primer] Checkout state: \(state)")
      }
    }
  },
  onCompletion: { state in
    print("[Primer] Terminal state: \(state)")
  }
)
```

### Inspect available payment methods

```swift theme={"dark"}
Task {
  for await state in checkoutScope.paymentMethodSelection.state {
    for method in state.paymentMethods {
      print("[Primer] Available: \(method.type) (\(method.name))")
    }
  }
}
```

### Check scope availability

After the checkout reaches `.ready` state, check which payment method scopes are available:

```swift theme={"dark"}
let cardScope = checkoutScope.getPaymentMethodScope(DefaultCardFormScope.self)
let applePayScope = checkoutScope.getPaymentMethodScope(DefaultApplePayScope.self)
let paypalScope = checkoutScope.getPaymentMethodScope(DefaultPayPalScope.self)

print("[Primer] Card form available: \(cardScope != nil)")
print("[Primer] Apple Pay available: \(applePayScope != nil)")
print("[Primer] PayPal available: \(paypalScope != nil)")
```

### Log card form validation errors

```swift theme={"dark"}
if let cardScope = checkoutScope.getPaymentMethodScope(DefaultCardFormScope.self) {
  Task {
    for await state in cardScope.state {
      for error in state.fieldErrors {
        print("[Primer] Field error - \(error.fieldType): \(error.message)")
      }
    }
  }
}
```

### Extract diagnostics for support

```swift theme={"dark"}
if case .failure(let error) = state {
  print("[Primer] Error ID: \(error.errorId)")
  print("[Primer] Description: \(error.errorDescription ?? "N/A")")
  print("[Primer] Diagnostics ID: \(error.diagnosticsId)")
  print("[Primer] Recovery: \(error.recoverySuggestion ?? "N/A")")
}
```

## Getting help

When contacting Primer support, include:

1. The `diagnosticsId` from any error callbacks
2. Your iOS version, Xcode version, and SDK version
3. Steps to reproduce the issue

```swift theme={"dark"}
if case .failure(let error) = state {
  print("Include in support request:")
  print("  Error ID: \(error.errorId)")
  print("  Description: \(error.errorDescription ?? "N/A")")
  print("  Diagnostics ID: \(error.diagnosticsId)")
  print("  Recovery: \(error.recoverySuggestion ?? "N/A")")
}
```

## See also

<CardGroup cols={2}>
  <Card title="Events guide" icon="bolt" href="/checkout/primer-checkout/configuration/events">
    Event handling patterns
  </Card>

  <Card title="Build a custom card form" icon="credit-card" href="/checkout/primer-checkout/guides-and-recipes/build-custom-card-form">
    Card form tutorial
  </Card>

  <Card title="Best practices" icon="star" href="/checkout/primer-checkout/configuration/best-practices">
    SwiftUI performance tips
  </Card>
</CardGroup>
