Overview

With version 2 of our Web SDK, Universal Checkout automatically creates and handles Payments by default. This greatly reduces the complexity and amount of boilerplate required to integrate Primer.

For backward compatibility reasons, it is still possible to manually create and resume payments. Follow this guide to setup Universal Checkout so that you handle the payment lifecycle.

Flow

flow

  1. 1
    Generate a clientToken on your backend by creating a Client Session with POST/client-session
  2. 2
    Initialize Universal Checkout with the clientToken to render the UI.
  3. 3
    Universal Checkout will generate a paymentMethodToken when the customer submits their payment data, or when they select particular payment methods.
  4. 4
    Create a payment using the paymentMethodToken via the Payments API POST/payments
  5. 5
    If the response indicates a requiredAction, you'll get a new clientToken.
  6. 6
    Pass the clientToken back to Universal Checkout to render next steps, like 3DS, and get a resumeToken.
  7. 7
    Call POST/payments/{id}/resume with the resumeToken to resume the payment and wrap things up. (If a new requiredAction is returned, you'll have to go back to step 5.)

Generate a Client Token

Get an API Key

You require an API Key to talk with our APIs. Head to the Developers area to manage your API keys.

When your account is created, we will also create an API token automatically. You can use this API key to get started.

Only client_tokens:write is required as the scope of the key.

Never share your API Key, only your backend should have access to it.

Find out more about API Keys in our API Reference

Generate a Client Session

Client token

A client session is the starting point for integrating payments at Primer. You can attach all the metadata associated with the order to the client session, and generate a clientToken, a temporary key used to initialize Universal Checkout.

The information you include in the client session is used in the Dashboard to conditionally route payments with Workflows, and activate payment methods and other features in Universal Checkout, so pass as much information as you can.

The X-Api-Version specifies the API version information. Earlier, this was supposed to be a date. For example, 2021-10-19.

This has changed post API version v2 which was represented by 2021-09-27 date.

Starting API version v2.1, the X-Api-Version needs to provide the API version as 2.1.

Depending upon the API version specified in the client-session request, your client-session will be processed accordingly with requisite features and options that are available for that version.

See API Reference Changelog for details.

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

POST/client-session
12345678910111213
# Generate a client token with cURLcurl --location --request \  POST 'https://api.sandbox.primer.io/client-session' \  --header 'X-Api-Key: <YOUR_API_KEY>' \  --header 'X-Api-Version: 2021-10-19' \  --header 'Content-Type: application/json' \  --data '{    "orderId": "<YOUR_ORDER_ID>",    "currencyCode": "GBP",    "amount": 1200,    "customerId": "<YOUR_CUSTOMER_ID>"    "order": { "countryCode": "GB" }  }'
curl
copy

Example Response

12345678910
{  "clientToken": "<THE_CLIENT_TOKEN>",  "clientTokenExpirationDate": "2021-08-12T16:14:08.578695",  "orderId": "<YOUR_ORDER_ID>",  "currencyCode": "GBP",  "amount": 1200,  "customerId": "<YOUR_CUSTOMER_ID>",  "metadata": {},  "warnings": []}
JSON
copy
ℹ️
Make sure to pass all the information required by the payment methods and features activated on your Dashboard.

As a rule of thumb, pass as much information as you can when creating the client session. As a minimum, make sure to pass:

  • orderId
  • currencyCode
  • amount
  • order.countryCode

The clientToken is a key concept within Primer. You may receive a client token from various places but as long as you pass it to the SDK, Universal Checkout knows where to start/resume the flow.

Set up Universal Checkout

Step 1. Turn off automatic payment creation

The Universal Checkout option paymentHandling defines how the SDK should handle payment creation.

Set paymentHandling to MANUAL to turn off automatic payment handling. This will allow you to create the payment yourself (via your backend).

This disables the callback onCheckoutComplete.

1234567891011121314151617181920212223
import {    Primer,    PrimerSettings,    PrimerCheckoutData,    PrimerTokenizationHandler,    PrimerResumeHandler,    PrimerPaymentMethodTokenData,} from '@primer-io/react-native'
const CheckoutScreen = (props: any) => {    const onUniversalCheckoutButtonTapped = async () => {        try {            const settings: PrimerSettings = {                paymentHandling: 'MANUAL',                /* Other options and callbacks */            }
            await Primer.configure(settings)        } catch (err) {            // Handle error        }    }}
typescript
copy

See our SDK API Reference for more info on available settings.

Step 2. Handle callbacks for creating and resuming payments

There are two required callbacks:

  • onTokenizeSuccess to create payments with paymentMethodToken
  • onResumeSuccess to resume payments with resumeToken
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647
const CheckoutScreen = (props: any) => {
  const onTokenizeSuccess: async (    paymentMethodTokenData: PrimerPaymentMethodTokenData,    handler: PrimerTokenizationHandler  ) => {    // Will be fired when the payment method has been tokenized.
    // See implementation details below  }
  const onResumeSuccess: async (    resumeToken: string,    handler: PrimerResumeHandler  ) => {    // Will be fired if the SDK is resumed after handling an action    // like 3DS or a redirect-based payment method.
    // See implementation details below  }
  const onError = (    error: PrimerError,    checkoutData: PrimerCheckoutData | null,    handler: PrimerErrorHandler | undefined  ) => {    // Handle any errors.    // checkoutData might be null if the payment creation flow hasn't reached the payment step.  }
  const onUniversalCheckoutButtonTapped = async () => {    try {      const settings: PrimerSettings = {        paymentHandling: "MANUAL",        /* Other options... */        onTokenizeSuccess,        onResumeSuccess,        onError,      }
      await Primer.configure(settings);
    } catch (err) {      // Handle error    }  };}
typescript
copy

See our SDK API Reference for the full list of callbacks.

Handle onTokenizeSuccess() callback

  • When a customer submits their payment data, the payment details are tokenized and you’ll receive a PrimerPaymentMethodTokenData object in onTokenizeSuccess()
  • Create a payment request with the token of the PrimerPaymentMethodTokenData received.
  • If the payment is successful, call handler.handleSuccess() in order to display a success screen.
  • If the payment is unsuccessful, call handler.handleFailure(errorMessage) to display an error / failed message.
  • Payments API may return a new clientToken for additional steps (in the requiredActions on the response). In this case, call handler.continueWithNewClientToken(clientToken) to the checkout.

See below for an example integration:

1234567891011121314151617181920212223242526272829
const onTokenizeSuccess: async (        paymentMethodTokenData: PrimerPaymentMethodTokenData,      handler: PrimerTokenizationHandler    ) => {            // Make an API call to your backend to create a payment.            try {                const paymentResponse = await createPayment(paymentMethodTokenData.token);
                // If the request failed you can cancel the flow and display an error message        if (!paymentResponse) {            handler.handleFailure('The payment failed. Please try with another payment method.');                        return;        }
                // If the payment has any actions for the SDK to complete                if (paymentResponse.requiredAction?.clientToken) {                    this.paymentId = paymentResponse.id;  // This will be used for resuming later                    handler.continueWithNewClientToken(paymentResponse.requiredAction.clientToken);                    return;                }
                // Display the success screen        handler.handleSuccess();                return;
            } catch (error) {                // Handle error            }    }
typescript
copy

Handle onResumeSuccess() callback

🚨

Handling onResumeSuccess() is required to fully support 3DS and the majority of payment methods.

  • You will receive a resumeToken via the onResumeSuccess() callback if applicable
  • Send a resume payment request with the resumeToken
  • If the payment is successful, call handler.handleSuccess() in order to display a success screen.
  • If the payment is unsuccessful, call handler.handleFailure(errorMessage) to display an error / failed message.
  • Payments API may again return a new clientToken for additional steps. In this case, call handler.continueWithNewClientToken(clientToken) to the checkout.
12345678910111213141516171819202122232425262728
const onResumeSuccess: async (      resumeToken: string,      handler: PrimerResumeHandler    ) => {            // Make an API call to resume the payment.            try {                const resumePaymentResponse = await resumePayment(this.paymentId, resumeToken);
                // If the request failed you can cancel the flow and display an error message        if (!resumePaymentResponse) {            handler.handleFailure('The payment failed. Please try with another payment method.');                        return;        }
        // The checkout will automatically perform the action required by the Workflow        if (resumePaymentResponse.requiredAction?.clientToken) {            handler.continueWithNewClientToken(resumePaymentResponse.requiredAction.clientToken);                        return;        }
        // Display the success screen        handler.handleSuccess();                return;
            } catch (error) {                // Handle error            }    }
typescript
copy

Step 3. Generate a client token

Make an API call to your backend to generate a Client Session. The Client Session request returns a client token, which you will need to initialize the checkout.

Here is an example of how it can be done from your component. Once successful, store your client token for future use.

12345678910111213
const CheckoutScreen = (props: any) => {    // ...
    const onUniversalCheckoutButtonTapped = async () => {        try {            // ...            // Ask your backend to create a client session            const clientToken = await createClientSession()        } catch (err) {            // Handle error        }    }}
typescript
copy

Step 4. Show Universal Checkout

At this point, you should have a client token available. To present Universal Checkout, call showUniversalCheckout(clientToken) as shown below.

1234567891011121314
const CheckoutScreen = async (props: any) => {    // ...
    const onUniversalCheckoutButtonTapped = async () => {        // ...
        try {            // Present Universal Checkout            await Primer.showUniversalCheckout(clientToken)        } catch (err) {            // Handle error        }    }}
typescript
copy
🚀

You should now be able to see Universal Checkout, and the user can now interact with it. When the user tries to pay, we will tokenize the payment method and invoke onTokenizeSuccess. You can then create the payment as mentioned in Step 4.

Full Snippet

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
import {        Primer,        PrimerSettings,        PrimerCheckoutData,        PrimerTokenizationHandler,        PrimerResumeHandler,        PrimerPaymentMethodTokenData} from '@primer-io/react-native'
const CheckoutScreen = (props: any) => {
    const onTokenizeSuccess: async (        paymentMethodTokenData: PrimerPaymentMethodTokenData,      handler: PrimerTokenizationHandler    ) => {            try {                const paymentResponse = await createPayment(paymentMethodTokenData.token);
        if (!paymentResponse) {            handler.handleFailure('The payment failed. Please try with another payment method.');                        return;        }
                if (paymentResponse.requiredAction?.clientToken) {                    this.paymentId = paymentResponse.id;  // This will be used for resuming later                    handler.continueWithNewClientToken(paymentResponse.requiredAction.clientToken);                    return;                }
        handler.handleSuccess();                return;
            } catch (error) {                // Handle error            }    }
    const onResumeSuccess: async (      resumeToken: string,      handler: PrimerResumeHandler    ) => {            try {                const resumePaymentResponse = await resumePayment(this.paymentId, resumeToken);
        if (!resumePaymentResponse) {            handler.handleFailure('The payment failed. Please try with another payment method.');                        return;        }
        if (resumePaymentResponse.requiredAction?.clientToken) {            handler.continueWithNewClientToken(resumePaymentResponse.requiredAction.clientToken);                        return;        }
        handler.handleSuccess();                return;
            } catch (error) {                // Handle error            }    }
    const onError = (    error: PrimerError,    checkoutData: PrimerCheckoutData | null,    handler: PrimerErrorHandler | undefined  ) => {            // Handle the error    }
  const onUniversalCheckoutButtonTapped = async () => {    try {      const settings: PrimerSettings = {        paymentHandling: "MANUAL",        /* Other options... */        onTokenizeSuccess,        onResumeSuccess,        onError      }
      await Primer.configure(settings);
      const clientToken = await createClientSession();
      await Primer.showUniversalCheckout(clientToken)    } catch (err) {      // Handle error    }  };}
typescript
copy