Subscription Billing via ACH Credit Transfers Preview
Allow customers to manually pay subscription invoices using ACH credit transfers instead of being automatically billed to a stored credit card or bank account.
By default, Stripe automatically attempts to pay subscription invoices using a customer’s default stored payment method, normally a credit card. But self-serve credit card payments aren’t always the most appropriate billing method, such as with business-to-business transactions. Stripe also supports the ability to send your customer an invoice that they can pay on their own schedule. If you are a United States user charging your customers in USD, Stripe will automatically reconcile these invoices via ACH credit transfers.
Using this approach, your customers receive an invoice via email, which they can pay on their own schedule before a due date you set. Stripe still manages the dunning and status tracking of these invoices, as with any other subscription.
The lifecycle differs slightly for the two approaches, but requires only one simple change in how the subscription is created.
Creating subscriptions for manual payment
To create a subscription to be paid manually via ACH credit transfers, perform a create subscription request, providing two additional arguments:
- A
billingvalue of send_invoice (the default value is charge_automatically) - A
days_until_duevalue as a positive integer
The days_until_due value is added to each invoice’s creation date to determine when the invoice becomes past due (if unpaid).
curl https://api.stripe.com/v1/subscriptions \
-u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
-d customer=cus_4fdAW5ftNQow1a \
-d items[0][plan]=basic-monthly \
-d billing=send_invoice \
-d days_until_due=30
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
Stripe::Subscription.create(
:customer => "cus_4fdAW5ftNQow1a",
:items => [
{
:plan => "basic-monthly",
},
],
:billing => "send_invoice",
:days_until_due => 30,
)
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
stripe.Subscription.create(
customer="cus_4fdAW5ftNQow1a",
items=[
{
"plan": "basic-monthly",
},
],
billing="send_invoice",
days_until_due=30,
)
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
\Stripe\Subscription::create(array(
"customer" => "cus_4fdAW5ftNQow1a",
"items" => array(
array(
"plan" => "basic-monthly",
),
),
"billing" => "send_invoice",
"days_until_due" => 30,
));
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
Map<String, Object> item = new HashMap<String, Object>();
item.put("plan", "basic-monthly"");
Map<String, Object> items = new HashMap<String, Object>();
items.put("0", item);
Map<String, Object> params = new HashMap<String, Object>();
params.put("customer", "cus_4fdAW5ftNQow1a");
params.put("items", items);
params.put("billing", "send_invoice");
params.put("days_until_due", 30);
Subscription subscription = Subscription.create(params);
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
stripe.subscriptions.create({
customer: "cus_4fdAW5ftNQow1a",
items: [
{
plan: "basic-monthly",
},
],
billing: "send_invoice",
days_until_due: 30,
}, function(err, subscription) {
// asynchronously called
});
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.Key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
params := &stripe.SubParams{
Customer: "cus_4fdAW5ftNQow1a",
Items: []*stripe.SubItemsParams{
{
Plan: "pro-monthly",
},
},
Billing: "send_invoice",
DaysUntilDue: 30,
}
subscription, err := sub.New(params)
When creating subscriptions in the Dashboard, select Email invoices for customer to pay manually under Billing, and click Change to enter a days_until_due value other than the default of 30 days.
ACH credit transfers billing lifecycle
The default subscription type—with a billing value of charge_automatically, billed to a stored payment method—has these steps in its lifecycle:
- The invoice is created.
- After an hour or so, Stripe automatically attempts to pay the invoice using a stored payment method.
- The invoice becomes
paidor, if the payment attempt fails, the subscription becomespast_due(although there are other states, depending upon your settings).
In this flow, Stripe never notifies the customer being billed about the invoice and a payment is automatically attempted on the invoice made shortly after its generation. (The customer will receive an email receipt if you have that enabled.)
Alternatively, when accepting manual payments on subscriptions via ACH credit transfers:
- The invoice is created with a set due date.
- After an hour or so, Stripe sends the invoice to the customer via email.
- At some later point, the customer pays the invoice by sending an ACH credit transfer and the invoice becomes
paid. - If the customer does not pay the invoice before the due date, the subscription becomes
past_due.
This lifecycle also introduces a new event: invoice.sent, triggered when Stripe emails the invoice to the customer. As with automatically billed invoices, you can modify an open invoice after receiving the invoice.created event (but before invoice.sent).
Paying an invoice
When the subscription or invoice is created, if the customer does not already have an ach_credit_transfer source, Stripe will automatically create one for them. When an invoice is sent to the customer, it will include instructions on where to send payment. The payment address included will be unique for each customer, but shared across all the customer’s invoices. Your customers will be able to transfer funds through either the US ACH system or US domestic wires.
Once the transfer is made, Stripe matches the payment to an invoice by fulfilling the oldest outstanding invoice of the same amount. When that match is made, an invoice.payment_succeeded event occurs that you can receive via your webhooks.
ACH credit transfers that do not match an invoice amount will not be charged; they will remain in the source object. If you wish to use these funds to fulfill your invoice, you have a few options:
- If there is enough money in the receiver to pay your invoice, you can claim those funds by clicking the Pay now button on the invoice in the Dashboard or calling the pay invoice endpoint and specifying the ACH credit transfer object as the source
- If there are insufficient funds to pay the invoice, you can ask your customer to send the remaining amount, or close the old invoice and open a new invoice for the lesser amount and immediately click Pay now on the new invoice
You can inspect the status of any ACH credit transfer by viewing the sources list for the customer, either via the Dashboard or the API:
curl https://api.stripe.com/v1/customers/cus_9jWC3097MQwYwF/sources \
-u sk_test_BQokikJOvBiI2HlWgH4olfQ2:
You should see a response like:
{
"object": "list",
"data": [
{
"id": "src_19Q3AILlRB0eXbMt81RVDnM9",
"object": "source",
"amount": null,
"client_secret": "src_client_secret_Z0zPIgnR0BVafiMLaJcxI3HS",
"created": 1481585102,
"currency": "usd",
"customer": "cus_9jWC3097MQwYwF",
"flow": "receiver",
"livemode": false,
"metadata": {},
"owner": {
"address": null,
"email": "[email protected]",
"name": null,
"phone": null,
"verified_address": null,
See all 45 lines
"verified_email": null,
"verified_name": null,
"verified_phone": null
},
"receiver": {
"address": "110000000-test_12e2b7d44ea6",
"amount_charged": 0,
"amount_received": 0,
"amount_returned": 0,
"refund_attributes_method": "email",
"refund_attributes_status": "missing"
},
"status": "chargeable",
"type": "ach_credit_transfer",
"usage": "reusable",
"ach_credit_transfer": {
"account_number": "test_12e2b7d44ea6",
"fingerprint": "3eoX8Ufbxh0oVDim",
"routing_number": 110000000
}
}
],
"has_more": false,
"url": "/v1/customers/cus_9jWC3097MQwYwF/sources"
}
That particular response shows the ACH credit transfer receiver is awaiting payment from the customer.
Sometimes customers may want to pay with payment methods outside of Stripe, such as check. In these situations, Stripe still allows you to keep track of the payment status of your invoices. Once you receive an invoice payment from a customer outside of Stripe, you can manually mark their invoices as paid.
curl https://api.stripe.com/v1/invoices/in_18jwqyLlRB0eXbMtrUQ97YBw \
-u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
-d paid=true
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
invoice = Stripe::Invoice.retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw")
invoice.paid = true
invoice.save
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
invoice = stripe.Invoice.retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw")
invoice.paid = True
invoice.save()
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
$invoice = \Stripe\Invoice::retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw");
$invoice->paid = true;
$invoice->save();
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
Invoice invoice = Invoice.retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw");
Map<String, Object> updateParams = new HashMap<String, Object>();
updateParams.put("paid", "true");
invoice.update(updateParams);
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
stripe.invoices.update("in_18jwqyLlRB0eXbMtrUQ97YBw", {
paid: true,
});
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.Key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
invoice.Update("in_18jwqyLlRB0eXbMtrUQ97YBw", &stripe.InvoiceParams{Paid: true})
Email settings and invoice details
You can configure the look of the email sent to your customer by editing the email template in your account’s business settings.
In your subscription settings, you can:
- Set the schedule for sending emails
- Set a schedule for Stripe to send reminder emails for unpaid invoices
- Disable automatic sending of invoice emails, in which case you’ll need to send them yourself (via a button in the Dashboard when looking at an invoice)
Note that the settings for invoices sent to a customer for manual payment are separate—and can differ—from the settings for invoices charged automatically.
The email Stripe sends to your customer includes all appropriate invoice details, such as:
- Your business address
- The customer’s address
- Any invoice line items
- The final payment amount due
- The due date
- The ACH details
The line items and payment amount include the properties of the subscription as well as any one-off items added to the invoice. You can also add a note to the invoice specifying any custom payment directions or other message by updating the invoice’s description (via the Dashboard or API).
Testing ACH credit transfer payments of invoices
Most of Stripe’s functionality is designed to behave the same in both test and live modes. With manual ACH credit transfer payments of invoices, the main difference is that in test mode we will fire the invoice.sent webhook according to the schedule in your settings, but the emails will not actually be sent. If you wish to actually send a test mode invoice, you can click the Send button when you view the invoice in the Dashboard:
To test manual payments on invoices via ACH credit transfers:
- Create the subscription, using send_invoice for the
billingparameter (or using the Dashboard). - After the invoice is created, find it in the Dashboard and click Send to send the email.
- Identify the newly created
ach_credit_transfersource on theCustomerobject (or customer in the Dashboard). - Update the owner email on the created source to amount_XXXX@any_domain.com, where XXXX is an integer representing the amount of money you want to simulate pushing (e.g., an invoice for $149.35 matches an owner email value of [email protected]).
The second step is not required, but only non-editable invoices can be paid, and this is the fastest way to move an invoice to that state. Step 4 is represented by this code:
curl https://api.stripe.com/v1/sources/src_19Q3AILlRB0eXbMt81RVDnM9 \
-u sk_test_BQokikJOvBiI2HlWgH4olfQ2: \
-d owner[email]="[email protected]"
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
source = Stripe::Source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9")
source.owner = { :email => "[email protected]" }
source.save
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
source = stripe.Source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9")
source.owner = { :email => "[email protected]" }
source.save()
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
$source = \Stripe\Source::retrieve("src_19Q3AILlRB0eXbMt81RVDnM9");
$source->owner = array("email" => "[email protected]");
$source->save();
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
Source source = Source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9");
Map<String, Object> ownerParams = new HashMap<String, Object>();
ownerParams.put("email", "[email protected]");
Map<String, Object> params = new HashMap<String, Object>();
params.put("owner", ownerParams);
source.update(params);
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
stripe.sources.update("src_19Q3AILlRB0eXbMt81RVDnM9", {
owner: {
email: "[email protected]",
}
}, function(err, source) {
// asynchronously called
});
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.Key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
params := &stripe.SourceObjectParams{
Owner: &stripe.OwnerParams {
Email: "[email protected]",
}
}
source, err := source.update("src_19Q3AILlRB0eXbMt81RVDnM9", params)
A few moments after performing the update request, you can retrieve the receiver:
curl https://api.stripe.com/v1/sources/src_19Q3AILlRB0eXbMt81RVDnM9 \
-u sk_test_BQokikJOvBiI2HlWgH4olfQ2:
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
source = Stripe::Source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9")
# Set your secret key: remember to change this to your live secret key in production
# See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
source = stripe.Source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9")
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
\Stripe\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
$source = \Stripe\Source::retrieve("src_19Q3AILlRB0eXbMt81RVDnM9");
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
Stripe.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
Source source = Source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9");
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
var stripe = require("stripe")("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
stripe.sources.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9", function(err, source) {
// asynchronously called
});
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
stripe.Key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
source, err := source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9")
You should see it filled with funds:
{
"object": "list",
"data": [
{
"id": "src_19Q3AILlRB0eXbMt81RVDnM9",
"object": "source",
"amount": null,
"client_secret": "src_client_secret_Z0zPIgnR0BVafiMLaJcxI3HS",
"created": 1481585102,
"currency": "usd",
"customer": "cus_9jWC3097MQwYwF",
"flow": "receiver",
"livemode": false,
"metadata": {},
"owner": {
"address": null,
"email": "[email protected]",
"name": null,
"phone": null,
"verified_address": null,
"verified_email": null,
"verified_name": null,
"verified_phone": null
},
"receiver": {
"address": "110000000-test_12e2b7d44ea6",
"amount_charged": 1000,
"amount_received": 1000,
"amount_returned": 0,
"refund_attributes_method": "email",
See all 45 lines
"refund_attributes_status": "missing"
},
"status": "chargeable",
"type": "ach_credit_transfer",
"usage": "reusable",
"ach_credit_transfer": {
"account_number": "test_12e2b7d44ea6",
"fingerprint": "3eoX8Ufbxh0oVDim",
"routing_number": 110000000
}
}
],
"has_more": false,
"url": "/v1/customers/cus_9jWC3097MQwYwF/sources"
}
You should also see the customer’s oldest invoice for the same amount transition to paid, with a corresponding Payment object appearing in your account, displaying the payment details.
Overdue payments
Depending on your settings, after an invoice passes its due date Stripe will continue to remind your customer to pay the invoice. If payment is still not made after the number of days specified in your settings, Stripe transitions the subscription to unpaid or canceled.
You can check the status of your invoices via the List Invoices API endpoint, or the Dashboard, which provides a range of options for filtering your invoices.
Next steps
Congrats! You’ve learned how to manually pay invoices using ACH credit transfers. You may also want to read:

