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
- 1Generate a
clientToken
on your backend by creating a Client Session with POST/client-session
- 2Initialize Universal Checkout with the
clientToken
to render the UI. - 3Universal Checkout will generate a
paymentMethodToken
when the customer submits their payment data, or when they select particular payment methods. - 4Create a payment using the
paymentMethodToken
via the Payments API POST/payments
- 5If the response indicates a
requiredAction
, you'll get a newclientToken
. - 6Pass the
clientToken
back to Universal Checkout to render next steps, like 3DS, and get aresumeToken
. - 7Call POST
/payments/{id}/resume
with theresumeToken
to resume the payment and wrap things up. (If a newrequiredAction
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.
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
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" } }'
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": []}
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
Set up Universal Checkout
Step 1. Install
Add the following to your app/build.gradle
file
1234567
repositories { mavenCentral()} dependencies { implementation 'io.primer:android:latest.version'}
For more details about SDK versions, please see our changelog.
It is highly recommended to add following settings to your app/build.gradle
file:
12345
android { kotlinOptions { freeCompilerArgs += '-Xjvm-default=all' }}
Step 2. Initialize the SDK
Prepare the PrimerCheckoutListener
that will handle the callbacks that happen during the lifecycle.
Import the Primer SDK and set its listener as shown in the following example.
In order to use manual payment handling, you have to set paymentHandling
to **PrimerPaymentHandling.MANUAL**
in the PrimerSettings
.
12345678910111213141516171819202122232425262728293031
class CheckoutActivity : AppCompatActivity() { private val listener = object : PrimerCheckoutListener { override fun onTokenizeSuccess( paymentMethodToken: PrimerPaymentMethodToken, decisionHandler: PrimerResumeDecisionHandler ) { // use the received paymentMethodToken to make the payment // and use decisionHandler to instruct SDK about next steps based on payment status } override fun onResumeSuccess(resumeToken: String, decisionHandler: PrimerResumeDecisionHandler) { // use the received resumeToken to resume the payment // and use decisionHandler to instruct SDK about next steps based on payment status } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) configureCheckout() } private fun configureCheckout() { // Set the paymentHandling to manual val settings = PrimerSettings(paymentHandling = PrimerPaymentHandling.MANUAL) Primer.instance.configure(settings, listener) }}
Check the SDK API here to customize your SDK settings.
Step 3. Generate a client token
For more information on generating a client token, take a look at our client session guide.
Make an API call to your backend to fetch a Client Token. Here is a simple example of how it can be done from your activity:
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) configureCheckout() setupViewModel() setupObservers() fetchClientToken() } private fun setupViewModel() { viewModel = ViewModelProvider(this).get(CheckoutViewModel::class.java) } private fun fetchClientToken() = viewModel.fetchClientToken() private fun setupObservers() { viewModel.clientToken.observe(this) { clientToken -> // show checkout } }}
Your view model code may look something like this:
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) _clientToken.postValue("retrieved_token") }}
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) }}
You should now be able to see Universal Checkout! The user can now interact with Universal Checkout.
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 onTokenizeSuccess
that can be safely passed to your server to create a payment with the Payments API.
Handle onTokenizeSuccess
callback
onTokenizeSuccess
callbackWhen a customer submits their payment data, the payment details are tokenized and you'll receive a
paymentMethodToken
inonTokenizeSuccess
.Create a payment request with the payment method token data.
If the payment is successful, call
decisionHandler.handleSuccess()
in order to display a success screen.If the payment is unsuccessful, call
decisionHandler.handleFailure("Your error message")
to display an error / failed message.Payments API may return a
requiredAction
with a newclientToken
for additional steps. In this case, calldecisionHandler.continueWithNewClientToken(clientToken)
.
123456789
private val listener = object: PrimerCheckoutListener { override fun onTokenizeSuccess( paymentMethodToken: PrimerPaymentMethodToken, decisionHandler: PrimerResumeDecisionHandler ) { viewModel.sendPaymentMethodToken(paymentMethodToken, decisionHandler) }}
1234567891011121314
class CheckoutViewModel : ViewModel() { // ⚠️ remember to call decisionHandler to resume SDK flow. fun sendPaymentMethodToken(paymentMethodToken: PrimerPaymentMethodToken, decisionHandler: PrimerResumeDecisionHandler) { val paymentResponse = //... if (paymentResponse.isSuccessful()) { decisionHandler.handleSuccess() } else if (paymentResponse.isPending()) { decisionHandler.continueWithNewClientToken(paymentResponse.requiredAction.clientToken) } else { decisionHandler.handleFailure("Your error message.") } }}
Handle onResumeSuccess
callback
onResumeSuccess
callbackOnce the required actions are completed, Primer will return resumeToken
via onResumeSuccess
that can be safely passed to your server to resume a payment with the Payments API.
You will receive a
resumeToken
via theonResumeSuccess()
callback if applicable.Send a resume payment request with
resumeToken
If the payment is successful, call
decisionHandler.handleSuccess()
in order to display a success screen.If the payment is unsuccessful, call
decisionHandler.handleFailure("Your error message")
to display an error / failed message.Payments API may return a
requiredAction
with a newclientToken
for additional steps. In this case, calldecisionHandler.continueWithNewClientToken(clientToken)
.
123456
private val listener = object : PrimerCheckoutListener { override fun onResume(resumeToken: String, decisionHandler: PrimerResumeDecisionHandler) { viewModel.sendResumeToken(resumeToken, decisionHandler) } }
1234567891011121314
class CheckoutViewModel : ViewModel() { // ⚠️ remember to call decisionHandler to resume SDK flow. fun sendResumeToken(resumeToken: String, decisionHandler: PrimerResumeDecisionHandler) { val paymentResponse = //... if (paymentResponse.isSuccessful()) { decisionHandler.handleSuccess() } else if (paymentResponse.isPending()) { decisionHandler.continueWithNewClientToken(paymentResponse.requiredAction.clientToken) } else { decisionHandler.handleFailure("Your error message.") } }}