Skip to main content
Primer Checkout iOS SDK is currently in beta (v3.0.0-beta.1). The API is subject to change before the stable release.

Installation issues

Swift Package Manager fails to resolve

Problem: Xcode reports that it cannot resolve primer-sdk-ios, or the package gets stuck while resolving versions. Solution: Confirm the package URL is https://github.com/primer-io/primer-sdk-ios.git and that the dependency rule targets the 3.0.0-beta.1 beta. Then reset Xcode’s cached package state:
  1. File ▸ Packages ▸ Reset Package Caches
  2. File ▸ Packages ▸ Resolve Package Versions
If resolution still fails, close Xcode and clear the derived data and SPM caches:
rm -rf ~/Library/Developer/Xcode/DerivedData
rm -rf ~/Library/Caches/org.swift.swiftpm
The Primer Checkout iOS SDK requires iOS 15.0+. If your target’s deployment version is lower, SPM cannot satisfy the platform requirement and resolution fails.

CocoaPods: bundle code-signing error on Xcode 14

Problem: After pod install, the build fails with an error stating that a resource bundle needs to be code signed. Solution: Add the following post_install script to your Podfile, then run pod install again. It disables code signing on the SDK’s resource bundles, which do not require signing:
post_install do |installer|
  installer.pods_project.targets.each do |target|
    if target.respond_to?(:product_type) and target.product_type == "com.apple.product-type.bundle"
      target.build_configurations.each do |config|
        config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
      end
    end
  end
end
After editing the Podfile, always run pod install again and reopen the generated .xcworkspace rather than the .xcodeproj. See the Installation guide for the full setup steps.

Runtime issues

Checkout stays in the initializing phase

Problem: PrimerCheckoutSession.phase never transitions from .initializing to .ready, so your composable views show only the loading state. Possible causes:
CauseWhat to check
Invalid or expired client tokenGenerate a fresh client token immediately before presenting checkout. Tokens are short-lived.
Network connectivityConfirm the device has connectivity and that your backend can reach Primer’s API.
Incorrect API keyVerify the API key used to create the client session belongs to the correct environment (sandbox vs. production).
Solution: When initialization fails, the session delivers a .failure(PrimerError) through the onCompletion closure on .primerCheckoutSession(_:onCompletion:). Inspect that error and log its diagnosticsId:
.primerCheckoutSession(session) { state in
    if case let .failure(error) = state {
        print("Checkout failed: \(error.localizedDescription)")
        print("Diagnostics ID: \(error.diagnosticsId)")
    }
}

Payment methods not showing

Problem: PrimerPaymentMethods renders its empty state with no payment methods listed. Possible causes:
  • No payment methods are configured in the Primer Dashboard.
  • The client session is missing required fields.
  • Payment methods are not enabled for the requested currency or country.
Solution: Verify your Primer Dashboard configuration, and ensure the client session includes the correct currencyCode and countryCode. Payment methods are filtered server-side based on these values.

Card form fields not appearing

Problem: Some card form fields — such as cardholder name or billing address fields — do not appear in PrimerCardForm. Explanation: The visible fields are driven by your Primer Dashboard configuration, surfaced through the session’s published state. Each default field building block self-hides unless its field is present in that configuration, so only fields enabled in the Dashboard appear. This is expected behavior, not a bug.
To branch on the active configuration inside a slot, read it from the session state, for example session.state.configuration.requiresBillingAddress.

3DS challenge not completing

Problem: A payment gets stuck during 3D Secure authentication. Solution: 3DS is handled automatically by the SDK. Make sure you do not dismiss or recreate the hosting view while the challenge is on screen — tearing down the view tears down the session (see below). For UIKit hosts, track the challenge lifecycle through the optional PrimerCheckoutPresenterDelegate 3DS callbacks. See the Handle payment result guide.

SwiftUI-specific issues

Session re-created on every render

Problem: Checkout reinitializes repeatedly, flickering back to .initializing, because a new PrimerCheckoutSession is allocated each time the view’s body is evaluated. Solution: Own the session with @StateObject so SwiftUI keeps a single instance across re-renders. Never construct it inline in body, inside a conditional, or in a callback:
// Correct — one session for the lifetime of the view
struct CheckoutView: View {
    @StateObject private var session = PrimerCheckoutSession(clientToken: token)

    var body: some View {
        ScrollView {
            PrimerPaymentMethods()
            PrimerCardForm()
        }
        .primerCheckoutSession(session) { state in handle(state) }
    }
}
// Incorrect — allocates a new session on every render
struct CheckoutView: View {
    var body: some View {
        let session = PrimerCheckoutSession(clientToken: token) // Don't do this
        return ScrollView {
            PrimerCardForm()
        }
        .primerCheckoutSession(session)
    }
}
The .primerCheckoutSession(_:onCompletion:) modifier bootstraps the session on appear and tears it down on disappear. If a fresh session is allocated on each render, that teardown-and-rebuild cycle prevents checkout from ever reaching .ready.

UI not updating

Problem: Your custom slot content does not reflect changes such as validation errors, surcharge updates, or the loading state. Solution: The Primer composable views observe their session’s @Published state automatically — you do not need a manual subscription. Drive your custom UI by reading from the session state inside the slot closure:
PrimerCardForm(submitButton: { session in
    Button("Pay \(session.state.surchargeAmount ?? "")") {
        session.submit()
    }
    .disabled(!session.state.isValid || session.state.isLoading)
})
Because each slot closure receives the observed session directly, capturing it in an external @State or copying its values into local variables breaks observation — always read session.state at the point of use.

Getting help

If you encounter an issue not covered here:
  1. Capture the diagnosticsId from the PrimerError delivered in onCompletion (SwiftUI) or primerCheckoutPresenterDidFailWithError(_:) (UIKit).
  2. Verify your configuration in the Primer Dashboard.
  3. Contact Primer support and include the diagnosticsId so the team can trace your request.
.primerCheckoutSession(session) { state in
    if case let .failure(error) = state {
        // Share this value with Primer support
        print("Diagnostics ID: \(error.diagnosticsId)")
    }
}

See also

Installation

Add the SDK via SPM or CocoaPods

Handle Payment Result

Process success, failure, and dismissal

Primer Error

Inspect errors and the diagnosticsId

Checkout Session

Own and observe the session lifecycle