Overview
Headless Universal Checkout is currently in beta for Web, iOS, Android and React Native. The following payment methods are unsupported:
ADYEN_IDEAL
, ADYEN_DOTPAY
, ADYEN_BLIK
, XFERS_PAYNOW
, RAPYD_PROMPTPAY
, RAPYD_FAST
,
PRIMER_TEST_KLARNA
, PRIMER_TEST_PAYPAL
, PRIMER_TEST_SOFORT
Web currently only supports PAYMENT_CARD
.
Where there is a need for more customization and control over the checkout experience, a headless version of Primer’s Universal Checkout is available. You can use Headless Universal Checkout with your own UI, giving you more flexibility and allowing you to move faster when making design changes, while still having Universal Checkout capture sensitive PCI card data or other form data.
As with all versions of Universal Checkout, any payment method can be added and configured through the Primer Dashboard, meaning Primer handles the logic of when to display each method.
Primer Headless Universal Checkout works in a simple way:
- 1Get a
clientToken
from your server - 2Start Primer Headless Universal Checkout with the client token
- 3Primer Headless Universal Checkout will then return the
available payment methods
for the session initiated. Those payment methods that have been configured in the Dashboard and whose conditions match the current client session will be returned. - 4Show the list of available payment methods.
- 5When the user selects a payment method, show its UI to enable the user to enter their credentials. Depending on the payment method, you will have to either ask the SDK to render it, or build the UI yourself.
- 6Primer Headless Universal Checkout will then create a payment for you and manage its lifecycle. You will receive a confirmation of payment with a callback to indicate the checkout flow has completed.
This documentation is only relevant for v2.5.0 and upward. The documentation for version v2.4.0 is available here.
Prerequisites
Step 1: Prepare the Headless Universal Checkout delegate
Set the Headless Universal Checkout delegate and conform to PrimerHeadlessUniversalCheckoutDelegate
, to handle the callbacks that happen during the checkout’s lifecycle.
123456789101112131415161718192021222324
import PrimerSDKimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() PrimerHeadlessUniversalCheckout.current.delegate = self }} extension ViewController: PrimerHeadlessUniversalCheckoutDelegate { func primerHeadlessUniversalCheckoutDidLoadAvailablePaymentMethods(_ paymentMethodTypes: [String]) { // Primer will return available payment methods based on the configuration. // Use it to create your styled views. } func primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData(_ data: PrimerCheckoutData) { // Primer checkout completed with checkoutData. // Show an order confirmation screen, fulfil the order... }}
Step 2: Start Headless Universal Checkout
Once you have a client token, you can initialize Primer’s Headless Universal Checkout by calling PrimerHeadlessUniversalCheckout.current.start(withClientToken:settings:completion:)
123456789101112131415161718192021222324
import PrimerSDKimport UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() PrimerHeadlessUniversalCheckout.current.delegate = self Networking.createClientSession() { (clientToken, err) in if let clientToken = clientToken { // Create the PrimerSDK settings (default settings) let settings: PrimerSettings = PrimerSettings() // Initialize Primer Headless Universal Checkout PrimerHeadlessUniversalCheckout.current.start(withClientToken: clientToken, settings: settings, completion: { (paymentMethods, err) in // paymentMethods is an array of the available payment Methods // for this client session }) } } }}
Once Primer’s Headless Checkout is configured, its delegate functions will notify you about Headless Universal Checkout events.
Check the SDK API here to customize your SDK settings.
Based on the values in the Client Session (e.g. currencyCode
or countryCode
) you can display different payment methods in the checkout.
Look at the “Checkout” tab on the Primer Dashboard to configure the rules for payment method display.
Step 3: Show available payment methods
When the checkout is done initializing, the primerHeadlessUniversalCheckoutDidLoadAvailablePaymentMethods
method in the delegate will be invoked. Use this event to show the list of payment methods to the user. You can use Primer SDK’s buttons or add your own.
1234567891011121314151617181920212223242526272829303132333435
extension ViewController: PrimerHeadlessUniversalCheckoutDelegate { func primerHeadlessUniversalCheckoutDidLoadAvailablePaymentMethods(_ paymentMethodTypes: [String]) { self.paymentMethods = paymentMethodsTypes } func primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData(_ data: PrimerCheckoutData) { // Checkout completed }} extension UIViewController: UITableViewDataSource, UITableViewDelegate { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.paymentMethods.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let paymentMethod = self.paymentMethods[indexPath.row] let cell = tableView.dequeueReusableCell(withIdentifier: "MerchantPaymentMethodCell", for: indexPath) as! MerchantPaymentMethodCell cell.configure(paymentMethodType: paymentMethod) // Primer SDK can provide you with Payment Method buttons that follow // the payment methods UI guidelines. // Example given for Apple Pay payment method // https://developer.apple.com/design/human-interface-guidelines/technologies/apple-pay/introduction // let paymentMethodButton = PrimerHeadlessUniversalCheckout.makeButton(for: paymentMethod) return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // The user has selected a payment method }}
Step 4: Handle payment method selection
When the user selects a payment method, you should show the payment method to the customer, either by asking the SDK to show it, or by building the UI.
Headless Universal Checkout currently handles payment methods in three ways:
- With the function
PrimerHeadlessUniversalCheckout.current.showPaymentMethod
that renders a view handled by Primer. This is the case today for redirect-based payment methods and Apple Pay. - With a Primer card manager that provides you with the list of data to capture so that you can build your on UI. This is the case today for card payments. Primer card manager provides you already built native components that handle validation and formatting of the input data.
- With a “Raw Data Manager” that provides you with the list of data to capture so that you can build your own UI. This is the case today for card payments.
123456789101112131415
extension UIViewController: UITableViewDataSource, UITableViewDelegate { //... func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let paymentMethodType = self.paymentMethods[indexPath.row] if paymentMethodType == "PAYMENT_CARD" { // The UI for payment method card should be implemented using the input elements and the card manager. self.showCardForm() } else { PrimerHeadlessUniversalCheckout.current.showPaymentMethod(paymentMethodType) } }}
Step 4.1: Handle showPaymentMethod
showPaymentMethod
Use the function showPaymentMethod
to display the payment method that should be handled by Primer.
1
PrimerHeadlessUniversalCheckout.current.showPaymentMethod(paymentMethodType)
The user can now interact with the payment method’s UI, and the SDK will create the payment. The payment’s data will
be returned on primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData
configured in Step 1.
Step 4.2: Show card form
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. Universal Checkout automatically uses this token to create a payment.
If the user wants to pay with a card, you can display a card form for paying by card.
Primer will provide you with the required inputs for a given payment method. Use this to create your inputs.
Step 4.2.1: Primer Card Manager
With the card manager, you can show your fully customized UI and still use all of the features that make Primer great.
Render input elements
First, use PrimerHeadlessUniversalCheckout.CardFormUIManager
to render the card form input elements like this:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
class ViewController: UIViewController { var cardFormUIManager: PrimerHeadlessUniversalCheckout.CardFormUIManager! // ... func renderCardForm() { PrimerHeadlessUniversalCheckout.current.delegate = self self.cardFormUIManager = try! PrimerHeadlessUniversalCheckout.CardFormUIManager() var inputElements: [PrimerInputElement] = [] for inputElementType in self.cardFormUIManager!.requiredInputElementTypes { // For each inputElementType create a PrimerInputTextField and style it as you want. let textField = PrimerInputTextField(type: inputElementType, frame: .zero) textField.borderStyle = .line textField.layer.borderColor = UIColor.black.cgColor if inputElementType == .cardNumber { self.cardNumberTextField = textField self.cardNumberTextField?.placeholder = "Card number" } else if inputElementType == .expiryDate { self.expiryTextField = textField self.expiryTextField?.placeholder = "Expiry" } else if inputElementType == .cvv { self.cvvTextField = textField self.cvvTextField?.placeholder = "CVV" } else if inputElementType == .cardholderName { self.cardHolderNameTextField = textField self.cardHolderNameTextField?.placeholder = "Cardholder" } // Set the input element's delegate to receive events regarding the field's state. textField.inputElementDelegate = self // Add the text field on your view self.stackView.addArrangedSubview(textField) textField.translatesAutoresizingMaskIntoConstraints = false textField.heightAnchor.constraint(equalToConstant: 50).isActive = true inputElements.append(textField) } // Register your inputElements on the PrimerHeadlessUniversalCheckout.CardFormUIManager self.cardFormUIManager.inputElements = inputElements }}
Conform to the PrimerCardFormDelegate
Then, listen to the card form validation state and enable your form’s submit button accordingly:
123456
extension MerchantCardFormViewController: PrimerCardFormDelegate { func cardFormUIManager(_ cardFormUIManager: PrimerHeadlessUniversalCheckout.CardFormUIManager, isCardFormValid: Bool) { // Enable your pay button when isCardFormValid is true. }}
Handle input updates (optional)
When displaying a card form, you may want to trigger UI changes based on the updates and/or validation status of an active input field. To achieve this, conform to the PrimerInputElementDelegate
and listen to the input element field’s update.
123456789101112131415161718192021222324252627282930
extension MerchantCardFormViewController: PrimerInputElementDelegate { func inputElementShouldFocus(_ sender: PrimerInputElement) -> Bool { } func inputElementDidFocus(_ sender: PrimerInputElement) { } func inputElementShouldBlur(_ sender: PrimerInputElement) -> Bool { } func inputElementDidBlur(_ sender: PrimerInputElement) { } func inputElementValueDidChange(_ sender: PrimerInputElement) { } func inputElementValueIsValid(_ sender: PrimerInputElement, isValid: Bool) { } func inputElementDidDetectType(_ sender: PrimerInputElement, type: Any?) { }}
Configure submit button
Finally, configure a pay action:
12345678
class ViewController { // ... @IBAction func payButtonTapped(_ sender: Any) { self.cardFormUIManager.tokenize() }}
When the function tokenize()
is called, the SDK will attempt to create a payment. The payment’s data will be returned on primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData
configured in Step 1.
Step 4.2.2: Primer Raw Data Manager
With the raw (card) manager, you can use your fully customized UI and still use all of the features that make Primer great.
At any point, you can set your data via defining an instance of PrimerHeadlessUniversalCheckout.RawDataManager
and setting its RawData primerRawDataManagerInstance.rawData
.
You can also receive events via the appropriate delegate PrimerRawDataManagerDelegate
.
Conform to the PrimerRawDataManagerDelegate
When setting Raw Card Data, you may want to validate the data you just set and all the its metadata changes. To achieve this, you can conform to our PrimerRawDataManagerDelegate
(e.g. primerRawDataManagerInstance.delegate = self
), that exposes two events.
12345
@objcpublic protocol PrimerRawDataManagerDelegate { @objc optional func primerRawDataManager(_ rawDataManager: PrimerHeadlessUniversalCheckout.RawDataManager, metadataDidChange metadata: [String: Any]?) @objc optional func primerRawDataManager(_ rawDataManager: PrimerHeadlessUniversalCheckout.RawDataManager, dataIsValid isValid: Bool, errors: [Error]?)}
Here is an example implementation:
1234567891011
extension MerchantHUCRawCardDataViewController: PrimerRawDataManagerDelegate { func primerRawDataManager(_ rawDataManager: PrimerHeadlessUniversalCheckout.RawDataManager, dataIsValid isValid: Bool, errors: [Error]?) { print(errors) } func primerRawDataManager(_ rawDataManager: PrimerHeadlessUniversalCheckout.RawDataManager, metadataDidChange metadata: [String : Any]?) { print(metadata) }}
Listen to the card data validation state and enable your form’s submit button accordingly.
Render Input Elements
After initializing an instance of PrimerHeadlessUniversalCheckout.RawDataManager
, by calling listRequiredInputElementTypes(for: "A_PAYMENT_METHOD_TYPE")
(e.g. for card data listRequiredInputElementTypes(for: "PAYMENT_CARD")
), will return a list of PrimerInputElementType
.
e.g. for PAYMENT_CARD
Possible values are CARD_NUMBER
, EXPIRY_DATE
, CVV
, CARDHOLDER_NAME
.
Primer Headless Universal Checkout requires input data to be sent for further processing.
For PAYMENT_CARD
, you should pass PrimerCardData
, example initialization below:
12345
let primerCardData = PrimerCardData(cardNumber: "4242424242424242", expiryMonth: "11", expiryYear: "2025", cvv: "123", cardholderName: "John Smith")
Submit input data changes
In order to validate input data, and do further processing, submit input data changes by assigning the primerCardData
we have just created, to your instance of PrimerHeadlessUniversalCheckout.RawDataManager
:
1
primerRawDataManager.rawData = primerCardData
All changes will be captured via its delegate function:
1
@objc optional func primerRawDataManager(_ rawDataManager: PrimerHeadlessUniversalCheckout.RawDataManager, dataIsValid isValid: Bool, errors: [Error]?)
Configure submit button
Finally, by calling the submit()
function of our instance of PrimerHeadlessUniversalCheckout.RawDataManager
(e.g. primerRawDataManager.submit()
) the SDK will attempt to create a payment.
The payment’s data will be returned on primerHeadlessUniversalCheckoutDidCompleteCheckoutWithData
configured in Step 1.
Step 5: Handle delegate functions
Handle primerHeadlessUniversalCheckoutPreparationDidStart
(optional)
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. You can use this to show a loader or notify users about progress in any other way.
123456
extension ViewController: PrimerHeadlessUniversalCheckoutDelegate { func primerHeadlessUniversalCheckoutPreparationDidStart(for paymentMethodType: String) { // e.g. show a loading screen. }}
Handle primerHeadlessUniversalCheckoutTokenizationDidStart
(optional)
This function will notify you that the tokenization API call has been fired. You can use this to show a loader or notify users about progress in any other way.
123456
extension ViewController: PrimerHeadlessUniversalCheckoutDelegate { func primerHeadlessUniversalCheckoutTokenizationDidStart(for paymentMethodType: String) { // e.g. show a loading screen. }}
Handle primerHeadlessUniversalCheckoutPaymentMethodDidShow
(optional)
This function will notify you that the payment method you requested to show has been presented.
123456
extension ViewController: PrimerHeadlessUniversalCheckoutDelegate { func primerHeadlessUniversalCheckoutPaymentMethodDidShow(for paymentMethodType: String) { // e.g. hide a loading screen. }}
Handle errors
Use the primerHeadlessUniversalCheckoutDidFail
method to handle any errors emitted by the SDK during the checkout flow.
123456
extension ViewController: PrimerHeadlessUniversalCheckoutDelegate { func primerHeadlessUniversalCheckoutDidFail(withError err: Error) { // Handle the error }}
Prepare 3DS
In order to make the SDK lightweight, 3DS requires the addition of the Primer 3DS library. Check out our guide on how to add the Primer 3DS library to your iOS app.
When the user pays by card, the Workflow will decide whether or not a 3DS challenge is required. If so, Headless Universal Checkout will automatically render the 3DS challenge.
To improve 3DS success rates, it is recommended to pass the following elements in the Client Session:
customer.emailAddress
customer.billingAddress
Did It Work?
If everything went well, you should be able to see the payment you just created on your Dashboard under the Payments menu.