Set up a subscription with ACH Direct Debit
Learn how to create and charge for a subscription with US bank account.
Use the Accounts v2 API to represent customers
The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a preview version in your code.
To join the Accounts v2 preview, go to Account previews and features in your Dashboard and enable Reusable payment methods for Global Payouts.
For most use cases, we recommend modeling your customers as customer-configured Account objects instead of using Customer objects.
Use this guide to set up a subscription using ACH Direct Debit.
Note
If you’re a new user, use the Payment Element instead of using Stripe Elements as described in this guide. The Payment Element provides a low-code integration path with built-in conversion optimizations. For instructions, see Build a subscription.
Create a product and price Dashboard
Products represent the item or service you’re selling. Prices define how much and how frequently you charge for a product. This includes how much the product costs, what currency you accept, and whether it’s a one-time or recurring charge. If you only have a few products and prices, create and manage them in the Dashboard.
This guide uses a stock photo service as an example and charges customers a 15 USD monthly subscription. To model this:
- Go to the Products page and click Create product .
- Enter a Name for the product. You can optionally add a Description and upload an image of the product.
- Select a Product tax code . Learn more about product tax codes .
- Select Recurring . Then enter 15 for the price and select USD as the currency.
- Choose whether to Include tax in price . You can either use the default value from your tax settings or set the value manually. In this example, select Auto .
- Select Monthly for the Billing period .
- Click More pricing options . Then select Flat rate as the pricing model for this example. Learn more about flat rate and other pricing models .
- Add an internal Price description and Lookup key to organize, query, and update specific prices in the future.
- Click Next . Then click Add product .
After you create the product and the price, record the price ID so you can use it in subsequent steps. The pricing page displays the ID and it looks similar to this: price_G0FvDp6vZvdwRZ.
Create the subscription Server-side
Note
To create a subscription with a free trial period, see Subscription trials.
Create a subscription with the price and customer with status incomplete by providing the payment_behavior parameter with the value of default_incomplete.
Command Line
Select a language
curl
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
The response includes the subscription ’s first Invoice. This contains the invoice’s payments, which includes a default PaymentIntent that Stripe generated for this invoice and the confirmation secret which you can use on the client side to securely complete the payment process instead of passing the entire PaymentIntent object. Return the latest_invoice.confirmation_secret.client_secret to the front end to complete payment.
Collect payment method details Client-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.
checkout.html
Create an instance of Stripe.js with the following JavaScript on your checkout page.
client.js
// 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 PaymentIntent 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.collectBankAccountForPayment to collect bank account details with Financial Connections, create a PaymentMethod, and attach that PaymentMethod to the PaymentIntent. Including the account holder’s name in the billing_details parameter is required to create an ACH Direct Debit PaymentMethod.
If the merchant of record is outside the US, include a complete payment_method_data.billing_details.address.
script.js
// 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.collectBankAccountForPayment({
clientSecret: clientSecret,
params: {
payment_method_type: 'us_bank_account',
payment_method_data: {
billing_details: {
name: accountHolderNameField.value,
email: emailField.value,
// Required if the merchant of record is outside the US.
address: {
line1: '123 Main Street',
city: 'San Francisco',
state: 'CA',
postal_code: '94111',
country: 'US',
},
},
},
},
expand: ['payment_method'],
})
.then(({paymentIntent, error}) => {
if (error) {
console.error(error.message);
// PaymentMethod collection failed for some reason.
} else if (paymentIntent.status === 'requires_payment_method') {
// Customer canceled the hosted verification modal. Present them with other
// payment method type options.
} else if (paymentIntent.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 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 PaymentIntent, and creates a Financial Connections Account.
Common mistake
Bank accounts that your customers link through manual entry and microdeposits won’t have access to additional bank account data like balances, ownership, and transactions.
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 submit Client-side
Before you can initiate the payment, you must obtain authorization from your customer by displaying mandate terms for them 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 PaymentIntent. Use stripe.confirmUsBankAccountPayment to complete the payment when the customer submits the form.
script.js
confirmationForm.addEventListener('submit', (ev) => {
ev.preventDefault();
stripe.confirmUsBankAccountPayment(clientSecret)
.then(({paymentIntent, error}) => {
if (error) {
console.error(error.message);
// The payment failed for some reason.
} else if (paymentIntent.status === "requires_payment_method") {
// Confirmation failed. Attempt again with a different payment method.
} else if (paymentIntent.status === "processing") {
// Confirmation succeeded! The account will be debited.
// Display a message to customer.
} else if (paymentIntent.next_action?.type === "verify_with_microdeposits") {
// The account needs to be verified through 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 through email).
}
});
});
Note
stripe.confirmUsBankAccountPayment might 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 the customer completes instant verification, the subscription automatically becomes active. Otherwise, see Verify bank account with microdeposits to learn how to handle microdeposit verification while the subscription remains incomplete.
Verify bank account with microdeposits Client-side
Note
Customers have 10 days to successfully verify microdeposits for a subscription, instead of the 23 hours normally given in the subscription lifecycle. However, this expiration can’t be later than the billing cycle date.
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 the related setting . Your customer uses the deposit amounts to verify their bank account.
The result of the stripe.confirmUsBankAccountPayment method call in the previous step is a PaymentIntent in the requires_action state. The PaymentIntent 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 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
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 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 on your site. Your customers then use this form to relay microdeposit amounts to you and verify the bank account using Stripe.js. Integrations only pass in the
descriptor_codeoramounts. To determine which one your integration uses, check the value forverify_with_microdeposits[microdeposit_type]in thenext_actionobject. - 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 might require it.
stripe.verifyMicrodepositsForPayment(clientSecret, {
// Provide either a descriptor_code OR amounts, not both
descriptor_code: 'SMT86W',
amounts: [32, 45],
});
Set the default payment method Server
You now have an active subscription belonging to a customer with a payment method, but this payment method isn’t automatically used for future payments. To automatically bill this payment method in the future, use a webhook consumer to listen to the invoice.payment_succeeded event for new subscriptions and set the default payment method.
server.rb
Select a language
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new('sk_test_BQokikJOvBiI2HlWgH4olfQ2')
if event.type == 'invoice.payment_succeeded'
invoice = event.data.object
if invoice['billing_reason'] == 'subscription_create'
subscription_id = invoice['parent']['subscription_details']['subscription']
# This example assumes you're using the default PaymentIntent that Stripe generated for the invoice.
invoice_payments = client.v1.invoice_payments.list({invoice: invoice['id']})
payment_intent_id = invoice_payments.data[0].payment.payment_intent
# Retrieve the payment intent used to pay the subscription
payment_intent = client.v1.payment_intents.retrieve(payment_intent_id)
# Set the default payment method
client.v1.subscriptions.update(
subscription_id,
default_payment_method: payment_intent.payment_method
)
end
end
Manage subscription status Client-side
A subscription can become active while its first payment is still processing, so don’t use the subscription status alone to determine whether the payment succeeded. Before fulfilling goods or services, wait for the invoice.paid event or confirm that the underlying PaymentIntent has a succeeded status, and verify that the related subscription is active.
When payments fail, the status changes to the Subscription status configured in your automatic collection settings. Notify the customer after a failure and charge them with a different payment method.
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.
If your domain is {domain} and your username is {username}, use the following email format to send test transaction emails: {username}+test_email@{domain}.
For example, if your domain is example.com and your username is info, use the format **** for testing ACH Direct Debit payments. This format ensures that emails route correctly. If you don’t include the +test_email suffix, Stripe won’t send the email.
Common mistake
You must set up 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 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. |
000000004954 | pm_usBankAccount_riskLevelHighest | 110000000 | The payment is blocked by Radar due to a high risk of fraud. |
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. |
000000000009 | pm_usBankAccount_processing | 110000000 | The payment stays in processing indefinitely. Useful for testing PaymentIntent cancellation. |
000777777771 | pm_usBankAccount_weeklyLimitExceeded | 110000000 | The payment fails due to payment amount causing the account to exceed its weekly payment volume limit. |
000888888885 | 110000000 | The payment fails because of a deactivated tokenized account number. |
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 in the following table.
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 | the related setting | Simulates verifying the account. |
10 and 11 | the related setting | Simulates exceeding the number of allowed verification attempts. |
40 and 41 | the related setting | Simulates a microdeposit timeout. |
Test settlement behavior
Test transactions settle instantly and are added to your available test balance. This behavior differs from live mode, where transactions can take multiple days to settle in your available balance.
