Skip to main content

Quick diagnosis

Configuration and initialization

Error screen after initialization

Cause: Invalid, expired, or malformed client token. PrimerCheckout and PrimerCheckoutPresenter show an error screen with a Retry button, not a blank screen. If you pass PrimerSettings(uiOptions: PrimerUIOptions(isErrorScreenEnabled: false)), PrimerCheckout shows nothing and only delivers .failure to onCompletion. PrimerCheckoutPresenter then closes the sheet and reports the failure to primerCheckoutPresenterDidFailWithError. In the composable flow (.primerCheckoutSession), the SDK never shows an error screen for a failed initialization, regardless of isErrorScreenEnabled. onCompletion receives one .failure, session.phase stays .initializing, and the composable views stay empty. Solution: Generate a fresh clientToken from your server for each checkout session. Client tokens are single-use and expire.
If you see invalid-client-token in the error, your token is malformed or has expired. Generate a new one from your server.

Payment methods not showing

Cause: If no payment method is left to show, the checkout does not show an empty list. The session never reaches .ready. It reports .failure through onCompletion, or through primerCheckoutPresenterDidFailWithError in UIKit. The errorId tells you why:
  • misconfigured-payment-methods: the configuration loaded for this client token lists no payment methods, because none are enabled in the Dashboard for this session
  • missing-configuration: the SDK supports none of the configured methods, or the session configuration could not be loaded
With PrimerCheckout and PrimerCheckoutPresenter, the shopper sees the error screen. It has no Retry button because no payment was attempted. Missing individual methods have multiple possible causes:
  • No payment methods configured in the Primer Dashboard for the given currency/country
  • The SDK only renders payment methods it supports — unsupported types are silently filtered out
  • Klarna appears only when PrimerKlarnaSDK is linked into your app. With CocoaPods, add pod 'PrimerKlarnaSDK' next to PrimerSDK in your Podfile. Without it, Klarna is filtered out of the list, and the debug log shows Filtering out unregistered payment method: KLARNA.
  • Client session missing required currencyCode or countryCode fields
Solution: The SDK logs nothing by default. In a Debug build, turn on SDK debug logging before you create the checkout: PrimerLogging.shared.logger = DefaultLogger(logLevel: .debug). Then check the Xcode console for messages like Filtering out unregistered payment method: TYPE. Verify your Dashboard configuration matches the currency and country in your client session. When using the composable PrimerPaymentMethods view, read the available methods from session.selection?.state.paymentMethods.

SwiftUI integration

Composable views render nothing

Cause: The composable views — PrimerCardForm, PrimerPaymentMethods, and PrimerVaultedPaymentMethods — resolve their session from the SwiftUI environment. Without the .primerCheckoutSession(_:theme:onCompletion:) modifier above them, they have no session to bind to. They also stay empty until the session reaches .ready. If initialization fails, for example because of an invalid client token or no supported payment methods, the session never reaches .ready. The modifier’s onCompletion receives a single .failure, session.phase stays .initializing, and the views stay empty with no error screen. To find the reason, match .failure(error, _) in onCompletion and read error.errorId. Error screen after initialization shows the pattern.
A composable view without .primerCheckoutSession above it in the hierarchy cannot resolve a session and will not function. This is the most common cause of “nothing renders” issues.
Solution: Hold a PrimerCheckoutSession as a @StateObject and attach the modifier once around your composable views. The modifier injects the session, the derived PrimerCardFormSession, and the PrimerSelectionSession into the environment, calls start() on appear, and tears the session down on disappear:
If you only need the prebuilt screens, use the managed PrimerCheckout view instead. It builds its own checkout, so it needs no PrimerCheckoutSession and no modifier.

Session re-initializes unexpectedly

Cause: A let or computed property builds a new PrimerCheckoutSession whenever SwiftUI re-evaluates the enclosing body. The modifier starts a session only when the view appears. The new session never starts, so the composable views stay empty. Nothing uses the original session anymore. Solution: Own the session as a @StateObject so SwiftUI keeps a single instance for the lifetime of the view:
The modifier also tears the session down whenever the views under it disappear. This includes transient cases such as switching tabs, pushing another screen, or presenting the parent sheet again. When the views reappear, the session initializes again from the client token. Attach .primerCheckoutSession to a view that stays on screen for the whole checkout. The .primerCheckoutSession modifier drives the lifecycle for you — you normally never call start() or cancel() yourself. Call session.refresh() only when you need to reload after a client-session update (for example, an amount or currency change).

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:
See 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.
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.
Solution: Set the delegate before presenting and implement all required methods:

Checkout not presented

Cause: Calling presentCheckout while a checkout is already on screen or still animating in. The SDK skips a second call only while the sheet is animating in. After that, a second call replaces the presenter’s reference to the checkout on screen. Solution: Check PrimerCheckoutPresenter.isPresenting before calling. It is true from the start of the presentation until the checkout is dismissed or completes:

Card form

Form shows invalid despite all visible fields being filled

Cause: state.isValid is true only when the card number, expiry date, CVV and cardholder name are all valid and non-empty. Billing address fields do not affect it. A field you left out of a custom cardDetails slot stays empty, so the form never becomes valid. Solution: Inspect state.fieldErrors on the PrimerCardFormSession to identify which specific fields are failing validation. The session is handed to each PrimerCardForm slot, and PrimerCardForm already observes it, so the slot re-renders on every state change:
fieldErrors lists only fields that the shopper filled in and that failed validation. A field that was never filled in or never shown has no entry. If fieldErrors is empty but isValid is false, check session.state.data for the four card fields. Use session.state.isValid to gate the submit button and session.state.errorMessage(for:) to display a per-field message.

Apple Pay

Apple Pay not available

Cause: Multiple possible causes:
  • Apple Pay is not enabled in the Primer Dashboard for this client session, so it is not in the payment method list
  • The device cannot make Apple Pay payments, or Wallet has no card for the supported networks. Apple Pay still appears in the list. Selecting it opens an “Apple Pay Unavailable” screen. The text under the title is the error description, for example [apple-pay-device-not-supported] Device does not support Apple Pay (diagnosticsId: ...) or [apple-pay-no-cards-in-wallet] Apple Pay has no cards in wallet (diagnosticsId: ...). When the shopper came from the payment method list, the screen has a Choose Another Payment Method button.
  • No merchant identifier: the payment fails with invalid-merchant-identifier because PrimerSettings has no PrimerApplePayOptions.
  • Wrong merchant identifier: the identifier does not match the Apple Developer portal and the Primer Dashboard. Apple rejects the request. If the Apple Pay sheet cannot open, the payment fails with unable-to-present-apple-pay.
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. There is no separate Apple Pay view or scope. Apple Pay appears whenever the Dashboard enables it for the session. It shows as a CheckoutPaymentMethod (with type == "APPLE_PAY") in PrimerPaymentMethods, or as a managed screen in PrimerCheckout. To pay, pass your merchant identifier through PrimerSettings(paymentMethodOptions:) with PrimerApplePayOptions(merchantIdentifier: "merchant.com.myapp", merchantName: nil). merchantName has no default value, so you must pass it. If Apple Pay never appears, confirm the method is present in session.selection?.state.paymentMethods and check the Dashboard. See the Apple Pay integration guide.

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 https://github.com/primer-io/primer-sdk-ios
  3. Set the dependency rule to Exact Version 3.0.0-beta.8, then add the PrimerSDK product to your target

CocoaPods installation fails

Cause: pod install fails or doesn’t include the Primer SDK. Solution: Verify that your Podfile pins the beta version, then reinstall. Without a version, CocoaPods resolves the latest stable 2.x release, which does not include Primer Checkout.

Minimum deployment target

Cause: Your app’s deployment target (or the Podfile’s platform :ios) is below iOS 15.0. The build or install fails before the app runs. Depending on how you integrate, you see one of these errors:
  • Swift Package Manager: The package product 'PrimerSDK' requires minimum platform version 15.0 for the iOS platform, but this target supports 14.0
  • Xcode build: compiling for iOS 14.0, but module 'PrimerSDK' has a minimum deployment target of iOS 15.0
  • pod install: a CocoaPods message that the matching PrimerSDK spec requires a higher minimum deployment target
Solution: The Primer Checkout iOS SDK requires iOS 15.0 or later. Update your deployment target:

Validation vs payment errors

Understanding the difference helps with proper error handling:
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.

Debugging tips

Log the outcome state

The onCompletion closure on PrimerCheckout or the .primerCheckoutSession(_:theme:onCompletion:) modifier receives the outcome PrimerCheckoutState. Expect it more than once when a payment attempt fails and the customer retries. .success and .dismissed end the session. In the composable flow, a failed initialization also ends it: the modifier delivers that one .failure and nothing after it.
For the composable flow, log it from the modifier instead:

Inspect available payment methods

Read the available methods from the PrimerSelectionSession once the session is .ready:

Check session readiness

The composable views bind only once the session reaches .ready. Observe session.phase to confirm initialization completed and the child sessions resolved:
phase has no failed value. If it stays .initializing, the session is either still loading or initialization failed, so check the onCompletion closure for a .failure.

Log card form validation errors

Read fieldErrors from the PrimerCardFormSession handed to a PrimerCardForm slot:

Extract diagnostics for support

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

See also

Events guide

Event handling patterns

Build a custom card form

Card form tutorial

Best practices

SwiftUI performance tips