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 (wires or bank transfers) from your customers to pay open invoices.

    For each of your customers, Stripe autogenerates a US virtual bank account number that can be paid with ACH credit or wires in USD. 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 payment sent to 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 or manually reconcile payments to your bank account with open invoices.

    With autoreconciliation for invoices, Stripe manages:

    • matching incoming payments with invoice amounts
    • manages the case of over or under payment, when the paid amount doesn’t match the invoice
    • minimizes the API calls you need to make to receive funds paid to an ACH-CT source
    • manages dunning and retries of open invoices

    To create a one-time that supports push payment, use the invoice creation flow in the Dashboard.

    Creating subscriptions for payment via an invoice

    To create a subscription that emails invoices to a user 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 unpaid).

    This is also possible in the Dashboard, during the subscription creation process.

    Invoices which are paid manually follow a slightly different lifecycle than automatically-paid invoices.

    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.

    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.

    In some cases, 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"
    
    Stripe::Invoice.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.api_key = "sk_test_BQokikJOvBiI2HlWgH4olfQ2"
    
    stripe.Invoice.modify('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\Stripe::setApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    \Stripe\Invoice::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.apiKey = "sk_test_BQokikJOvBiI2HlWgH4olfQ2";
    
    Invoice invoice = Invoice.retrieve("in_18jwqyLlRB0eXbMtrUQ97YBw");
    Map<String, Object> params = new HashMap<>();
    params.put("paid", true);
    invoice.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");
    
    const invoice = 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"
    
    params := &stripe.InvoiceParams{
      Paid: stripe.Bool(true),
    }
    inv, _ := invoice.Update("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_BQokikJOvBiI2HlWgH4olfQ2");
    
    var options = new StripeInvoiceUpdateOptions {
        Paid = true,
    };
    var service = new StripeInvoiceService();
    StripeInvoice invoice = service.Update("in_18jwqyLlRB0eXbMtrUQ97YBw", options);
    

    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 Billing settings section of the Dashboard, you have the ability to 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: 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 (usually within $20) is acceptable, you can pre-specify this 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 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 and you choose not to forgive the difference, 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

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

    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.

    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. You can initiate sending an email immediately by clicking “Send” on the invoice in the Dashboard.

    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"
    
    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_BQokikJOvBiI2HlWgH4olfQ2"
    
    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_BQokikJOvBiI2HlWgH4olfQ2");
    
    \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_BQokikJOvBiI2HlWgH4olfQ2";
    
    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_BQokikJOvBiI2HlWgH4olfQ2");
    
    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_BQokikJOvBiI2HlWgH4olfQ2"
    
    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_BQokikJOvBiI2HlWgH4olfQ2");
    
    var options = new StripeSourceUpdateOptions {
        Owner = new StripeSourceOwner {
            Email = "amount_1000@example.com",
        }
    };
    var service = new StripeSourceService();
    StripeSource 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_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");
    
    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_BQokikJOvBiI2HlWgH4olfQ2"
    
    src, _ := source.Get("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
    StripeConfiguration.SetApiKey("sk_test_BQokikJOvBiI2HlWgH4olfQ2");
    
    var service = new StripeSourceService();
    StripeSource 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

    Congrats! You’ve learned how Stripe reconciles invoices. You may also want to read:

    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.