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.

Set up Stripe
Server-side
Client-side

Server-side

This integration requires endpoints on your server that talk to the Stripe API. Use our official libraries for access to the Stripe API from your server:

Command Line
Ruby
# Available as a gem sudo gem install stripe
Gemfile
Ruby
# If you use bundler, you can add this line to your Gemfile gem 'stripe'

Client-side

The React Native SDK is open source and fully documented. Internally, it uses the native iOS and Android SDKs. To install Stripe’s React Native SDK, run one of the following commands in your project’s directory (depending on which package manager you use):

Command Line
yarn add @stripe/stripe-react-native

Next, install some other necessary dependencies:

  • For iOS, navigate to the ios directory and run pod install to ensure that you also install the required native dependencies.
  • For Android, there are no more dependencies to install.

Stripe initialization

To initialize Stripe in your React Native app, either wrap your payment screen with the StripeProvider component, or use the initStripe initialization method. Only the API publishable key in publishableKey is required. The following example shows how to initialize Stripe using the StripeProvider component.

import { StripeProvider } from '@stripe/stripe-react-native'; function App() { const [publishableKey, setPublishableKey] = useState(''); const fetchPublishableKey = async () => { const key = await fetchKey(); // fetch key from your server here setPublishableKey(key); }; useEffect(() => { fetchPublishableKey(); }, []); return ( <StripeProvider publishableKey={publishableKey} merchantIdentifier="merchant.identifier" // required for Apple Pay urlScheme="your-url-scheme" // required for 3D Secure and bank redirects > // Your app code here </StripeProvider> ); }

Note

Use your API 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

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.

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

A SetupIntent includes a client secret. You can use the client secret in your React Native app to securely complete the payment process instead of passing back the entire SetupIntent object. In your app, request a SetupIntent from your server and store its client secret.

function PaymentScreen() { // ... const fetchIntentClientSecret = async () => { const response = await fetch(`${API_URL}/create-intent`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ // This is an example request body, the parameters you pass are up to you customer: '<CUSTOMER_ID>', }), }); const {clientSecret} = await response.json(); return clientSecret; }; return <View>...</View>; }

Collect payment method details
Client-side

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 collectBankAccountForSetup to collect bank account details, create a PaymentMethod, and attach that PaymentMethod to the SetupIntent. You must include the account holder’s name in the billingDetails parameter to create an ACH Direct Debit PaymentMethod.

import {collectBankAccountForSetup} from '@stripe/stripe-react-native'; export default function MyPaymentScreen() { const [name, setName] = useState(''); const handleCollectBankAccountPress = async () => { // Fetch the intent client secret from the backend. // See `fetchIntentClientSecret()`'s implementation above. const {clientSecret} = await fetchIntentClientSecret(); const {setupIntent, error} = await collectBankAccountForSetup( clientSecret, { paymentMethodType: 'USBankAccount', payment_method_data: { billing_details: { name: "John Doe", }, }, }, ); if (error) { Alert.alert(`Error code: ${error.code}`, error.message); } else if (setupIntent) { Alert.alert('Setup status:', setupIntent.status); if (setupIntent.status === SetupIntents.Status.RequiresConfirmation) { // The next step is to call `confirmSetup` } else if (setupIntent.status === SetupIntents.Status.RequiresAction) { // The next step is to call `verifyMicrodepositsForSetup` } } }; return ( <PaymentScreen> <TextInput placeholder="Name" onChange={(value) => setName(value.nativeEvent.text)} /> <Button onPress={handleCollectBankAccountPress} title="Collect bank account" /> </PaymentScreen> ); }

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

Set up a return URL (iOS only)
Client-side

When a customer exits your app, for example to authenticate in Safari or their banking app, provide a way for them to automatically return to your app afterward. Many payment method types require a return URL, so if you fail to provide it, we can’t present those payment methods to your user, even if you’ve enabled them.

To provide a return URL:

  1. Register a custom URL. Universal links aren’t supported.
  2. Configure your custom URL.
  3. Set up your root component to forward the URL to the Stripe SDK as shown below.

Note

If you’re using Expo, set your scheme in the app.json file.

App.tsx
import React, { useEffect, useCallback } from 'react'; import { Linking } from 'react-native'; import { useStripe } from '@stripe/stripe-react-native'; export default function MyApp() { const { handleURLCallback } = useStripe(); const handleDeepLink = useCallback( async (url: string | null) => { if (url) { const stripeHandled = await handleURLCallback(url); if (stripeHandled) { // This was a Stripe URL - you can return or add extra handling here as you see fit } else { // This was NOT a Stripe URL – handle as you normally would } } }, [handleURLCallback] ); useEffect(() => { const getUrlAsync = async () => { const initialUrl = await Linking.getInitialURL(); handleDeepLink(initialUrl); }; getUrlAsync(); const deepLinkListener = Linking.addEventListener( 'url', (event: { url: string }) => { handleDeepLink(event.url); } ); return () => deepLinkListener.remove(); }, [handleDeepLink]); return ( <View> <AwesomeAppComponent /> </View> ); }

For more information on native URL schemes, refer to the Android and iOS docs.

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 comply 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 confirmSetup to confirm the intent.

import {confirmSetup} from '@stripe/stripe-react-native'; export default function MyPaymentScreen() { const [name, setName] = useState(''); const handleCollectBankAccountPress = async () => { // See above }; const handlePayPress = async () => { // use the same clientSecret as earlier, see above const {error, setupIntent} = await confirmSetup(clientSecret, { type: 'USBankAccount', }); if (error) { Alert.alert(`Error code: ${error.code}`, error.message); } else if (setupIntent) { if (setupIntent.status === SetupIntents.Status.Processing) { // The debit has been successfully submitted and is now processing } else if ( setupIntent.status === SetupIntents.Status.RequiresAction && setupIntent?.nextAction?.type === 'verifyWithMicrodeposits' ) { // The payment must be verified with `verifyMicrodepositsForPayment` } else { Alert.alert('Payment status:', setupIntent.status); } } }; return ( <PaymentScreen> <TextInput placeholder="Name" onChange={(value) => setName(value.nativeEvent.text)} /> <Button onPress={handleCollectBankAccountPress} title="Collect bank account" /> <Button onPress={handlePayPress} title="Pay" /> </PaymentScreen> ); }

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 wasn’t necessary.No action needed
RequiresActionBank account verification requires further action.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 microdeposits
Client-side

Not all customers can verify their bank account instantly. In these cases, Stripe sends a microdeposit to the bank account. This deposit might take up to 1-2 business days to appear on the customer’s online statement. This deposit takes one of two shapes:

  • Descriptor code. Stripe sends a single, 0.01 USD microdeposit to the customer’s bank account with a unique, 6-digit descriptorCode 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.

If the result of the confirmSetup method call in the previous step is a SetupIntent with a requiresAction status, the SetupIntent contains a next_action field that contains some useful information for completing the verification.

nextAction: { type: 'verifyWithMicrodeposits'; redirectUrl: "https://payments.stripe.com/…", microdepositType: "descriptor_code"; arrivalDate: "1647586800"; }

If you supplied a billing email, Stripe uses this email to notify your customer when we expect the deposits 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.

When you know the payment is in the requiresAction state and the nextAction is of type verifyWithMicrodeposits, you can complete verification of a bank account in two ways:

  1. Verify the deposits directly in your app by calling verifyMicrodepositsForSetup after collecting either the descriptor code or amounts.
import { verifyMicrodepositsForSetup } from '@stripe/stripe-react-native'; export default function MyPaymentScreen() { const [verificationText, setVerificationText] = useState(''); return ( <TextInput placeholder="Descriptor code or comma-separated amounts" onChange={(value) => setVerificationText(value.nativeEvent.text)} // Validate and store your user's verification input /> <Button title="Verify microdeposit" onPress={async () => { const { paymentIntent, error } = await verifyMicrodepositsForSetup(secret, { // Provide either the descriptorCode OR amounts, not both descriptorCode: verificationText, amounts: verificationText, }); if (error) { Alert.alert(`Error code: ${error.code}`, error.message); } else if (paymentIntent) { Alert.alert('Payment status:', paymentIntent.status); } }} /> ); }
  1. Use the Stripe-hosted verification page that we automatically provide for you. To do this, use the nextAction[redirectUrl] URL in the nextAction object (see above) to direct your customer to complete the verification process.
const {error, setupIntent} = await confirmSetup(clientSecret, { type: 'USBankAccount', }); if (error) { Alert.alert(`Error code: ${error.code}`, error.message); } else if (setupIntent) { if ( setupIntent.status === SetupIntents.Status.RequiresAction && setupIntent?.nextAction?.type === 'verifyWithMicrodeposits' ) { // Open the Stripe-hosted verification page Linking.openURL(setupIntent.nextAction.redirectUrl); } else { Alert.alert('Payment status:', setupIntent.status); } }

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 might happen synchronously as a direct error response, or asynchronously through a setup_intent.setup_failed webhook event (shown in the following examples).

{ "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 CodeSynchronous or AsynchronousMessageStatus change
payment_method_microdeposit_failedSynchronously, or asynchronously through webhook eventMicrodeposits failed. Please check the account, institution and transit numbers providedstatus is requires_payment_method, and last_setup_error is set.
payment_method_microdeposit_verification_amounts_mismatchSynchronouslyThe amounts provided don’t match the amounts that we sent to the bank account. You have {attempts_remaining} verification attempts remaining.Unchanged
payment_method_microdeposit_verification_attempts_exceededSynchronously, or asynchronously through webhook eventExceeded the number of allowed verification attemptsstatus is requires_payment_method, and last_setup_error is set.
payment_method_microdeposit_verification_timeoutAsynchronously through a webhook eventMicrodeposit timeout. The 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

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 creates a new PaymentMethod attached to a Customer. You can use these 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