Headless Universal Checkout


Headless Universal Checkout is currently in beta for iOS and Android.

Where there is a need for more customization and control over the checkout experience, a headless version of Primer’s Universal Checkout is available.

Universal Checkout can be used with your own UI, giving you more flexibility and allowing you to move quicker when making design changes, while still having Universal Checkout capture sensitive PCI card data.

As with all versions of Universal Checkout, any payment method can be added and configured through Dashboard, meaning Primer handles the logic of when to display each method.

Headless Universal Checkout is currently available in beta on iOS SDK version 1.28.0-beta.2

Step 1. Install

You can integrate PrimerSDK using CocoaPods or Swift Package Manager. Follow the relevant guide below.

Via CocoaPods

The iOS SDK is available with Cocoapods. Just pod init in your project directory and add this to your Podfile :

target 'MyApp' do  # Other pods...   # Add this to your Podfile  pod 'PrimerSDK'end

Once you're done editing, just run pod install in the terminal and open your project using the .xcworkspace file.

Via Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into Xcode.

Go to File → Swift Packages → Add Package Dependency. In the window that opened enter https://github.com/primer-io/primer-sdk-ios.git

Or just add:

dependencies: [    .package(url: "https://github.com/primer-io/primer-sdk-ios.git")]

Step 2. Initialize Primer’s Headless Universal Checkout

Request a client token from your backend.

Check our guide on how to set up the client session here.
Remember that based on your client token different payment methods will show up.

Once you have a client token, you can initialize Primer’s Headless Checkout with start(withClientToken:settings:delegate:completion:). The completion handler will return the available payment methods for the client session.

The completion will contain an array of the payment method type enum cases. Below you can find all the payment methods cases.

public enum PrimerPaymentMethodType {    case adyenAlipay    case adyenDotPay    case adyenGiropay    case adyenIDeal    case adyenMobilePay    case adyenSofort    case adyenTrustly    case adyenTwint    case adyenVipps    case apaya    case applePay    case atome    case buckarooBancontact    case buckarooEps    case buckarooGiropay    case buckarooIdeal    case buckarooSofort    case goCardlessMandate    case googlePay    case hoolah    case klarna    case mollieBankcontact    case mollieIdeal    case payNLBancontact    case payNLGiropay    case payNLIdeal    case payNLPayconiq    case paymentCard    case payPal    case xfers}

The only PrimerSettings that you might need on the HUC are the merchantIdentifier for Apple Pay, and the urlScheme for PayPal.

Payment methods are added and configured through Dashboard. The completion handler will return the payment methods whose conditions match the current client session.

You can set the delegate from the function above, or set it separately PrimerHeadlessUniversalCheckout.current.delegate = self

// 👇 Import Primer SDKimport PrimerSDK class ViewController: UIViewController {         override func viewDidLoad() {                super.viewDidLoad()                 // 👇 Create a client session                self.fetchClientToken { (clientToken, err) in            if let err = err {                    // Handle error             } else if let clientToken = clientToken {                // 👇 Settings are optional, but they are needed for Apple Pay and PayPal                let settings = PrimerSettings(                    merchantIdentifier: "merchant.dx.team",  // 👈 Entitlement added in Xcode's settings, required for Apple Pay                    urlScheme: "merchant://")                // 👈 URL Scheme added in Xcode's settings, required for PayPal                 PrimerHeadlessUniversalCheckout.current.start(withClientToken: clientToken,                  settings: settings, delegate: self) { paymentMethodTypes, err in                    // Any additional logic here                }            }        }        } }

Once Primer’s Headless Checkout is configured, the delegate functions will notify you about HUC events.

extension ViewController: PrimerHeadlessUniversalCheckoutDelegate {         // This function will notify you when the client session has been set up successfully.        // At this point it's safe to continue with the rest of the flow.    func primerHeadlessUniversalCheckoutClientSessionDidSetUpSuccessfully() {     }         // This function will notify you that the tokenization preparation has started. At this        // point the SDK has not yet started to communicate with Primer's backend.    func primerHeadlessUniversalCheckoutPreparationStarted() {     }         // This function will notify you that the tokenization API call has been fired.    func primerHeadlessUniversalCheckoutTokenizationStarted() {     }         // This function will notify you that the payment method you requested to show (e.g.        // Apple Pay) has been presented.    func primerHeadlessUniversalCheckoutPaymentMethodPresented() {     }         // This function will notify you that tokenization has succeeded, now you can use the        // payment method token to create a payment on your backend.    func primerHeadlessUniversalCheckoutTokenizationSucceeded(paymentMethodToken: PaymentMethodToken, resumeHandler: ResumeHandlerProtocol?) {     }         // This function will notify you that you can resume a previously created paused        // payment.        func primerHeadlessUniversalCheckoutResume(withResumeToken resumeToken: String, resumeHandler: ResumeHandlerProtocol?) {     }         // This function will notify you that something went wrong, listen and handle the        // errors appropriately.    func primerHeadlessUniversalCheckoutUniversalCheckoutDidFail(withError err: Error) {     } }

Step 3. Creating your UI

You can create any UI suits better your needs, for forms and/or buttons. However, the SDK exposes payment method assets and buttons (that follow each payment method’s guidelines). It’s up to you whether you want to use them, or just create your own.

To get payment method assets use PrimerHeadlessUniversalCheckout.getAsset(for:type) specifying the payment method and the type (i.e. logo or icon).

class ViewController: UIViewController {         override func viewDidLoad() {                super.viewDidLoad()                 // ...                let appleLogoImage = PrimerHeadlessUniversalCheckout.getAsset(for: .applePay, type: .logo)                let applePayButton = PrimerHeadlessUniversalCheckout.makeButton(for: .applePay)        } }

Step 3.a. Present Apple Pay, PayPal and/or Alternative Payment Methods (APMs)

Provided that the payment method you want to use has been returned in the available payment methods, you can add your buttons for them, or use Primer SDK’s buttons.

Remember that the client token and Dashboard configuration determine which payment methods are returned.
class ViewController: UIViewController {         var applePayButton: UIButton!        var payPalButton: UIButton!        var giropayButton: UIButton!         override func viewDidLoad() {                super.viewDidLoad()                 // ...                applePayButton = PrimerHeadlessUniversalCheckout.makeButton(for: .applePay)                payPalButton = PrimerHeadlessUniversalCheckout.makeButton(for: .payPal)                giropayButton = PrimerHeadlessUniversalCheckout.makeButton(for: .adyenGiropay)                 // Add the buttons on your view, and add actions for them (let's say that the                // actions are named payWithApplePayButtonTapped(_:), payWithPayPalButtonTapped(_:)                // and payWithAdyenGiropayButtonTapped(_:).        } }

Once any payment method’s button is tapped, call PrimerHeadlessUniversalCheckout.current.showPaymentMethod(_:). You will receive events that will notify you for the progress on the delegate functions.

class ViewController: UIViewController {         @IBAction func payWithApplePayButtonTapped(_ sender: Any) {        PrimerHeadlessUniversalCheckout.current.showPaymentMethod(.applePay)    }     @IBAction func payWithPayPalButtonTapped(_ sender: Any) {        PrimerHeadlessUniversalCheckout.current.showPaymentMethod(.payPal)    }         @IBAction func payWithAdyenGiropayButtonTapped(_ sender: Any) {        PrimerHeadlessUniversalCheckout.current.showPaymentMethod(.adyenGiropay)    } }

That’s it 🎉

Step 3.b. Create your Card Form UI

Headless Universal Checkout securely captures payment method data while fully embedded in your app. By communicating directly with Primer's PCI-L1 tokenization service, Universal Checkout transforms sensitive customer data into a secure uniform string called a payment method token.

Provided that the availablePaymentMethodTypes includes the .paymentCard type, create a PrimerHeadlessUniversalCheckout.CardFormUIManager.

Then loop through the card form UI manager’s requiredInputElementTypes and create a PrimerInputTextField for each one.

Remember that you have to register your textfields on the PrimerHeadlessUniversalCheckout.CardFormUIManager
class ViewController: UIViewController {         var cardFormUIManager: PrimerHeadlessUniversalCheckout.CardFormUIManager!         override func viewDidLoad() {                super.viewDidLoad()                self.cardFormUIManager = try! PrimerHeadlessUniversalCheckout.CardFormUIManager(delegate: self)        }         func makeCardForm() {        var inputElements: [PrimerInputTextField] = []         for requiredInputElementType in self.cardFormUIManager.requiredInputElementTypes {            let textField = PrimerInputTextField(type: requiredInputElementType, frame: .zero)                         // Set the textField's inputElementDelegate if you want to react to textfield events                        textField.inputElementDelegate = self                         // Style your textfield as you would with any UITextField and keep its                        // reference, because we'll need to register them in the cardFormUIManager            inputElements.append(textField)        }                 // ❗ Register your textfields on the card form manager        cardFormUIManager.inputElements = inputElements    } }

The textfields will automatically format and validate their text input, depending on their type.

Remember you won’t have access to the textfield’s text.

You can react to textfields’ events with the following delegate functions.

class ViewController: PrimerInputElementDelegate {         func inputElementShouldBlur(_ sender: PrimerInputElement) -> Bool {            return false    }     func inputElementDidBlur(_ sender: PrimerInputElement) {     }         func inputElementShouldFocus(_ sender: PrimerInputElement) -> Bool {        return false    }     func inputElementDidFocus(_ sender: PrimerInputElement) {     }     func inputElementValueDidChange(_ sender: PrimerInputElement) {     }     func inputElementDidDetectType(_ sender: PrimerInputElement, type: Any?) {     }     func inputElementValueIsValid(_ sender: PrimerInputElement, isValid: Bool) {     }}

Create your own “Pay by card” button, and connect it to an IBAction (e.g. payWithCardButtonTapped(_:).

The PrimerHeadlessUniversalCheckout.CardFormUIManager delegate will notify you when all the fields’ values are valid. At this point, it’s safe to call the PrimerHeadlessUniversalCheckout.CardFormUIManager's tokenize() function.

class ViewController {         // ...         @IBAction func payWithCardButtonTapped(_ sender: Any) {        self.cardFormUIManager.tokenize()    }         // ...} class ViewController: PrimerCardFormDelegate {    func cardFormUIManager(_ cardFormUIManager: PrimerHeadlessUniversalCheckout.CardFormUIManager, isCardFormValid: Bool) {         }}

Enable 3DS

The HUC supports 3DS out of the box, but some configuration is needed in production.

Firstly, contact Primer Solution Engineers to enable 3DS on your account. Once this is done, you’ll have access to Primer’s private 3DS repositories. Add the Primer3DS SDK in your project.

Via CocoaPods

  • Add Primer’s private specs repo at the top of your podfile
  • Add the Primer3DS pod

Your podfile should look like this:

source 'https://github.com/CocoaPods/Specs.git'source 'https://github.com/primer-io/primer-podspecs.git' use_frameworks! target 'PrimerSDK_Example' do  pod 'PrimerSDK'  pod 'Primer3DS'    // ...end

Via Swift Package Manager

With your Xcode project opened:

  • Select the project like, then Package Dependencies like the example below Package Dependencies
  • Click on the + button at the bottom of the Packages section
  • In the Search Bar at your left, paste the Primer 3DS URL: https://github.com/primer-io/primer-sdk-3ds-ios Search for Primer 3DS Adding Primer 3DS
  • Press Add Package
  • Let Xcode download the package and set everything up

Ensure that the 3DS Framework is Imported Correctly

  • Clean, build and run your project
  • Once PrimerSDK is initialized check the console logs whether Can import Primer3DS is logged.
  • If you want to perform 3DS when vaulting cards you should set is3DSOnVaultingEnabled in the PrimerSettings object to true.
  • That’s it! 🎉 Check the 3DS testing section below to understand how to perform 3DS payments in your sandbox environment.


If you see Failed to import Primer3DS in the console logs, choose Manual Order in your project's scheme. See the screenshots below for details. Manage Schemes Manual Order

Demo Implementation

Here is a Demo app you can download to test the implementation straightaway.

Demo Implementation

Make sure to implement the client session API fetching on your BE. The app must consume the client session response in order to retrieve the clientToken and initialize the Primer SDK.