Subscription Billing via ACH Credit Transfers Public Beta

    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 automatically reconciles 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 billing value of send_invoice (the default value is charge_automatically)
    • A days_until_due value 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:

    1. The invoice is created.
    2. After an hour or so, Stripe automatically attempts to pay the invoice using a stored payment method.
    3. The invoice becomes paid or, if the payment attempt fails, the subscription becomes past_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 receives an email receipt if you have that enabled.)

    Alternatively, when accepting manual payments on subscriptions via ACH credit transfers:

    1. The invoice is created with a set due date.
    2. After an hour or so, Stripe sends the invoice to the customer via email.
    3. At some later point, the customer pays the invoice by sending an ACH credit transfer and the invoice becomes paid.
    4. 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, Stripe creates an ach_credit_transfer source if the customer does not already have one. When an invoice is sent to the customer, it includes instructions on where to send payment. The payment address is unique for each customer, but shared across all the customer’s invoices. Your customers can then transfer funds through either the U.S. ACH system or domestic wire.

    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 are not charged and 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:
    

    Stripe returns a list of sources attached to that customer. The source type for ACH Credit Transfer has a value of ach_credit_transfer.

    {
      "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": "jenny.rosen@example.com",
            "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 send the invoice.sent webhook event according to the schedule in your settings, but the emails are not actually sent. If you wish to 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:

    1. Create the subscription, using send_invoice for the billing parameter (or using the Dashboard).
    2. After the invoice is created, find it in the Dashboard and click Send to send the email.
    3. Identify the newly created ach_credit_transfer source on the Customer object (or customer in the Dashboard).
    4. 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 amount_14935@example.com).

    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]="amount_1000@example.com"
    
    # 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 => "amount_14935@example.com" }
    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 => "amount_14935@example.com" }
    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" => "amount_14935@example.com");
    $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", "amount_14935@example.com");
    
    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: "amount_14935@example.com",
      }
    }, 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: "amount_14935@example.com",
      }
    }
    
    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": "amount_1000@test.com",
            "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 continues 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.

    Refunding payments

    ACH credit transfer payments can be refunded through either the Dashboard or API. However, the account information that the funds should be returned to needs to be provided by the customer. We automatically contact the customer at the email address provided. Once the customer provides us with their account information, we process the refund automatically.

    The refund’s initial status is pending. If it fails, the charge.refund.updated event is sent and its status transitions to failed. This means that we have been unable to process the refund, and you must return the funds to your customer outside of Stripe. This is a rare occurrence and can happen if the account the refund is being sent to has been frozen. Refunds that have been completed have the status succeeded.

    Next steps

    Congrats! You’ve learned how to manually pay invoices using ACH credit transfers. You may also want to read: