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

If you're looking for a simpler way of integrating Universal Checkout, consider integrating our Drop-In checkout.

Not all payment methods are currently compatible with Headless Checkout.

Please refer to this table to learn more about the payment methods available for Headless Checkout.

Before you start

Before you start, make sure:

Create a client session

A client session is the starting point for integrating payments at Primer. You can attach any data associated with the order to your client session.

Creating a client session provides you with a client token, a temporary key used to initialize the Universal Checkout.

The information you include in the client session is used in the Dashboard:

  • to conditionally route payments with Workflows
  • to activate payment methods and other features in Universal Checkout

So pass as much information as you can!

Generate an API key

Requests to our API are authenticated using an API key in the X-Api-Key header. Create an API key by visiting the developer page of the Primer Dashboard.

Make sure to set the following scopes for your API Key:

  • client_tokens:write
  • transactions:authorize

Make a client session request

On your server, create a client session with POST/client-session.

Make sure to pass at least the following data:

FieldDescription
Your reference for the payment.

Make sure to keep track of orderId - you will later receive updates to the payment via Webhooks. The payment will contain the orderId specified in the client session.
The three-letter currency code in ISO 4217 format.
e.g. use USD for US dollars.
  1. order
The details of the line items of the order.
ℹ️

The body of a successful response contains a clientToken that you will use to initialize the Universal Checkout.

Here is how the client session request to the Primer API should look like:

123456789101112131415161718192021222324252627282930313233343536
curl --location --request \ POST 'https://api.sandbox.primer.io/client-session' \ --header 'X-Api-Key: <YOUR_API_KEY>' \ --header 'X-Api-Version: 2.2' \ --header 'Content-Type: application/json' \ --data '{    "orderId": "<YOUR_ORDER_ID>",    "currencyCode": "GBP",    "amount": 5000,    "order": {      "lineItems": [{        "itemId": "shoes-123",        "amount": 2500,        "quantity": 2      }],      "countryCode": "GB"    } }'
# Here is a (heavily truncated) example response
{  "clientToken": "THE_CHECKOUT_SESSION_TOKEN",  "clientExpirationDate": "2022-03-08T14:00:00Z",  "orderId": "<YOUR_ORDER_ID>",  "currencyCode": "GBP",  "amount": 5000,    "order": {    "lineItems": [{      "itemId": "shoes-123",      "amount": 2500,      "quantity": 2    }],    "countryCode": "GB",  }}
bash
copy

To use new Workflows and all of its exciting features, make sure to pass the following header in your API request:

1
Legacy-Workflows: false
shell
copy

See this migration guide for more information.

Get Started

Primer Headless Universal Checkout works in a simple way:

  1. 1
    Get a clientToken from your server.
  2. 2
    Start Primer Headless Universal Checkout with the client token.
  3. 3
    Primer 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.
  4. 4
    You show the user the list of available payment methods.
  5. 5
    When 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.
  6. 6
    Primer's 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.17.0 and upward.

The migration guide to update from v2.16.1 is available here.

The documentation for versions v2.5.0 up to v2.16.1 is available here.

Install the SDK

Add the SDK package

12345
# With yarnyarn add @primer-io/react-native
# With npmnpm i @primer-io/react-native --save
bash
copy

Requirement for iOS

Once you are done, navigate to the /ios folder and run pod install.

For more details about SDK versions, please see our changelog.

Step 1. Prepare Headless Universal Checkout

Firstly, import HeadlessUniversalCheckout from @primer-io/react-native. Then set Headless Universal Checkout callbacks in the headlessUniversalCheckoutCallbacks property of your settings. These callbacks are handled during the checkout’s lifecycle.

Once done, start HeadlessUniversalCheckout with your session’s client token. This will return a list of available payment methods for you to handle.

1234567891011121314151617181920212223242526272829
import { useEffect } from 'react'import { HeadlessUniversalCheckout, PrimerSettings, PrimerCheckoutData } from '@primer-io/react-native'
export const CheckoutScreen = () => {    const [paymentMethods, setPaymentMethods] = useState(null)
    useEffect(() => {        const initHeadlessUniversalCheckout = async () => {            const settings: PrimerSettings = {                //...                headlessUniversalCheckoutCallbacks: {                    onCheckoutComplete = (checkoutData: PrimerCheckoutData) => {                        // Here you will receive the checkout data of the payment.                    },                    onAvailablePaymentMethodsLoad = (paymentMethodTypes: string[]) => {                        // paymentMethodTypes contains the payment methods identifiers available                        // for the client session.                    },                },            }
            // 👇 Call this function to start Headless Universal Checkout and retrieve the list of available payment methods            const availablePaymentMethods = await HeadlessUniversalCheckout.startWithClientToken(clientToken, settings)            setPaymentMethods(availablePaymentMethods)        }
        initHeadlessUniversalCheckout()    }, [clientToken])}
tsx
copy

Each payment method returned contains the following:

  • paymentMethodType
    a unique string identifier for the payment method (e.g. ADYEN_IDEAL)
  • paymentMethodName
    an user friendly English localized string identifier for the payment method (e.g. Apple Pay)
  • supportedPrimerSessionIntents
    an array of SessionIntent which defines what intents can be used with this payment method (i.e. CHECKOUT or VAULT).
  • paymentMethodManagerCategories
    an array which defines the payment method managers that can be used with this payment method (i.e. NATIVE_UI or RAW_DATA).
  • [Optional] requiredInputDataClass
    the String representing class type that should be used with the raw data manager.

Step 2: Render available payment methods UI

You should now have the available payment methods for the client session. You can render your own UI for each payment method, or take advantage of the AssetsManager helper to get access to payment methods' assets.

1234567
import { AssetsManager, Asset } from '@primer-io/react-native'
const getPaymentMethodAssets = async () => {    const assetsManager = new AssetsManager()    const assets: Asset[] = await assetsManager.getPaymentMethodAssets()    return assets}
tsx
copy

The payment method asset contains the following:

  • paymentMethodType
    a unique string identifier for the payment method.
  • paymentMethodName
    an user friendly English localized string identifier for the payment method (e.g. Apple Pay)
  • paymentMethodLogo
    contains the logos’ local URLs
    • colored?: string: Local URL for the colored value of the logo
    • dark?: string: Local URL for the dark value of the logo
    • light?: string: Local URL for the light value of the logo
  • paymentMethodBackgroundColor
    contains the background colors hex values
    • colored?: string: Color hex value for the colored background color
    • dark?: string: Color hex value for the dark mode background color
    • light?: string: Color hex value for the light mode background color
❗️

colored, dark and light variables are all optional, but it is guaranteed that the objects will contain at least one of them.

With the above URLs and colors you can build your own payment method UI 💪.

Step 3: Implement a payment method manager

Now that your UI presents the available payment methods, let's use the payment method managers to allow users to go through the payment method flow.

A payment method manager is a class abstracting the state and UI of a given payment method. Currently, there are 3 payment methods managers, each of them responsible for a different payment method category.

  • PrimerHeadlessUniversalCheckout.NativeUIManager
    The native UI manager is responsible for all payment methods that need to present their own UI (e.g. Apple Pay or Google Pay).
  • PrimerHeadlessUniversalCheckout.RawDataManager
    The raw data manager is responsible for each payment method that you need to pass some raw data (e.g. card form).
💡

Bear in mind that a payment method might be able to be implemented by more than one payment method manager. You can check which managers are supported for each payment method in the paymentMethodManagerCategories of the payment method objects received in Step 1.

❗️

A manager can only be used after Headless Checkout has been initialized. We consider Headless Checkout to be initialized after the onAvailablePaymentMethodsLoad is triggered and payment methods for the provided clientToken are returned.

If that’s not the case SDK will throw a uninitialized-sdk-session error.

If a payment method manager gets initialized with an unsupported payment method type (i.e. the payment method's object doesn't include the manager's category), it will throw an unsupported-payment-method-type error.

Native UI manager

This manager can be used for any payment method that contains NATIVE_UI in the paymentMethodManagerCategories array.

The native UI manager can be used for any payment method that needs to present its own UI, like Apple Pay.

❗️

Make sure that the intent in the showPaymentMethod function is valid and contained in the supportedPrimerSessionIntents array.

1234567
import { NativeUIManager } from '@primer-io/react-native'
const payWithPaymentMethod = async (paymentMethodType: string) => {    const nativeUIManager = new NativeUIManager()    await nativeUIManager.configure({ paymentMethodType: paymentMethod.paymentMethodType })    await nativeUIManager.showPaymentMethod(SessionIntent.CHECKOUT)}
tsx
copy

That’s it! The SDK will present the native UI to the user, and then attempt to create a payment.

The payment’s data will be returned on the onCheckoutComplete that we configured in Step 1.

Raw Data Manager

This manager can be used for any payment method that contains RAW_DATA in the paymentMethodManagerCategories array.

The raw data manager can be used for payment methods that allow you to pass the data in the SDK (an example would be card data).

With the raw manager, you can use your fully customized UI and still use all of the features that make Primer great.

At any point, you can validate your data by calling setRawData() and by listening to onValidation. Primer will return errors specific to each required input.

When the data is valid, you can send it to Primer for further processing by calling submit().

Configure the RawDataManager

Assuming that the payment method contains RAW_DATA in the paymentMethodManagerCategories, create your raw data manager, and optionally set its delegate.

123456789101112
const initialize = async (paymentMethod: PaymentMethod) => {    // 👇 Initialize the raw data manager    await rawDataManager.configure({        paymentMethodType: paymentMethod.paymentMethodType,        onMetadataChange: data => {            // For example card number detected to be Visa        },        onValidation: (isValid, errors) => {            // Show on your UI if the card data aren't valid        },    })}
tsx
copy

Build a form

Build your own form to let the users enter their data. You can use the getRequiredInputElementTypes() object to get informed about what input fields you have to render. It returns an array that can contain any of the following:

  • "CARD_NUMBER",
  • "EXPIRY_DATE",
  • "CVV",
  • "CARDHOLDER_NAME",
  • "PHONE_NUMBER"
1234567
const initialize = async () => {    // ...    // Once done, get the required input elements types.
    // 👇 Get the required input element types, which will enable you to build your form    const requiredInputElementTypes = await rawDataManager.getRequiredInputElementTypes()}
tsx
copy

Build and validate your rawData

Then use the class type that has been provided in the requiredInputDataClass in the payment method to build your raw data. Let’s say that you’re building card data, then you would do the following:

12345678910
// 👇 Create your raw dataconst rawCardData: CardData = {    cardNumber: cardNumber,    expiryDate: expiryDate, // 👈 ex. 03/2030    cvv: cvv,    cardholderName: cardholderName,}
// 👇 Set your raw data on the managerawait rawDataManager.setRawData(rawCardData)
tsx
copy

Each time you set the data on the raw data manager, the manager will validate them and notify you via the callback onValidation. You can then change the state of your submit button based on the validation changes.

You can also listen to the onMetadataChange that will notify you of metadata updates, e.g. that the card type was detected.

Submit the form's data

Once the data has been validated, call the manager's submit() function.

1234
const handleSubmit = async () => {    // 👇 Submit the data that have been set in prior steps    await rawDataManager.submit()}
tsx
copy

That’s it! When the function submit() is called, the SDK will attempt to create a payment.

The payment’s data will be returned on the onCheckoutComplete configured in Step 1.

Handle errors

Use the onError callback of the HeadlessUniversalCheckout options to handle any errors emitted by the SDK during the checkout flow.

1234567
const onError = (    error: PrimerError,    checkoutData: PrimerCheckoutData | null,    handler: PrimerErrorHandler | undefined,) => {    // Handle the error}
typescript
copy

Advanced Configuration

For more customization on your needs, you can listen to all events posted by Headless Universal Checkout and react to them. Visit the API reference for a full list of the listener functions.

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:
FieldDescription
  1. customer
The customer's email address
  1. customer
The customer's billing address

Learn more about how to configure 3DS!

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. Payment list