Universal Checkout


The first fully dynamic checkout

Universal Checkout is a drop-in UI for web and mobile, with a no-code interface for product and payments teams. Now payments can be a first-class product area in your organization.

uc styling

🛒

Accessible and responsive UX that presents the payment methods you choose, along with 3D Secure 2.0 and convenient checkout modules, fully in-context with no redirects, ever!

🎛️

Use the Dashboard to activate a custom set of payment methods and checkout modules based on amount, currency, customer location and more.

🎨

Completely customizable, embedded UI for seamless user experience on your site or in your app

🔐

PCI Level 1 compliant with a centralized vault for recurring payments and one-click checkout

🎉

Low code integration that you won't have to touch when adding new payment methods, processors or other Connections

🚀

Fully dynamic checkout driven by Workflows that let you fully automate end-to-end payment flows for the first time

Payment method tokenization

Universal Checkout securely captures payment method data while fully embedded on your site or 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.

You can safely pass this token to your backend to create payments with the Payments API, with no compliance risk.



payment instrument

🚀

Our agnostic tokenization service and centralized vault enable you to handle recurring payments, fallbacks and retries across processors without compromising UX. No more PSP-specific tokens — now you own your payments data.


So, how does it work?

flow

Whether you're embedding in a website or a mobile app:

  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
    As the user interacts with the checkout, Universal Checkout will propose to update the Client Session using Client Session actions. Forward them to your backend to update your Client Session with POST/client-session/actions
  4. 4
    Universal Checkout will generate a paymentMethodToken when the customer submits their payment data.
  5. 5
    Create a payment using the paymentMethodToken via the Payments API POST/payments
  6. 6
    If the response indicates a requiredAction, you'll get a new clientToken.
  7. 7
    Pass the clientToken back to Universal Checkout to render next steps, like 3DS, and get a resumeToken.
  8. 8
    Call POST/payments/{paymentId}/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.)
🚨

Universal Checkout can dynamically handle front-end payment flows constructed in Workflows such as 3D Secure, KYC and more, enabling your payments team to craft new commerce experiences with no additional code.


Manage your checkout with no code

The Dashboard features a no-code interface enabling you to simply drag-and-drop payment methods and checkout modules and create conditions that determine when and how they are displayed on Universal Checkout.

checkout builder


Generate a client token to initialize the SDK

The clientToken is a temporary key used to initialize Universal Checkout, and render the checkout options you've configured with the Dashboard.

Generate the first client token by creating a Client Session with POST/client-session.

If a payment method or checkout module requires additional information passed in the Client Session this will be documented in the Connection setup in the Dashboard.

client token

Client session request POST/client-session

12345678910111213141516
curl --location --request \ POST 'https://api.sandbox.primer.io/client-session' \ --header 'X-Api-Key: <YOUR_API_KEY>' \ --header 'X-Api-Version: 2021-10-19' \  # These fields can be passed in the payment request as well --data '{    "orderId": "<YOUR_ORDER_ID>",    "currencyCode": "GBP",    "amount": 1200,     # a customerId is required to reuse successfully authorized payment method data for your customer    # we recommend you always pass this in if you intend to use vaulting    "customerId": "<YOUR_CUSTOMER_ID>"    "metadata": { }, }'
curl
copy

🚨

Data you pass when requesting a clientToken is persisted in the payment request unless explicitly overwritten.

Client token response

Check the warnings array for missing data which may be required to display certain payment methods and checkout modules in the Universal Checkout.

123456789101112
{  "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": [    "Apple Pay is missing 'customerDetails.countryCode'"  ]}
JSON
copy

Implement Universal Checkout on front-end

Step 1. Install the SDK

Add the following to your app/build.gradle file:

1234567
repositories {  mavenCentral()} dependencies {  implementation 'io.primer:android:latest.version'}
kotlin
copy

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

Step 2. Prepare Universal Checkout's event listener

Prepare the UniversalCheckout.EventListener that will handle the events that happen during the lifecycle:

12345678910111213141516171819202122232425262728
class CheckoutActivity : AppCompatActivity() {   private val listener = object : CheckoutEventListener {     override fun onCheckoutEvent(e: CheckoutEvent) {      when (e) {        is CheckoutEvent.TokenizationSuccess -> {          /* TODO */        }        is CheckoutEvent.ResumeSuccess -> {          /* TODO */        }        else -> // handle other events      }    }  }   override fun onCreate(savedInstanceState: Bundle?) {    super.onCreate(savedInstanceState)     configureCheckout()  }   private fun configureCheckout() {    // with default settings    Primer.instance.configure(listener = listener)  }}
kotlin
copy

Step 3. Prepare the Client Token

Make an API call to your backend to fetch a Client Token. Here is a simple example of how it can be done:

12345678910111213141516171819202122232425262728
class CheckoutActivity : AppCompatActivity() {   // other code goes here  private lateinit var viewModel: CheckoutViewModel   override fun onCreate(savedInstanceState: Bundle?) {    super.onCreate(savedInstanceState)    setContentView(R.layout.activity_main)     setupViewModel()    setupObservers()    fetchClientToken()  }    private fun setupViewModel() {    viewModel = ViewModelProvider(this).get(CheckoutViewModel::class.java)  }   // prepare the client (docs here)  private fun fetchClientToken() = viewModel.fetchClientToken()   private fun setupObservers() {    viewModel.clientToken.observe(this) { clientToken ->      // show checkout    }  }}
kotlin
copy
12345678910
class CheckoutViewModel : ViewModel() {     private val _clientToken= MutableLiveData<String>()    val clientToken: LiveData<String> = _clientToken     fun fetchClientToken() {    // fetch your token here (ask your backend to provide token) add our docs to exlain       _clientToken.postValue("retrieved_token")    }}
kotlin
copy

Step 4. Show Universal Checkout

When the client token is retrieved, show Universal Checkout.

1234567891011121314
class CheckoutActivity : AppCompatActivity() {   // other code goes here   private fun setupObservers() {    viewModel.clientToken.observe(this) { clientToken ->      showUniversalCheckout(clientToken)    }  }   private fun showUniversalCheckout(clientToken: String) {    Primer.instance.showUniversalCheckout(this, clientToken)  }}
kotlin
copy

Step 5. Handle callbacks for creating and resuming payments

Once the payment method data has been securely captured, Primer will return a uniform paymentMethodToken via CheckoutEvent.TokenizationSuccess that can be safely passed to your server to create a payment with the Payments API.

Handle CheckoutEvent.TokenizationSuccess event

1234567891011
private val listener = object : CheckoutEventListener {   override fun onCheckoutEvent(e: CheckoutEvent) {    when (e) {      is CheckoutEvent.TokenizationSuccess -> {        viewModel.sendPaymentMethodToken(e.data, e.resumeHandler)      }      //...    }  }}
kotlin
copy
123456789101112131415161718
class CheckoutViewModel : ViewModel() {   //..  fun sendPaymentMethodToken(token: PaymentMethodToken, handler: ResumeHandler)  {    // Send the payment method token to your server to create a payment (https://primer.io/docs/payments/#one-create-a-payment-request) with `paymentMethod.token`    val paymentResponse =  //...    if (paymentResponse.isSuccessful()) {      // If the payment is successful, call resumeHandler.handleSuccess().      handler.handleSuccess()    } else if (paymentResponse.isPending()) {      // Payments API may return a new `clientToken` for additional steps. In this case, call `resumeHandler.handleNewClientToken(clientToken)`      handler.handleNewClientToken(paymentResponse.requiredAction.clientToken)    } else {      // If the payment is unsuccessful, call `resumeHandler.handleError(error: Error)` to show an error / failed message.      handler.handleError(Error())    }  }}
kotlin
copy

Handle CheckoutEvent.ResumeSuccess event

Once the required actions are completed, Primer will return resumeToken via CheckoutEvent.ResumeSuccess that can be safely passed to your server to resume a payment with the Payments API.

1234567891011
private val listener = object : CheckoutEventListener {   override fun onCheckoutEvent(e: CheckoutEvent) {    when (e) {      is CheckoutEvent.ResumeSuccess -> {        viewModel.sendResumeToken(e.resumeToken, e.resumeHandler)      }      //...    }  }}
kotlin
copy
123456789101112131415161718
class CheckoutViewModel : ViewModel() {   //...  fun sendResumeToken(token: String, handler: ResumeHandler)  {    // Send a resume payment request with resumeToken (https://primer.io/docs/payments/#two-handle-resuming-payment-flows)    val paymentResponse =  //..    if (paymentResponse.isSuccessful()) {      // If the payment is successful, call resumeHandler.handleSuccess().      handler.handleSuccess()    } else if (paymentResponse.isPending()) {      // Payments API may return a new `clientToken` for additional steps. In this case, call `resumeHandler.handleNewClientToken(clientToken)`      handler.handleNewClientToken(paymentResponse.requiredAction.clientToken)    } else {      // If the payment is unsuccessful, call `resumeHandler.handleError(error: Error)` to show an error / failed message.      handler.handleError(Error())    }  }}
kotlin
copy