Save details for future payments with ACH Direct Debit
You can use the Setup Intents API to collect payment method details in advance, with the final amount or payment date determined later. This is useful for:
- Saving payment methods to a wallet to streamline future purchases
- Collecting surcharges after fulfilling a service
- Starting a free trial for a subscription
Note
ACH Direct Debit is a delayed notification payment method, which means that funds aren’t immediately available after payment. A payment typically takes 4 business days to arrive in your account.
Set up StripeServer-side
First, you need a Stripe account. Register now.
With Stripe, you can instantly verify a customer’s bank account. If you want to retrieve additional data on an account, sign up for data access with Stripe Financial Connections.
Stripe Financial Connections lets your customers securely share their financial data by linking their financial accounts to your business. Use Financial Connections to access customer-permissioned financial data such as tokenized account and routing numbers, balance data, ownership details, and transaction data.
Access to this data helps you perform actions like check balances before initiating a payment to reduce the chance of a failed payment because of insufficient funds.
Use our official libraries for access to the Stripe API from your application:
Create or retrieve a CustomerServer-side
To reuse a bank account for future payments, you must attach it to a Customer.
Create a Customer object when your customer creates an account with your business. Associating the ID of the Customer object with your own internal representation of a customer enables you to retrieve and use the stored payment method details later. If your customer hasn’t created an account, you can still create a Customer object now and associate it with your internal representation of the customer’s account later.
Create a new Customer or retrieve an existing Customer to associate with these payment details. Include the following code on your server to create a new Customer.
Create a SetupIntentServer-side
A SetupIntent is an object that represents your intent to set up a customer’s payment method for future payments. The SetupIntent
tracks the steps of this set-up process.
Create a SetupIntent on your server with payment_method_types set to us_bank_account and specify the Customer’s id.
Set the permissions parameter to payment_method
. If you signed up for data access with Stripe Financial Connections, specify the additional account data you require. We recommend specifying balances
in the permissions
parameter to access balance data on an account. Checking an account balance before confirming a payment helps prevent payment failures because of insufficient funds.
Optionally, specify which data you require immediately upon account creation with the prefetch
parameter. The prefetch
parameter saves you from making another API call to retrieve data after account creation and reduces the latency of data retrieval. To protect the privacy of your customer’s data, you only receive access to account data you request. Your customer can see the data you want to access before verifying their account.
Note
To expand access to additional data after a customer authenticates their account, they must re-link their account with expanded permissions.
Retrieve the client secret
The SetupIntent includes a client secret that the client side uses to securely complete the payment process. You can use different approaches to pass the client secret to the client side.
Collect payment method detailsClient-side
When a customer clicks to pay with ACH Direct Debit, we recommend you use Stripe.js to submit the payment to Stripe. Stripe.js is our foundational JavaScript library for building payment flows. It will automatically handle integration complexities, and enables you to easily extend your integration to other payment methods in the future.
Include the Stripe.js script on your checkout page by adding it to the head
of your HTML file.
<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head>
Create an instance of Stripe.js with the following JavaScript on your checkout page.
// Set your publishable key. Remember to change this to your live publishable key in production! // See your keys here: https://dashboard.stripe.com/apikeys const stripe = Stripe(
);'pk_test_TYooMQauvdEDq54NiTphI7jx'
Rather than sending the entire SetupIntent object to the client, use its client secret from the previous step. This is different from your API keys that authenticate Stripe API requests.
Handle the client secret carefully because it can complete the charge. Don’t log it, embed it in URLs, or expose it to anyone but the customer.
Use stripe.collectBankAccountForSetup to collect bank account details, create a PaymentMethod, and attach that PaymentMethod to the SetupIntent. Including the account holder’s name in the billing_details
parameter is required to create an ACH Direct Debit PaymentMethod.
// Use the form that already exists on the web page. const paymentMethodForm = document.getElementById('payment-method-form'); const confirmationForm = document.getElementById('confirmation-form'); paymentMethodForm.addEventListener('submit', (ev) => { ev.preventDefault(); const accountHolderNameField = document.getElementById('account-holder-name-field'); const emailField = document.getElementById('email-field'); // Calling this method will open the instant verification dialog. stripe.collectBankAccountForSetup({ clientSecret: clientSecret, params: { payment_method_type: 'us_bank_account', payment_method_data: { billing_details: { name: accountHolderNameField.value, email: emailField.value, }, }, }, expand: ['payment_method'], }) .then(({setupIntent, error}) => { if (error) { console.error(error.message); // PaymentMethod collection failed for some reason. } else if (setupIntent.status === 'requires_payment_method') { // Customer canceled the hosted verification modal. Present them with other // payment method type options. } else if (setupIntent.status === 'requires_confirmation') { // We collected an account - possibly instantly verified, but possibly // manually-entered. Display payment method details and mandate text // to the customer and confirm the intent once they accept // the mandate. confirmationForm.show(); } }); });
The Stripe Financial Connections authentication flow automatically handles bank account details collection and verification. When your customer completes the authentication flow, the PaymentMethod automatically attaches to the SetupIntent, and a Financial Connections account is created.
Warning
The Stripe Financial Connections Terms describe the terms that apply to your use of Stripe Financial Connections. You may not access or use Stripe Financial Connections unless you abide by all of the terms, and by accessing or using Stripe Financial Connections, you agree to abide by all of the terms.
To provide the best user experience on all devices, set the viewport minimum-scale
for your page to 1 using the viewport meta
tag.
<meta name="viewport" content="width=device-width, minimum-scale=1" />
Collect mandate acknowledgement and submitClient-side
Before you can complete the SetupIntent and save the payment method details for the customer, you must obtain authorization for payment by displaying mandate terms for the customer to accept.
To be compliant with Nacha rules, you must obtain authorization from your customer before you can initiate payment by displaying mandate terms for them to accept. For more information on mandates, see Mandates.
When the customer accepts the mandate terms, you must confirm the SetupIntent. Use stripe.confirmUsBankAccountSetup to complete the payment when the customer submits the form.
confirmationForm.addEventListener('submit', (ev) => { ev.preventDefault(); stripe.confirmUsBankAccountSetup(clientSecret) .then(({setupIntent, error}) => { if (error) { console.error(error.message); // The payment failed for some reason. } else if (setupIntent.status === "requires_payment_method") { // Confirmation failed. Attempt again with a different payment method. } else if (setupIntent.status === "succeeded") { // Confirmation succeeded! The account is now saved. // Display a message to customer. } else if (setupIntent.next_action?.type === "verify_with_microdeposits") { // The account needs to be verified via microdeposits. // Display a message to consumer with next steps (consumer waits for // microdeposits, then enters a statement descriptor code on a page sent to them via email). } }); });
Note
stripe.confirmUsBankAccountSetup may take several seconds to complete. During that time, disable resubmittals of your form and show a waiting indicator (for example, a spinner). If you receive an error, show it to the customer, re-enable the form, and hide the waiting indicator.
If successful, Stripe returns a SetupIntent object, with one of the following possible statuses:
Status | Description | Next Steps |
---|---|---|
succeeded | The bank account has been instantly verified or verification was not necessary. | No action needed |
requires_action | Further action needed to complete bank account verification. | Step 6: Verifying bank accounts with microdeposits |
After successfully confirming the SetupIntent, an email confirmation of the mandate and collected bank account details must be sent to your customer. Stripe handles these by default, but you can choose to send custom notifications if you prefer.
Verify bank account with microdepositsClient-side
Not all customers can verify the bank account instantly. This step only applies if your customer has elected to opt out of the instant verification flow in the previous step.
In these cases, Stripe sends a descriptor_code
microdeposit and might fall back to an amount
microdeposit if any further issues arise with verifying the bank account. These deposits take 1-2 business days to appear on the customer’s online statement.
- Descriptor code. Stripe sends a single, 0.01 USD microdeposit to the customer’s bank account with a unique, 6-digit
descriptor_code
that starts with SM. Your customer uses this string to verify their bank account. - Amount. Stripe sends two, non-unique microdeposits to the customer’s bank account, with a statement descriptor that reads
ACCTVERIFY
. Your customer uses the deposit amounts to verify their bank account.
The result of the stripe.confirmUsBankAccountSetup method call in the previous step is a SetupIntent in the requires_action
state. The SetupIntent contains a next_action
field that contains some useful information for completing the verification.
next_action: { type: "verify_with_microdeposits", verify_with_microdeposits: { arrival_date: 1647586800, hosted_verification_url: "https://payments.stripe.com/…", microdeposit_type: "descriptor_code" } }
If you supplied a billing email, Stripe notifies your customer via this email when the deposits are expected to arrive. The email includes a link to a Stripe-hosted verification page where they can confirm the amounts of the deposits and complete verification.
Warning
Verification attempts have a limit of ten failures for descriptor-based microdeposits and three for amount-based ones. If you exceed this limit, we can no longer verify the bank account. In addition, microdeposit verifications have a timeout of 10 days. If you can’t verify microdeposits in that time, the SetupIntent reverts to requiring new payment method details. Clear messaging about what these microdeposits are and how you use them can help your customers avoid verification issues.
Optional: Send custom email notifications
Optionally, you can send custom email notifications to your customer. After you set up custom emails, you need to specify how the customer responds to the verification email. To do so, choose one of the following:
Use the Stripe-hosted verification page. To do this, use the
verify_with_microdeposits[hosted_verification_url]
URL in thenext_action
object to direct your customer to complete the verification process.If you prefer not to use the Stripe-hosted verification page, create a form on your site. Your customers then use this form to relay microdeposit amounts to you and verify the bank account using Stripe.js.
- At minimum, set up the form to handle the
descriptor code
parameter, which is a 6-digit string for verification purposes. - Stripe also recommends that you set your form to handle the
amounts
parameter, as some banks your customers use may require it.
Integrations only pass in the
descriptor_code
oramounts
. To determine which one your integration uses, check the value forverify_with_microdeposits[microdeposit_type]
in thenext_action
object.- At minimum, set up the form to handle the
stripe.verifyMicrodepositsForSetup(clientSecret, { // Provide either a descriptor_code OR amounts, not both descriptor_code: `SMT86W`, amounts: [32, 45], });
When the bank account is successfully verified, Stripe returns the SetupIntent object with a status
of succeeded
, and sends a setup_intent.succeeded webhook event.
Verification can fail for several reasons. The failure may happen synchronously as a direct error response, or asynchronously through a setup_intent.setup_failed webhook event (shown in the following examples).
Error Code | Synchronous or Asynchronous | Message | Status change |
---|---|---|---|
payment_method_microdeposit_failed | Synchronously, or asynchronously through webhook event | Microdeposits failed. Please check the account, institution and transit numbers provided | status is requires_payment_method , and last_setup_error is set. |
payment_method_microdeposit_verification_amounts_mismatch | Synchronously | The amounts provided do not match the amounts that were sent to the bank account. You have {attempts_remaining} verification attempts remaining. | Unchanged |
payment_method_microdeposit_verification_attempts_exceeded | Synchronously, or asynchronously through webhook event | Exceeded number of allowed verification attempts | status is requires_payment_method , and last_setup_error is set. |
payment_method_microdeposit_verification_timeout | Asynchronously through webhook event | Microdeposit timeout. Customer hasn’t verified their bank account within the required 10 day period. | status is requires_payment_method , and last_setup_error is set. |
Test your integration
To test scenarios with instant verifications, mimic different scenarios for ACH charges by selecting the test bank account for the use case you want to test.
Send transaction emails in test mode
After you collect the bank account details and accept a mandate, send the mandate confirmation and microdeposit verification emails in test mode. To do this, provide an email in the payment_method_data.billing_details[email]
field in the form of {any-prefix}+test_email@{any_domain}
when you collect the payment method details.
Common mistake
You need to activate your Stripe account before you can trigger these emails in Test mode.
Test account numbers
Stripe provides several test account numbers and corresponding tokens you can use to make sure your integration for manually-entered bank accounts is ready for production.
Account number | Token | Routing number | Behavior |
---|---|---|---|
000123456789 | pm_usBankAccount_success | 110000000 | The payment succeeds. |
000111111113 | pm_usBankAccount_accountClosed | 110000000 | The payment fails because the account is closed. |
000111111116 | pm_usBankAccount_noAccount | 110000000 | The payment fails because no account is found. |
000222222227 | pm_usBankAccount_insufficientFunds | 110000000 | The payment fails due to insufficient funds. |
000333333335 | pm_usBankAccount_debitNotAuthorized | 110000000 | The payment fails because debits aren’t authorized. |
000444444440 | pm_usBankAccount_invalidCurrency | 110000000 | The payment fails due to invalid currency. |
000666666661 | pm_usBankAccount_failMicrodeposits | 110000000 | The payment fails to send microdeposits. |
000555555559 | pm_usBankAccount_dispute | 110000000 | The payment triggers a dispute. |
Before test transactions can complete, you need to verify all test accounts that automatically succeed or fail the payment. To do so, use the test microdeposit amounts or descriptor codes below.
Test microdeposit amounts and descriptor codes
To mimic different scenarios, use these microdeposit amounts or 0.01 descriptor code values.
Microdeposit values | 0.01 descriptor code values | Scenario |
---|---|---|
32 and 45 | SM11AA | Simulates verifying the account. |
10 and 11 | SM33CC | Simulates exceeding the number of allowed verification attempts. |
40 and 41 | SM44DD | Simulates a microdeposit timeout. |
Accepting future paymentsServer-side
When the SetupIntent succeeds, it will create a new PaymentMethod attached to a Customer. These can be used to initiate future payments without having to prompt the customer for their bank account a second time.