Auto-reconciliation with Invoices

    Allow customers to pay invoices using push payment methods, while Stripe handles the cash reconciliation.

    Push payments are often used for larger business deals or new business relationships, but can come with a lot of manual work for your team. Stripe makes it easy to accept push payments from your customers to pay open invoices.

    For each of your customers, Stripe auto-generates a U.S. virtual bank account number that can be paid in USD with ACH credit or wires. When this virtual bank account is surfaced to your customer, via an invoice, the customer can send the amount owed to that bank account. Stripe automatically matches the sent payment to both the virtual bank account number and the amount owed on the invoice. Stripe then marks the corresponding invoice as paid.

    The advantage of using Stripe’s approach, as opposed to the usual method of ACH credit payment, is that you don’t need to expose your sensitive bank account details to users, nor manually reconcile open invoices with payments to your bank account.

    With auto-reconciliation for invoices, Stripe takes care of:

    • Matching incoming payments with invoice amounts
    • Managing over- or underpayments, when the paid amount doesn’t match the invoice
    • Minimizing the API calls you must make to receive funds paid to an ACH-CT source
    • Managing payment retries on open invoices

    To create a one-off invoice that supports push payment—via the Stripe Dashboard or API—use the flow outlined in One-off invoices.

    Creating subscriptions for payment via an invoice

    To create a subscription that emails invoices to a customer so that it can be paid 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 it is unpaid.

    Using the Dashboard, you can enter the same settings when creating a subscription. Select the option labeled Email invoices to the customer to pay manually, and enter a number of days for Payment due.

    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 is 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; you can receive this event via your webhooks.

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

    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.

    In some cases, customers might want to pay with payment methods outside of Stripe, such as paper checks. In these situations, Stripe still allows you to keep track of your invoices’ payment status. 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/pay \
       -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
       -d paid_out_of_band=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_4eC39HqLyjWDarjtT1zdp7dc"
    
    invoice = Stripe::Invoice.retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw")
    invoice.pay
    
    # 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_4eC39HqLyjWDarjtT1zdp7dc"
    
    invoice = stripe.Invoice.retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw")
    invoice.pay()
    
    // 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_4eC39HqLyjWDarjtT1zdp7dc");
    
    $invoice = \Stripe\Invoice::retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw");
    $invoice->pay();
    
    // 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_4eC39HqLyjWDarjtT1zdp7dc";
    
    Invoice invoice = Invoice.retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw");
    invoice.pay();
    
    // 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_4eC39HqLyjWDarjtT1zdp7dc");
    
    stripe.invoices.pay("in_18jwqyLlRB0eXbMtrUQ97YBw", function(err, invoice) {
      // 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_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.InvoicePayParams{}
    i, err := invoice.Pay("in_18jwqyLlRB0eXbMtrUQ97YBw", 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
    StripeConfiguration.SetApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    var payOptions = new InvoicePayOptions{};
    var service = new InvoiceService();
    Invoice invoice = service.Pay("in_18jwqyLlRB0eXbMtrUQ97YBw", payOptions);
    

    Handling exceptions

    If the amount your customer has paid does not match an invoice amount, the funds 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:

    • Overpayment: If a user sends more funds than the invoice requests, Stripe automatically marks the invoice as paid, using the funds that match the open invoice. The remaining funds stay in the Source receiver. You can choose to manually apply these funds to an invoice. If there are multiple matching open invoices, Stripe applies the funds to the oldest invoice.
    • Underpayment: In the Dashboard’s Billing settings section, you can specify rules around underpayment. You can specify that within a certain margin for error, Stripe should auto-reconcile invoices and credit the difference to the user.

      Typically, the scenario for underpayment is that a customer’s bank might take funds from the total amount your customer sent. For example, if they chose to send $100 to pay their $100 invoice, the customer’s bank might take $20, leaving you with $80. If this difference (which is usually within $20) is acceptable, you can pre-specify this margin, to minimize manual effort.

    For any other exceptions:

    • If there is enough money in the receiver to pay your invoice, you can claim those funds in the Dashboard by clicking the Charge customer button on the invoice, or by calling the pay invoice endpoint and specifying the ACH credit transfer object as the source.
    • If there are insufficient funds to pay the invoice, and you choose not to forgive the difference, you can ask your customer to send the remaining amount. Or you can close the old invoice, open a new invoice for the lesser amount, and immediately click Charge customer on the new invoice.

    If your customer has an ACH credit transfer source with sufficient funds, or a credit card or bank account on file, you can use those sources to pay the invoice by calling the pay invoice endpoint with the source you want to use.

    Refunding payments

    ACH credit transfer payments can be refunded through either the Dashboard or theAPI. However, the customer must specify the account to which the funds should be returned. Stripe automatically contacts 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 the refund fails, the charge.refund.updated event is sent, and the refund’s 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, which can happen if the refund is being sent to an account that has been frozen. Refunds that have been completed have the status succeeded.

    Testing payment

    If you are in test mode, you can simulate pushing money into the receiver by updating the owner email on the source to amount_XXXX@any_domain.com, where XXXX is the amount of money you want to simulate pushing. Note that the payment will not be associated with the invoice unless the invoice has been frozen from editing. This happens either one hour after webhooks have been delivered, or when an email for the invoice has been sent to the customer. In the Dashboard, you can initiate immediately sending an email by clicking the invoice’s Send invoice button.

    curl https://api.stripe.com/v1/sources/src_19Q3AILlRB0eXbMt81RVDnM9 \
       -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
       -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_4eC39HqLyjWDarjtT1zdp7dc"
    
    Stripe::Source.update('src_19Q3AILlRB0eXbMt81RVDnM9', {
      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_4eC39HqLyjWDarjtT1zdp7dc"
    
    stripe.Source.modify('src_19Q3AILlRB0eXbMt81RVDnM9',
      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\Stripe::setApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    \Stripe\Source::update('src_19Q3AILlRB0eXbMt81RVDnM9', [
        '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.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc";
    
    Source source = Source.retrieve("src_19Q3AILlRB0eXbMt81RVDnM9");
    Map<String, Object> ownerParams = new HashMap<>();
    ownerParams.put("email", "amount_1000@example.com");
    Map<String, Object> params = new HashMap<>();
    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_4eC39HqLyjWDarjtT1zdp7dc");
    
    stripe.sources.update('src_19Q3AILlRB0eXbMt81RVDnM9', {
      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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.SourceObjectParams{
      Owner: &stripe.SourceOwnerParams{
        Email: stripe.String("amount_1000@example.com"),
      },
    }
    src, _ := source.Update("src_19Q3AILlRB0eXbMt81RVDnM9", 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
    StripeConfiguration.SetApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    var options = new SourceUpdateOptions {
        Owner = new SourceOwnerOptions {
            Email = "amount_1000@example.com",
        }
    };
    var service = new SourceService();
    Source source = service.Update("src_19Q3AILlRB0eXbMt81RVDnM9", options);
    

    A few moments after the update request, you can retrieve your receiver:

    curl https://api.stripe.com/v1/sources/src_19Q3AILlRB0eXbMt81RVDnM9 \
       -u sk_test_4eC39HqLyjWDarjtT1zdp7dc:
    
    # 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_4eC39HqLyjWDarjtT1zdp7dc"
    
    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_4eC39HqLyjWDarjtT1zdp7dc"
    
    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_4eC39HqLyjWDarjtT1zdp7dc");
    
    $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_4eC39HqLyjWDarjtT1zdp7dc";
    
    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_4eC39HqLyjWDarjtT1zdp7dc");
    
    const source = stripe.sources.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.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"
    
    params := &stripe.SourceObjectParams{}
    src, _ := source.Get("src_19Q3AILlRB0eXbMt81RVDnM9", 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
    StripeConfiguration.SetApiKey("sk_test_4eC39HqLyjWDarjtT1zdp7dc");
    
    var service = new SourceService();
    Source source = service.Get("src_19Q3AILlRB0eXbMt81RVDnM9");
    

    You should see it fill up with funds:

    {
      "object": "list",
      "data": [
        {
          "id": "src_19Q3AILlRB0eXbMt81RVDnM9",
          "object": "source",
          "amount": null,
          "client_secret": "src_client_secret_Z0zPIgnR0BVafiMLaJcxI3HS",
          "created": 1481585102,
          "currency": "usd",
          "customer": "cus_4fdAW5ftNQow1a",
          "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",
            "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_4fdAW5ftNQow1a/sources"
    }
    

    You should also see the customer’s open invoice (of the same amount) transition to paid, with a corresponding payment object displaying the details of the payment.

    Next steps

    Congratulations! You’ve learned how Stripe reconciles invoices. You might also want to read about:

    Questions?

    We're always happy to help with code or other questions you might have! Search our documentation, contact support, or connect with our sales team. You can also chat live with other developers in #stripe on freenode.

    Was this page helpful? Yes No

    Send

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.