Before you begin
This guide assumes that you know how to:Accept payments with ACH via Stripe
Prepare the client session
ACH via Stripe requires the following data to process a payment successfully. Pass the following data in the client session, or in the payment request (for manual payment creation).| Parameter Name | Required | Description |
|---|---|---|
| currencyCode | ✓ | 3-letter currency code in ISO 4217 format. For Stripe ACH only USD is supported. |
| order ↳ lineItems | ✓ | Details of the line items of the order |
| metadata | ✓ | In order to have Stripe ACH successful, you need to pass your device info: IP address and user agent in the metadata.metadata.deviceInfo.ipAddress and metadata.deviceInfo.userAgent |
| paymentMethod | ✓ |
JSX
Copy
Ask AI
"paymentMethod": {
"vaultOnAgreement": true,
"paymentType": "FIRST_PAYMENT"
}
Prepare the SDK for payments
- Web
- iOS
- Android
- React Native
Handle payment method
Spirit of Cadeau requires instance of Redirect Manager in order to be presented to the user:TypeScript
Copy
Ask AI
const manager = await headless.createPaymentMethodManager('THUNES_SPIRIT_OF_CADEAU')
STRIPE_ACH must be returned as PaymentMethod.type in the onAvailablePaymentMethodsLoad callback.When the user has selected the payment method, call the start function. This function automatically:- opens a popup to present a loading indicator, then the payment method web page
- shows an overlay on top of your page that prompts the user to focus on the popup
TypeScript
Copy
Ask AI
myButton.addEventListener('click', () => {
manager.start()
})
Pre-requisites
Integration
PrimerHeadlessUniversalCheckout.AchManager is an SDK manager designed to streamline the headless integration with ACH providers like Stripe. You will be using it alongside PrimerHeadlessUniversalCheckoutDelegate to handle ACH via Stripe payments in your project.To integrate ACH via Stripe in your project, follow these steps:Pass device info when creating the client session
ACH via Stripe requires that a mandate is accepted before the payment is completed.Mandates that are accepted online require IP address and user-agent of the device that the payment is made from.Specify this information when creating the client session, otherwise payments will remain in Pending state.JSON
Copy
Ask AI
{
...
customer: {
emailAddress: '...',
...
},
"metadata": {
"deviceInfo": {
"ipAddress": "127.0.0.1", // IP address of the device accepting the mandate
"userAgent": "Mozilla/5.0" // user-agent of the device accepting the mandate
}
},
...
}
Define the Primer Stripe dependency
In order to be able to use ACH via Stripe you will have to install PrimerStripeSDK which is available via CocoaPods.RUBY
Copy
Ask AI
use_frameworks!
target 'PrimerSDK_Example' do
pod 'PrimerSDK'
# Add the line below 👇
pod 'PrimerStripeSDK'
end
When starting the headless universal checkout, it is important that you specify your Stripe publishable key when instantiating
PrimerSettings, otherwise you’ll get validation errors when interacting with the payment manager.SWIFT
Copy
Ask AI
let settings = PrimerSettings(
// ...
paymentMethodOptions: PrimerPaymentMethodOptions(
stripeOptions: PrimerStripeOptions(publishableKey: "your_stripe_publishable_key"),
)
// ...
)
Create a new instance of StripeAchUserDetailsComponent
ACH via Stripe requires specific user details for payment handling. It is expected that you have a screen that allows entry of these details. In the context of this screen you can fetch an instance of StripeAchUserDetailsComponent via PrimerHeadlessUniversalCheckout.AchManager that can be assigned the details that the user enters.Although the component collects user details pertaining to ACH, it also handles tokenization and payment processes. Because of this, it should be kept in memory until the checkout is completed.Ensure that the screen that owns this component is not dismissed until the payment is finished, as the component returned by the manager is optional. As a result, having a weak reference to it will stop the entire payment flow if the screen is dismissed prematurely.
SWIFT
Copy
Ask AI
do {
manager = PrimerHeadlessUniversalCheckout.AchManager()
stripeAchComponent = try manager.provide(paymentMethodType: "STRIPE_ACH")
} catch {
// Catch errors here
}
Listen for the emitted data
Conform to the stepDelegate in order to be notified about the state of the payment process:SWIFT
Copy
Ask AI
func didReceiveStep(step: PrimerHeadlessStep) {
guard let step = step as? ACHUserDetailsStep else { return }
switch step {
// Emitted once the component is started via a call to the `start()` function.
case .retrievedUserDetails(let userDetails):
// Holds user details passed during the client session creation process.
// These details serve as a basis for prefilling user input fields in your app's UI,
// offering users the convenience of reviewing and potentially modifying their information as needed.
// Emitted once the component is submitted via the `submit()` method call,
// signaling that the user detail collection has completed.
case .didCollectUserDetails:
// From this point on you may dismiss the user details collection screen.
}
}
Listen to the data validation statuses
This component allows you to collect and validate details such as the user’s first name, last name and email address.It’s worth mentioning that the data validation is designed to operate in real-time. This means that the accuracy and completeness of the collected data can be confirmed even while users are actively inputting their information. This real-time validation feature ensures that your data remains accurate and complete throughout the data entry process, enhancing the user experience and the reliability of the collected data.Conform to the validationDelegate to handle any validation statuses that may occur.SWIFT
Copy
Ask AI
func didUpdate(validationStatus: PrimerValidationStatus, for data: PrimerCollectableData?) {
guard let data = data as? ACHUserDetailsCollectableData else { return }
switch validationStatus {
case .validating:
// Handle validation being in progress (e.g. show progress view)
case .valid:
// valid (e.g. enable component submission button, submit data)
case .invalid(errors: let errors):
// invalid (e.g. disable component submission button, show validation error messages)
case .error(error: let error):
// and error status (e.g. display error messages to the user)
}
}
Start the flow of the component
Call the start function in order to start the flow of the component:SWIFT
Copy
Ask AI
stripeAchComponent.start()
retrievedUserDetails step will be emitted. As mentioned previously, the retrievedUserDetails step contains user details previously passed during the client session creation process. Use these details to prefill the fields on your user detail collection screen, offering users the convenience of reviewing and potentially modifying their information as needed.Handle user data collection
As mentioned previously, the component allows you to collect the user’s first name, last name and email address. Note that all these details are supposed to be set before submitting the component via the submit function.SWIFT
Copy
Ask AI
let firstNameCollectedData = ACHUserDetailsCollectableData.firstName(firstName)
stripeAchComponent.updateCollectedData(collectableData: firstNameCollectedData)
let lastNameCollectedData = ACHUserDetailsCollectableData.lastName(lastName)
stripeAchComponent.updateCollectedData(collectableData: lastNameCollectedData)
let emailCollectedData = ACHUserDetailsCollectableData.emailAddress(emailAddress)
stripeAchComponent.updateCollectedData(collectableData: emailCollectedData)
SWIFT
Copy
Ask AI
stripeAchComponent.submit()
didCollectUserDetails step, after which you should transition away from the user details collection screen.Handle primerHeadlessUniversalCheckoutDidReceiveAdditionalInfo calls
Once the user data collection component emits the didCollectUserDetails step, you are expected to handle calls to primerHeadlessUniversalCheckoutDidReceiveAdditionalInfo in your PrimerHeadlessUniversalCheckoutDelegate implementation in order to continue with the payment process. Doing so will allow you to submit the mandate authorization, a process required for all Stripe ACH payments.SWIFT
Copy
Ask AI
func primerHeadlessUniversalCheckoutDidReceiveAdditionalInfo(_ additionalInfo: PrimerCheckoutAdditionalInfo?) {
if let stripeCollector = additionalInfo as? ACHBankAccountCollectorAdditionalInfo {
// Emitted when starting the Stripe bank collection flow, which opens a UIViewController from the Stripe SDK
present(stripeCollector.collectorViewController, animated: true)
} else {
// Emitted either when a bank is chosen through the Stripe bank collection process.
// ACH via Stripe requires that a mandate is displayed before the payment is completed.
// Here you can show the mandate acceptance alert, with the two options: Accept or Decline.
showAlert(title: "Mandate title", message: "Mandate description text") {
// Accepting the mandate will lead to a call to complete the payment.
self.manager.mandateDelegate?.mandateAccepted()
} cancelHandler: {
// Declining the mandate will cancel the payment flow, with a "payment-cancelled" error.
self.manager.mandateDelegate?.mandateDeclined()
}
}
}
PrimerHeadlessUniversalCheckoutDelegate protocol.Handle component errors
You can conform to the errorDelegate to handle any errors that may occur during user details collection or tokenization:SWIFT
Copy
Ask AI
func didReceiveError(error: PrimerError) {
// Handle errors (e.g., display error messages to the user)
}
errorDelegate is a protocol that emits PrimerError objects, allowing you to identify and respond to any errors that occur during the payment process.Vaulting
ACH via Stripe payment tokens can be vaulted automatically on successful agreement if the vaultOnAgreement flag is turned on when creating the client session. Note that the vaultOnAgreement flag is supported starting with API version 2.4.JSON
Copy
Ask AI
{
...
customer: {
emailAddress: '...',
...
},
"metadata": {
"deviceInfo": {
"ipAddress": "127.0.0.1", // IP address of the device accepting the mandate
"userAgent": "Mozilla/5.0" // user-agent of the device accepting the mandate
}
},
"paymentMethod": {
"paymentType": "FIRST_PAYMENT",
"vaultOnAgreement": true
...
}
...
}
bankName and accountNumberLast4Digits from returned the paymentInstrumentData to render your saved payment method.Making payments with vaulted ACH via Stripe
User details are stored alongside payment information in the vault so the usage of StripeAchUserDetailsComponent described earlier does not apply when utilizing Stripe ACH via Vault.To allow payments with a vaulted payment token, use the Vault Manager as usual (fetch the payment methods then perform the payment).Handle payment method
ACH via Stripe requires a Native UI Manager in order to be presented to the user:SWIFT
Copy
Ask AI
// 👇 Create the payment method manager
let nativeUIPaymentMethodManager = try PrimerHeadlessUniversalCheckout.NativeUIManager(paymentMethodType: "STRIPE_ACH")
// 👇 Show the payment method
try nativeUIPaymentMethodManager.showPaymentMethod(intent: .checkout)
Handle payment method
PrimerHeadlessUniversalCheckoutAchManager is an SDK manager designed to streamline the headless integration with ACH providers like Stripe. You will be using it alongside PrimerHeadlessUniversalCheckoutListener to handle ACH via Stripe payments in your project.To integrate ACH via Stripe in your project, follow these steps:Pass device info and payment type when creating the client session
ACH via Stripe requires that a mandate is accepted before the payment is completed. Mandates that are accepted online require IP address and user-agent of the device that the payment is made from.Specify this information when creating the client session, otherwise payments will remain in Pending state.JSON
Copy
Ask AI
{
...
customer: {
emailAddress: '...',
...
},
"metadata": {
"deviceInfo": {
"ipAddress": "127.0.0.1", // IP address of the device accepting the mandate
"userAgent": "Mozilla/5.0" // user-agent of the device accepting the mandate
}
}
...
}
Define the Primer Stripe dependency
Amend the dependencies section of your app’s build.gradle to include Primer’s stripe-android library:JS
Copy
Ask AI
dependencies {
/* Other dependencies... */
// Use the latest version available (https://central.sonatype.com/search?q=primer&namespace=io.primer)
implementation "io.primer:stripe-android:x.y.z"
}
When starting the headless universal checkout, it is important that you specify your Stripe publishable key when instantiating
PrimerSettings, otherwise you’ll get validation errors when interacting with the payment manager.JS
Copy
Ask AI
val settings = PrimerSettings(
// ...
paymentMethodOptions = PrimerPaymentMethodOptions(
stripeOptions = PrimerStripeOptions(publishableKey = "your_stripe_publishable_key")
)
// ...
)
Create a new instance of StripeAchUserDetailsComponent
ACH via Stripe requires specific user details for payment handling. It is expected that you have a screen that allows entry of these details. In the context of this screen you can fetch an instance of StripeAchUserDetailsComponent via PrimerHeadlessUniversalCheckoutAchManager that can be assigned the details that the user enters.Here viewModelStoreOwner could point to the current activity or fragment.Although the component collects user details pertaining to ACH, it also handles tokenization and payment processes. Because of this, it should be kept in memory until the checkout is completed. This component implements Android’s
ViewModel class, therefore keeping a reference to it is not enough. Instead, pass a long lifecycle ViewModelStoreOwner when you initialize PrimerHeadlessUniversalCheckoutAchManager, such as that of your activity or parent fragment.JS
Copy
Ask AI
val stripeAchUserDetailsComponent = PrimerHeadlessUniversalCheckoutAchManager(viewModelStoreOwner).provide<StripeAchUserDetailsComponent>("STRIPE_ACH")
Listen for the emitted data
Subscribe to the componentStep Flow in order to be notified about the state of the process:JS
Copy
Ask AI
stripeAchUserDetailsComponent.componentStep.collectLatest { step ->
when (step) {
// Emitted once the component is started via a call to the `start()` function.
is StripeAchUserDetailsStep.UserDetailsRetrieved -> {
val (firstName, lastName, emailAddress) = step
// Holds user details passed during the client session creation process.
// These details serve as a basis for prefilling user input fields in your app's UI,
// offering users the convenience of reviewing and potentially modifying their information as needed.
}
// Emitted once the component is submitted via a call to the `submit()` function, signaling that the
// user detail collection has completed.
is StripeAchUserDetailsStep.UserDetailsCollected -> {
// From this point on you may dismiss the user details collection screen.
}
}
}
Listen to the data validation statuses
This component allows you to collect and validate details such as the user’s first name, last name and email address.It’s worth mentioning that the data validation is designed to operate in real-time. This means that the accuracy and completeness of the collected data can be confirmed even while users are actively inputting their information. This real-time validation feature ensures that your data remains accurate and complete throughout the data entry process, enhancing the user experience and the reliability of the collected data.Listen to the componentValidationStatus to handle any validation statuses that may occur.JS
Copy
Ask AI
stripeAchUserDetailsComponent.componentValidationStatus.collectLatest { validationStatus ->
// Handle validation being in progress (e.g. show progress view),
// valid (e.g. enable component submission button, submit data automatically..),
// invalid (e.g. disable component submission button, show validation error messages in input components..)
// and error status (e.g. display error messages to the user)
}
Start the flow of the component
Call the start function in order to start the flow of the component:JS
Copy
Ask AI
stripeAchUserDetailsComponent.start()
UserDetailsRetrieved step will be emitted. As mentioned previously, the UserDetailsRetrieved step contains user details previously passed during the client session creation process. Use these details to prefill the fields on your user detail collection screen, offering users the convenience of reviewing and potentially modifying their information as needed.Handle user data collection
As mentioned previously, the component allows you to collect the user’s first name, last name and email address. Note that all these details are supposed to be set before submitting the component via the submit function.JS
Copy
Ask AI
stripeAchUserDetailsComponent.updateCollectedData(
AchUserDetailsCollectableData.FirstName(firstName)
)
stripeAchUserDetailsComponent.updateCollectedData(
AchUserDetailsCollectableData.LastName(lastName)
)
stripeAchUserDetailsComponent.updateCollectedData(
AchUserDetailsCollectableData.EmailAddress(emailAddress)
)
Flow, call the submit function to initiate the tokenization process.JS
Copy
Ask AI
stripeAchUserDetailsComponent.submit()
UserDetailsCollected step, after which you should transition away from the user details collection screen.Handle Headless Universal Checkout Listener calls
Once the user data collection component emits the UserDetailsCollected step, interaction with the component is over, and you are expected to handle calls in your Headless Universal Checkout Listener implementation in order continue with the payment process. Doing so will allow you to submit the mandate authorization, process required for all Stripe ACH payments.Start by overriding onCheckoutAdditionalInfoReceived to handle the mandate.JS
Copy
Ask AI
override fun onCheckoutAdditionalInfoReceived(additionalInfo: PrimerCheckoutAdditionalInfo) {
when (additionalInfo) {
// Emitted when starting the Stripe bank collection flow, which opens an activity from the Stripe SDK,
// thus it requires an 'ActivityResultRegistry' to handle activity results.
is AchAdditionalInfo.ProvideActivityResultRegistry -> { // Not applicable to Vault Manager sessions
// Call 'provide()', passing your current activity's 'ActivityResultRegistry'
// Eg.: inside an activity
additionalInfo.provide(activityResultRegistry)
// Eg.: inside a fragment
additionalInfo.provide(requireActivity().activityResultRegistry)
}
// Emitted either when a bank is chosen through the Stripe bank collection process, or when paying via vaulted payment information.
// Stripe ACH requires that a mandate is displayed before the payment is completed.
is AchAdditionalInfo.DisplayMandate -> {
// 1. Navigate to your customer screen/dialog/UI that shows the mandate text
// 2. Accept or decline the mandate via the aptly named lambda functions.
// Declining the mandate will cancel the payment, leading to an 'onFailure()' call on your listener,
// while accepting the mandate would (assuming successful backend interaction) translate in a call to
// 'onCheckoutCompleted()' on that same listener.
lifecycleScope.launch {
(additionalInfo).onAcceptMandate.invoke()
}
lifecycleScope.launch {
(additionalInfo).onDeclineMandate.invoke()
}
}
}
}
JS
Copy
Ask AI
override fun onCheckoutCompleted(checkoutData: PrimerCheckoutData) {
// Show your checkout completion UI.
}
JS
Copy
Ask AI
override fun onFailed(error: PrimerError) {
// Show your error UI.
}
override fun onFailed(error: PrimerError, checkoutData: PrimerCheckoutData?) {
// Show your error UI.
}
PrimerHeadlessUniversalCheckoutListener interface.Handle component errors
You can listen to the componentError Flow to handle any errors that may occur during user details collection or tokenization:JS
Copy
Ask AI
stripeAchUserDetailsComponent.componentError.collectLatest { error ->
// Handle errors (e.g., display error messages to the user)
}
componentError is a Flow that emits PrimerError objects, allowing you to identify and respond to any errors that occur during the payment process.Vaulting
ACH via Stripe payment tokens can be vaulted automatically on successful agreement if the vaultOnAgreement flag is turned on when creating the client session. Note that the vaultOnAgreement flag is supported starting with API version 2.4.JSON
Copy
Ask AI
{
...
customer: {
emailAddress: '...',
...
},
"metadata": {
"deviceInfo": {
"ipAddress": "127.0.0.1", // IP address of the device accepting the mandate
"userAgent": "Mozilla/5.0" // user-agent of the device accepting the mandate
}
},
"paymentMethod": {
"paymentType": "FIRST_PAYMENT",
"vaultOnAgreement": true
...
}
...
}
bankName and accountNumberLast4Digits from returned the paymentInstrumentData to render your saved payment method.Making payments with vaulted ACH via Stripe
User details are stored alongside payment information in the vault so the usage of StripeAchUserDetailsComponent described earlier does not apply when utilizing ACH via Stripe with the Vault.To allow payments with a vaulted payment token, use the Vault Manager as usual ([fetch the payment methods](fetch the payment methods) then perform the payment).Pre-requisites
Integration
AchManager is an SDK manager designed to streamline the headless integration with ACH-based payment methods. To integrate AchManager, follow these steps:Pass device info and payment type when creating the client session
ACH via Stripe requires that a mandate is accepted before the payment is completed. Mandates that are accepted online require IP address and user-agent of the device that the payment is made from.Specify this information when creating the client session, otherwise payments will remain in Pending state.JSON
Copy
Ask AI
{
...
customer: {
emailAddress: '...',
...
},
"metadata": {
"deviceInfo": {
"ipAddress": "127.0.0.1", // IP address of the device accepting the mandate
"userAgent": "Mozilla/5.0" // user-agent of the device accepting the mandate
}
}
...
}
Define the Primer Stripe dependency
Amend the dependencies section of your app’s build.gradle to include Primer’s stripe-android library:JS
Copy
Ask AI
dependencies {
/* Other dependencies... */
// Use the latest version available (https://central.sonatype.com/search?q=primer&namespace=io.primer)
implementation "io.primer:stripe-android:x.y.z"
}
Define the Stripe publishable key
When preparing the headless universal checkout, it is important that you specify your Stripe publishable key when instantiating PrimerSettings, otherwise you’ll get validation errors when interacting with the payment manager.JS
Copy
Ask AI
let settings: PrimerSettings = {
...
paymentMethodOptions: {
...
stripeOptions: {
publishableKey: "your_stripe_publishable_key"
},
...
},
...
}
AchManagerPropsTypescript
Copy
Ask AI
const achManagerProps: AchManagerProps = {
paymentMethodType: "STRIPE_ACH",
onStep: (data: AchStep) => {
// Pre-populate your form with first name, last name and email address or close form.
},
onError: (error: PrimerError) => {
// An error that occurred during the component's operation.
},
onInvalid: (data: PrimerInvalidComponentData<AchValidatableData>) => {
// Data was considered invalid during validation.
},
onValid: (data: PrimerValidComponentData<AchValidatableData>) => {
// Data was successfully validated.
},
onValidating: (data: PrimerValidatingComponentData<AchValidatableData>) => {
// Data is in the process of being validated.
},
onValidationError: (data: PrimerComponentDataValidationError<AchValidatableData>) => {
// Error occurred during data validation.
},
};
StripeAchUserDetailsComponentTypescript
Copy
Ask AI
const achManager = new AchManager();
const stripeAchUserDetailsComponent: StripeAchUserDetailsComponent = await achManager.provide(achManagerProps);
Listen for the emitted data
Make use of AchManagerProps.onStep in order to be notified about the user details collection state.Typescript
Copy
Ask AI
const achManagerProps: AchManagerProps = {
...
onStep: (data: AchStep) => {
switch(data.stepName) {
case "userDetailsRetrieved":
// Emitted once the component is started via a call to the `start()` function.
// Holds user details passed during the client session creation process.
// These details serve as a basis for prefilling user input fields in your app's UI,
// offering users the convenience of reviewing and potentially modifying their information as needed.
break;
case "userDetailsCollected":
// Emitted once the component is submitted via a call to the `submit()` function.
// From this point on you may dismiss the user details collection screen and start listening for `DisplayStripeAchMandateAdditionalInfo` events.
break;
}
},
...
};
Listen to the data validation statuses
This component allows you to collect and validate details such as the user’s first name, last name and email address. It’s worth mentioning that the data validation is designed to operate in real-time. This means that the accuracy and completeness of the collected data can be confirmed even while users are actively inputting their information. This real-time validation feature ensures that your data remains accurate and complete throughout the data entry process, enhancing the user experience and the reliability of the collected data.Use AchManagerProps validation callbacks to handle any validation statuses that may occur.Typescript
Copy
Ask AI
const achManagerProps: AchManagerProps = {
...
onInvalid: (data: PrimerInvalidComponentData<AchValidatableData>) => {
// Data was considered invalid during validation, perhaps display an error message to the user.
switch(data.data.validatableDataName) {
case "firstName":
// ...
break;
case "lastName":
// ...
break;
case "emailAddress":
// ...
break;
}
},
onValid: (data: PrimerValidComponentData<AchValidatableData>) => {
// Data was successfully validated, perhaps enable a submission button.
switch(data.data.validatableDataName) {
case "firstName":
// ...
break;
case "lastName":
// ...
break;
case "emailAddress":
// ...
break;
}
},
onValidating: (data: PrimerValidatingComponentData<AchValidatableData>) => {
// Data is in the process of being validated, perhaps show a progress view.
switch(data.data.validatableDataName) {
case "firstName":
// ...
break;
case "lastName":
// ...
break;
case "emailAddress":
// ...
break;
}
},
onValidationError: (data: PrimerComponentDataValidationError<AchValidatableData>) => {
// Error occurred during data validation, perhaps display an error message to the user.
switch(data.data.validatableDataName) {
case "firstName":
// ...
break;
case "lastName":
// ...
break;
case "emailAddress":
// ...
break;
}
},
};
Start the flow of the component
Call the start function in order to start the flow of the component.Typescript
Copy
Ask AI
stripeAchUserDetailsComponent.start()
UserDetailsRetrieved step will be emitted. As mentioned previously, the UserDetailsRetrieved step contains user details previously passed during the client session creation process. Use these details to prefill the fields on your user detail collection screen, offering users the convenience of reviewing and potentially modifying their information as needed.Handle user data collection
As mentioned previously, the component allows you to collect the user’s first name, last name and email address. Note that all these details are supposed to be set before submitting the component via the submit function.JS
Copy
Ask AI
stripeAchUserDetailsComponent.handleFirstNameChange(firstName);
stripeAchUserDetailsComponent.handleLastNameChange(lastName);
stripeAchUserDetailsComponent.handleEmailAddressChange(emailAddress);
AchManagerProps.onValid, call the submit function to initiate the tokenization process.TypeScript
Copy
Ask AI
stripeAchUserDetailsComponent.submit()
UserDetailsCollected step, after which you should transition away from the user details collection screen.Handle onCheckoutAdditionalInfo calls
Handle Headless Universal Checkout Listener calls
Once the user data collection component emits theUserDetailsCollected step, you are expected to handle calls to onCheckoutAdditionalInfo in order continue with the payment process. Doing so will allow you to submit the mandate authorization, process required for all Stripe ACH payments.Once the user data collection component emits the UserDetailsCollected step, interaction with the component is over, and you are expected to handle calls in your Headless Universal Checkout Listener implementation in order continue with the payment process. Doing so will allow you to submit the mandate authorization, process required for all Stripe ACH payments. You’ll also be notified about payment completion or failure.Start by overriding onCheckoutAdditionalInfo to handle the mandate.JS
Copy
Ask AI
let settings: PrimerSettings = {
...
headlessUniversalCheckoutCallbacks: {
...
onCheckoutAdditionalInfo: additionalInfo => {
switch(additionalInfo.additionalInfoName) {
// Emitted either when a bank is chosen through the Stripe bank collection process, or when paying via vaulted payment information.
// ACH via Stripe requires that a mandate is displayed before the payment is completed.
case "DisplayStripeAchMandateAdditionalInfo":
// 1. Navigate to your customer screen/dialog/UI that shows the mandate text
// 2. Accept or decline the mandate via the aptly named functions exposed by AchMandateManager.
// Declining the mandate will cancel the payment, leading to an 'onError()' call on your listener,
// while accepting the mandate would (assuming successful backend interaction) translate in a call to
// 'onCheckoutComplete()' on that same listener.
const achMandateManager = new AchMandateManager();
achMandateManager.acceptMandate();
// or
achMandateManager.declineMandate();
break;
}
},
...
}
}
JS
Copy
Ask AI
let settings: PrimerSettings = {
...
headlessUniversalCheckoutCallbacks: {
...
onCheckoutComplete: (checkoutData: PrimerCheckoutData) => {
// Show your checkout completion UI.
},
...
}
}
JS
Copy
Ask AI
let settings: PrimerSettings = {
...
headlessUniversalCheckoutCallbacks: {
...
onError: (error: PrimerError, checkoutData: PrimerCheckoutData | null) => {
// Show your error UI.
},
...
}
}
PrimerSettings interface.Handle component errors
Make use of AchManagerProps.onError to handle any errors that may occur during the user details collection flow:Typescript
Copy
Ask AI
const achManagerProps: AchManagerProps = {
...
onError: (error: PrimerError) => {
// An error that occurred during the component's operation, perhaps display an error message to the user.
},
...
};
Vaulting
ACH via Stripe payment tokens can be vaulted automatically on successful agreement if thevaultOnAgreement flag is turned on when creating the client session.JSON
Copy
Ask AI
{
...
customer: {
emailAddress: '...',
...
},
"metadata": {
"deviceInfo": {
"ipAddress": "127.0.0.1", // IP address of the device accepting the mandate
"userAgent": "Mozilla/5.0" // user-agent of the device accepting the mandate
}
},
"paymentMethod": {
"paymentType": "FIRST_PAYMENT",
"vaultOnAgreement": true
...
}
...
}
fetchVaultedPaymentMethods function. You may use bankName and accountNumberLast4Digits from returned the paymentInstrumentData to render your saved payment method.Making payments with vaulted ACH via Stripe
User details are stored alongside payment information in the vault so the usage ofStripeAchUserDetailsComponent described earlier does not apply when utilizing Stripe ACH via Vault.To allow payments with a vaulted payment token, fetch the vaulted payment methods as usual (VaultManager.fetchVaultedPaymentMethods()) then perform the payment (VaultManager.startPaymentFlow(vaultedPaymentMethodId: string)).Go live
You don’t need to do anything particular to go live — just make sure to use production credentials.Handle payment method ACH via Stripe requires a Native UI Manager in order to be presented to the user:Typescript
Copy
Ask AI
// 👇 Create the payment method manager
const nativeUIManager = new NativeUIManager()
// 👇 Configure the payment method manager
await nativeUIManager.configure({ paymentMethodType: 'STRIPE_ACH' })
// 👇 Show the payment method
await nativeUIManager.showPaymentMethod(SessionIntent.CHECKOUT)
Test
You can test payments using USD as the currency.Sandbox testing
- After selecting the payment method button in the Checkout, the page will navigate to a form
- After you complete the form, the Stripe pop-up will appear
- After finishing the steps inside the Stripe pop-up, the user will be taken back to the Checkout to confirm / decline mandate
- At the end of this flow, the payment status will be first PENDING in the Primer Dashboard and depending on the user actions or external factors, like insufficient funds in the bank account, it will be either DECLINED, CANCELLED or SETTLED for successful completion in Primer Dashboard
- To simulate the receiving of funds and transition the payment to SETTLED, you can choose in the Stripe pop-up:
Test InstitutionandSuccessaccount in the next screen. Next you will be taken back to Checkout to confirm the mandate. Primer will receive a webhook and subsequently update the payment to SETTLED.