Skip to content
Blog

The payment failed, the invoice is open, and Stripe never retried

An invoice has been sitting open for five weeks. The first payment attempt failed and the timeline after it is empty. No retry and no email. The customer says their card has worked fine the whole time.

Nothing is broken in a way that produced an error, because what went wrong is an absence.

Why there is no next attempt

next_payment_attempt on the invoice holds the time payment will next be attempted. Null means Stripe has nothing scheduled. Getting to null happens several different ways, and there is one way to get a date that never fires.

The retry schedule ran to its end. Stripe retries on either a Smart Retries policy or a custom schedule of up to three retries, and after the final attempt it makes no further attempts. What happens then is an account setting with three options. Cancel the subscription, mark it unpaid, or leave it past due. The third leaves the invoice open and uncollected for as long as nobody looks.

There is no payment method to retry. Stripe does not retry at all when none is available, and the schedule simply has nothing to run against.

The schedule exists and nothing executes it. This is the case that looks least like a failure. On a hard decline from the issuer, lost_card, stolen_card and seven other codes, Stripe cannot retry without a new payment method, and its documented behaviour is that the retries stay scheduled and attempt_count keeps incrementing while the retries only execute once a new payment method appears. Unexecuted retries create no Charge. So the counter climbs, next_payment_attempt holds a date, and no money moves. An invoice in this state is indistinguishable from a healthy one on both fields you would think to check.

The invoice is not on automatic collection. With collection_method set to send_invoice, Stripe emails the invoice and waits for the customer to pay it. next_payment_attempt is null on those invoices by definition. There is no saved payment method being retried because retrying was never the plan.

The invoice was never handed to Stripe to drive. auto_advance controls whether Stripe performs automatic collection, and with it false the invoice’s state does not advance without an explicit action from you. An invoice created through the API that way stays a draft.

Collection is paused. pause_collection keeps the subscription active and keeps invoices generating while Stripe collects nothing. Under the keep_as_draft behavior those invoices are created as drafts with auto_advance false, waiting for a person. It outlives the reason it was set, especially when it was set by hand during a support conversation.

The part that defeats event-driven code

The first failure emitted invoice.payment_failed. That is the last thing your webhook handler hears. A retry that never gets scheduled emits nothing, and if the account is set to leave the subscription past due, no subscription event follows either. An integration built entirely around incoming events is waiting for a message that will not arrive. The only evidence lives in a field on an object nobody has a reason to re-read.

Billing automations move that field too. With automations turned on, next_payment_attempt is no longer set on invoice.payment_failed and is set on invoice.updated instead, so a handler reading it off the failure event reads null.

Confirming it

List invoices with status open and read collection_method, auto_advance, attempted and next_payment_attempt on each one. An invoice on automatic collection, older than your full retry window, with nothing scheduled, has stopped. It will stay stopped.

Then open Billing, Revenue recovery, Retries in the Dashboard and read what your account does after the final attempt. It was chosen once during setup, and “leave the subscription past due” is the option that leaves invoices open with nothing chasing them.

Fixing it

Decide the terminal behavior on purpose. Cancelling the subscription or marking the invoice uncollectible both leave a legible record of what happened. Leaving invoices open forever does not, and it is the setting that makes this failure invisible.

For invoices already stranded, get the customer onto a working payment method and pay the invoice directly, or void it and reissue if the amount is no longer right. Voiding works on an invoice that is open or uncollectible, and it is terminal. Set auto_advance true on invoices you create through the API and expect Stripe to collect.

An unretried failure is revenue you invoiced correctly and then never chased.