Headless Universal Checkout is currently in beta for iOS and Android.
Where there is a need for more customization and control over the checkout experience, a headless version of Primer’s Universal Checkout is available.
Universal Checkout can be used with your own UI, giving you more flexibility and allowing you to move quicker when making design changes, while still having Universal Checkout capture sensitive PCI card data.
As with all versions of Universal Checkout, any payment method can be added and configured through Dashboard, meaning Primer handles the logic of when to display each method.
For more details about SDK versions, please see our changelog.
Step 2. Prepare the Checkout Listener
12345678910111213141516171819202122232425
class CheckoutActivity :AppCompatActivity(){privateval listener =object: PrimerHeadlessUniversalCheckoutListener {overridefunonClientSessionSetupSuccessfully(paymentMethods: List<PrimerHeadlessUniversalCheckoutPaymentMethod>){/* TODO */}overridefunonTokenizationSuccess( paymentMethodToken: PaymentMethodToken, resumeHandler: ResumeHandler){/* TODO */}overridefunonResumeSuccess(resumeToken: String, resumeHandler: ResumeHandler){/* TODO */}overridefunonError(error: APIError){/* TODO */}// handle other events}}
kotlin
copy
Step 3. Generate a Client Token
Check our guide on how to set up the client session here
❗️
Remember that based on your client token different payment methods will show up.
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 hereprivatelateinitvar viewModel: CheckoutViewModeloverridefunonCreate(savedInstanceState: Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)setupViewModel()setupObservers()fetchClientToken()}privatefunsetupViewModel(){ viewModel =ViewModelProvider(this).get(CheckoutViewModel::class.java)}// prepare the clientprivatefunfetchClientToken()= viewModel.fetchClientToken()privatefunsetupObservers(){ viewModel.clientToken.observe(this){ clientToken ->// show checkout}}}
kotlin
copy
Your view model code may look something like this:
12345678910
class CheckoutViewModel :ViewModel(){privateval _clientToken= MutableLiveData<String>()val clientToken: LiveData<String>= _clientTokenfunfetchClientToken(){// fetch your token here (ask your backend to provide token) _clientToken.postValue("retrieved_token")}}
kotlin
copy
Step 4. Start Headless Universal Checkout
Once you have a client token, you can initialize Primer’s Headless Universal Checkout like this:
12345678910111213141516171819202122
class CheckoutActivity :AppCompatActivity(){// other code goes hereprivateval listener =object: PrimerHeadlessUniversalCheckoutListener {// ...}privatefunsetupObservers(){ viewModel.clientToken.observe(this){ clientToken ->startHeadlessUniversalCheckout(clientToken)}}privatefunstartHeadlessUniversalCheckout(clientToken: String){ PrimerHeadlessUniversalCheckout.current.start(this, clientToken,PrimerConfig(), listener)}}
kotlin
copy
Once Primer’s Headless Universal Checkout is configured, its listener functions will notify you about Headless Universal Checkout events.
Additionally, you can pass the listener as an argument in the start function, or set it with PrimerHeadlessUniversalCheckout.current.setListener(listener)
Step 5: Show (Alternative) Payment Methods
When the checkout is done initializing, the onClientSessionSetupSuccessfully method in the listener will be invoked.
Use this event to show the list of payment methods to the user. You can add your buttons for them, or use Primer SDK’s buttons.
privateval listener =object: PrimerHeadlessUniversalCheckoutListener {overridefunonClientSessionSetupSuccessfully( paymentMethods: List<PrimerHeadlessUniversalCheckoutPaymentMethod>){ paymentMethods.forEach{setupView(it)}}}// your custom method to render list of payment method buttons UI.privatefunsetupView(paymentMethod: PrimerHeadlessUniversalCheckoutPaymentMethod){val headlessUniversalCheckout = PrimerHeadlessUniversalCheckout.currentval containerView = findViewById<ViewGroup>(R.id.container)val view = PrimerHeadlessUniversalCheckout.current.makeView(paymentMethod.paymentMethodType) view?.apply{ containerView.addView(this) setOnClickListener {// for card, render your own UI, see more details below.if(paymentMethod.paymentMethodType == PrimerPaymentMethodType.PAYMENT_CARD){createForm()}// for all payment methods headlessUniversalCheckout.showPaymentMethod(this.context, paymentMethod.paymentMethodType)}}}
kotlin
copy
Payment methods are added and configured through Dashboard. Only payment methods whose conditions match the current client session will be returned.
Handle onPaymentMethodShowed Callback (Optional)
This function will notify you that the payment method you requested to show (e.g. Google Pay) has been showed.
Headless Universal Checkout securely captures payment method data while fully embedded in your app. By communicating directly with Primer's PCI-L1 tokenization service, Headless Universal Checkout transforms sensitive customer data into a secure uniform string called a payment method token.
If the user wants to pay with card you should avail a card form using the following three steps.
Render Input Elements
First, use the PrimerCardManager object and render the card form input elements like this:
1234567891011121314151617181920
class CheckoutActivity :AppCompatActivity(){privateval cardManager by lazy { PrimerCardManager.newInstance()}privatefuncreateForm(){val elementTypes = cardManager.getRequiredInputElementTypes().orEmpty()val inputElements = elementTypes.map{ type -> PrimerEditTextFactory.createFromType(this, type).apply{setPrimerInputElementListener(inputElementListener)}}val cardFormView = findViewById<ViewGroup>(R.id.cardForm) inputElements.forEach{ cardFormView.addView(it)} cardManager.setInputElements(inputElements)}}
kotlin
copy
The returned elements are of EditText type so you can do any type of customization on them. Additionally you can include them in the xml files if you need it.
Subscribe to Validation Listener
Then, listen to the card form validation state and enable your form’s submit button accordingly:
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 please implement the following listener interface and pass it to the input field:
12345678910111213141516
val inputListener =object: PrimerInputElementListener {overridefuninputElementValueChanged(inputElement: PrimerInputElement){/* TODO */}overridefuninputElementValueIsValid(inputElement: PrimerInputElement, isValid: Boolean){/* TODO */}overridefuninputElementDidDetectCardType(type: CardType.Type){/* TODO */}}cardNumberField.setListener(inputLister)
kotlin
copy
Configure Submit Button
Finally, configure the on click listener of the submit button:
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. It may be useful in case you want to show a loader or notify user about progress in any other way.
This function will notify you that the tokenization API call has been fired. It may be useful in case you want to show a loader or notify user about progress in any other way.
The HUC supports 3DS out of the box, but some configuration is needed to be able to use it.
Installation Guide
3D Secure on Android requires the addition of the io.primer:3ds-android library to your project. This library is currently held on a Primer’s artifactory.
❗️
If the library is not setup, 3DS will fail. In the case of a 3DS triggered by the workflow, the resumeError event will be fired.
1
Add the URL to our artifactory to your gradle.properties
Finally, amend the dependencies section of your app's build.gradle to include the 3ds-android library
12345
dependencies { /* Other dependencies... */ implementation "io.primer:3ds-android:1.1.0"}
gradle
copy
5
Use the following card number to trigger a 3DS challenge on the Android SDK with the workflow: 9120 0000 0000 0006
Step 7. Create Payment
Once the payment method data has been securely captured, Primer will return a uniform paymentMethodToken via onTokenizationSuccess that can be safely passed to your server to create a payment with the Payments API.
class CheckoutViewModel :ViewModel(){//..funsendPaymentMethodToken(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()}elseif(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
If the above callback responds with a required action, the SDK flow will need to resume (e.g. to complete a 3DS authentication for a card payment). You will need to implement the onResumeSuccess callback to once again call the payment API once this required action has completed.
class CheckoutViewModel :ViewModel(){//...funsendResumeToken(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()}elseif(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 Errors
Use the onError method to handle any errors emitted by the SDK during the checkout flow.
1234567
privateval listener =object: PrimerHeadlessUniversalCheckoutListener {overridefunonError(error: APIError){// your custom method to show an error viewshowErrorView(error)}}