Skip to content
Create account
or
Sign in
The Stripe Docs logo
/
Ask AI
Create account
Sign in
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Developer tools
Get started
Payments
Finance automation
Get started
Payments
Finance automation
Platforms and marketplaces
Money management
Overview
About Stripe payments
Upgrade your integration
Payments analytics
Online payments
OverviewFind your use caseManaged Payments
Use Payment Links
Build a checkout page
Build an advanced integration
Build an in-app integration
Payment methods
Add payment methods
    Overview
    Payment method integration options
    Manage default payment methods in the Dashboard
    Payment method types
    Cards
    Bank debits
      ACH Direct Debit
        Accept a payment
        Save bank details
        Migrating from the Charges API
        Migrating from another processor
        Blocked bank accounts
        SEC codes
      Bacs Direct Debit
      Pre-authorized debit in Canada
      Australia BECS Direct Debit
      New Zeland BECS Direct Debit
      SEPA Direct Debit
    Bank redirects
    Bank transfers
    Credit transfers (Sources)
    Buy now, pay later
    Real-time payments
    Vouchers
    Wallets
    Enable local payment methods by country
    Custom payment methods
Manage payment methods
Faster checkout with Link
Payment interfaces
Payment Links
Checkout
Web Elements
In-app Elements
Payment scenarios
Custom payment flows
Flexible acquiring
Orchestration
In-person payments
Terminal
Other Stripe products
Financial Connections
Crypto
Climate
HomePaymentsAdd payment methodsBank debitsACH Direct Debit

Save details for future payments with ACH Direct Debit

Learn how to save payment method details for future ACH Direct Debit payments.

Copy page

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.

The Stripe Android SDK is open source and fully documented.

To install the SDK, add stripe-android and financial-connections to the dependencies block of your app/build.gradle file:

build.gradle.kts
Kotlin
plugins { id("com.android.application") } android { ... } dependencies { // ... // Stripe Android SDK implementation("com.stripe:stripe-android:21.12.0") // Include the financial connections SDK to support US bank account as a payment method implementation("com.stripe:financial-connections:21.12.0") // Financial Connections SDK implementation("com.stripe:financial-connections:21.12.0") }

Note

For details on the latest SDK release and past versions, see the Releases page on GitHub. To receive notifications when a new release is published, watch releases for the repository.

Configure the SDK with your Stripe publishable key so that it can make requests to the Stripe API, such as in your Application subclass:

Kotlin
import com.stripe.android.PaymentConfiguration class MyApp : Application() { override fun onCreate() { super.onCreate() PaymentConfiguration.init( applicationContext,
"pk_test_TYooMQauvdEDq54NiTphI7jx"
) } }

Note

Use your test keys while you test and develop, and your live mode keys when you publish your app.

Create or retrieve a customer
Recommended
Server-side

Create a Customer object when your user creates an account with your business, or retrieve an existing Customer associated with this user. 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. Include an email address on the Customer to enable Financial Connections’ return user optimization.

Command Line
cURL
curl https://api.stripe.com/v1/customers \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d email={{CUSTOMER_EMAIL}}

Create a SetupIntent
Server-side
Client-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.

Server-side

Create a SetupIntent on your server with payment_method_types set to us_bank_account and specify the Customer’s id.

For more information on Financial Connections fees, see pricing details.

By default, collecting bank account payment information uses Financial Connections to instantly verify your customer’s account, with a fallback option of manual account number entry and microdeposit verification. See the Financial Connections docs to learn how to configure Financial Connections and access additional account data to optimize your ACH integration. For example, you can use Financial Connections to check an account’s balance before initiating the ACH payment.

Note

To expand access to additional data after a customer authenticates their account, they must re-link their account with expanded permissions.

Command Line
cURL
curl https://api.stripe.com/v1/setup_intents \ -u "
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:"
\ -d customer={{CUSTOMER_ID}} \ -d "payment_method_types[]"=us_bank_account \ -d "payment_method_options[us_bank_account][financial_connections][permissions][]"=payment_method \ -d "payment_method_options[us_bank_account][financial_connections][permissions][]"=balances

Client-side

Included in the returned SetupIntent is a client secret, which is used on the client side to securely complete the setup instead of passing the entire SetupIntent object. There are different approaches that you can use to pass the client secret to the client side.

Kotlin
import androidx.appcompat.app.AppCompatActivity class CheckoutActivity : AppCompatActivity() { lateinit var setupIntentClientSecret: String fun startCheckout() { // Request a SetupIntent from your server and store its client secret } }

Collect payment method details
Client-side

ACH Direct Debit requires a customer name and (optional) email for the payment to succeed. In your app, collect the required billing details from the customer:

  • Their full name (first and last)
  • Their email address

Use CollectBankAccountConfiguration.USBankAccount to create the parameters required to call presentWithSetupIntent.

Initialize a CollectBankAccountLauncher instance inside onCreate of your checkout Activity, passing a method to handle the result. Then proceed to call presentWithSetupIntent to collect bank account details, create a PaymentMethod, and attach that PaymentMethod to the SetupIntent.

Kotlin
import androidx.appcompat.app.AppCompatActivity class CheckoutActivity : AppCompatActivity() { private lateinit var setupIntentClientSecret: String private lateinit var collectBankAccountLauncher: CollectBankAccountLauncher override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Create collector collectBankAccountLauncher = CollectBankAccountLauncher.create( this ) { result: CollectBankAccountResult -> when (result) { is CollectBankAccountResult.Completed -> { val intent = result.response.intent if (intent.status === StripeIntent.Status.RequiresPaymentMethod) { // Customer canceled the Financial Connections modal. Present them with other // payment method type options. } else if (intent.status === StripeIntent.Status.RequiresConfirmation) { // 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. } } is CollectBankAccountResult.Cancelled -> { // handle cancellation } is CollectBankAccountResult.Failed -> { // handle error print("Error: ${result.error}") } } } } fun startCheckout() { // ... // Build params val collectParams: CollectBankAccountConfiguration = CollectBankAccountConfiguration.USBankAccount( name, email ) // Calling this method will trigger the Financial Connections modal to be displayed collectBankAccountLauncher.presentWithSetupIntent( publishableKey, setupIntentClientSecret, collectParams ) } }

This loads a modal UI that handles bank account details collection and verification. When it completes, the PaymentMethod automatically attaches to the SetupIntent.

OptionalAccess data on a Financial Connections bank account
Server-side

Collect mandate acknowledgement and submit
Client-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 Setup. Use confirm to complete the payment when the customer submits the form.

Kotlin
class CheckoutActivity : AppCompatActivity() { // ... private lateinit var setupIntentClientSecret: String private val paymentLauncher: PaymentLauncher by lazy { val paymentConfiguration = PaymentConfiguration.getInstance(applicationContext) PaymentLauncher.Companion.create( this, paymentConfiguration.publishableKey, paymentConfiguration.stripeAccountId, ::onPaymentResult ) } private fun startCheckout() { // ... val confirmParams = ConfirmSetupIntentParams.create( clientSecret = setupIntentClientSecret, paymentMethodType = PaymentMethod.Type.USBankAccount ) paymentLauncher.confirm(confirmParams) } private fun onPaymentResult(paymentResult: PaymentResult) { when (paymentResult) { is PaymentResult.Completed -> { // show success UI } is PaymentResult.Canceled -> { // handle cancel flow } is PaymentResult.Failed -> { // handle failures // (for example, the customer might need to choose a new payment // method) } } } }

Note

confirm 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:

StatusDescriptionNext Steps
succeededThe bank account has been instantly verified or verification was not necessary.No action needed
requires_actionFurther action needed to complete bank account verification.Step 6: Verifying bank accounts with microdeposits

After successfully confirming the Setup, 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 microdeposits
Client-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 confirmSetupIntent 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.

If you supplied a billing email, Stripe notifies your customer through 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 PaymentIntent 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

You can also 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 the next_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 in your app. Your customers then use this form to relay microdeposit amounts to you and verify the bank account using the Android SDK.

    • 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 or amounts. To determine which one your integration uses, check the value for verify_with_microdeposits[microdeposit_type] in the next_action object.

Kotlin
// Use if you are using a descriptor code, do not use if you are using amounts fun verifySetupIntentWithMicrodeposits( clientSecret: String, descriptorCode: String, callback: ApiResultCallback<PaymentIntent> ) // Use if you are using amounts, do not use if you are using descriptor code fun verifySetupIntentWithMicrodeposits( clientSecret: String, firstAmount: Int, secondAmount: Int, callback: ApiResultCallback<PaymentIntent> )

When the bank account is successfully verified, Stripe returns the SetupIntent object with a status of succeeded.

Verification can fail for several reasons. The failure happens synchronously as a direct error response.

{ "error": { "code": "payment_method_microdeposit_verification_amounts_mismatch", "message": "The amounts provided do not match the amounts that were sent to the bank account. You have {attempts_remaining} verification attempts remaining.", "type": "invalid_request_error" } }
Error CodeMessageStatus change
payment_method_microdeposit_failedMicrodeposits 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_mismatchThe 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_exceededExceeded number of allowed verification attemptsstatus is requires_payment_method, and last_setup_error is set.

Test your integration

Learn how to test scenarios with instant verifications using Financial Connections.

Send transaction emails in a sandbox

After you collect the bank account details and accept a mandate, send the mandate confirmation and microdeposit verification emails in a sandbox. 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 while testing.

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 numberTokenRouting numberBehavior
000123456789pm_usBankAccount_success110000000The payment succeeds.
000111111113pm_usBankAccount_accountClosed110000000The payment fails because the account is closed.
000111111116pm_usBankAccount_noAccount110000000The payment fails because no account is found.
000222222227pm_usBankAccount_insufficientFunds110000000The payment fails due to insufficient funds.
000333333335pm_usBankAccount_debitNotAuthorized110000000The payment fails because debits aren’t authorized.
000444444440pm_usBankAccount_invalidCurrency110000000The payment fails due to invalid currency.
000666666661pm_usBankAccount_failMicrodeposits110000000The payment fails to send microdeposits.
000555555559pm_usBankAccount_dispute110000000The payment triggers a dispute.
000000000009pm_usBankAccount_processing110000000The payment stays in processing indefinitely. Useful for testing PaymentIntent cancellation.
000777777771pm_usBankAccount_weeklyLimitExceeded110000000The payment fails due to payment amount causing the account to exceed its weekly payment volume limit.

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 values0.01 descriptor code valuesScenario
32 and 45SM11AASimulates verifying the account.
10 and 11SM33CCSimulates exceeding the number of allowed verification attempts.
40 and 41SM44DDSimulates a microdeposit timeout.

Test settlement behavior

Test transactions settle instantly and are added to your available test balance. This behavior differs from livemode, where transactions can take multiple days to settle in your available balance.

Accepting future payments
Server-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.

Command Line
curl
curl https://api.stripe.com/v1/payment_intents \ -u
sk_test_BQokikJOvBiI2HlWgH4olfQ2
:
\ -d "amount"=1099 \ -d "currency"="usd" \ -d "customer"="{{CUSTOMER_ID}}" \ -d "payment_method"="{{PAYMENT_METHOD_ID}}" \ -d "payment_method_types[]"="us_bank_account" \ -d "confirm"="true"

OptionalInstant only verification
Server-side

OptionalMicrodeposit only verification
Server-side

OptionalUpdating the default payment method
Server-side

Was this page helpful?
YesNo
Need help? Contact Support.
Join our early access program.
Check out our changelog.
Questions? Contact Sales.
LLM? Read llms.txt.
Powered by Markdoc