Why architecture matters here

The architecture matters because involuntary churn is often the single largest and most fixable leak in a recurring-revenue business. Voluntary churn — customers who consciously cancel — is hard to fix; it requires better product, pricing, or onboarding. Involuntary churn is a plumbing problem: the customer never chose to leave, a charge just failed. A business losing 8% of charges to declines and recovering none of them is effectively handing back 8% of its revenue every billing cycle. A dunning system that recovers 60% of those turns an 8% leak into a 3.2% leak, and because there is no additional cost of goods, that recovered revenue flows almost entirely to the bottom line.

It matters because the timing of a retry dramatically changes whether it succeeds, and only an architecture that treats retry scheduling as a first-class concern can exploit that. Retrying an 'insufficient funds' decline one hour later almost always fails again; retrying it on the 1st or 15th of the month, or a few days after the original attempt, catches the customer after a deposit. Some issuers process retries better at certain times of day. A naive 'retry every 24 hours for a week' schedule leaves recoverable revenue on the table compared to a scheduler informed by decline codes, day-of-month patterns, and issuer behavior.

It matters because the customer experience of a failed payment is delicate. The customer is not a debtor dodging a bill — they are usually a happy user whose card quietly expired. Overwhelming them with five panicked emails in two days, or worse, cutting off their service without warning, converts a minor plumbing hiccup into a reason to actually churn. The notification architecture has to inform without alarming, give a frictionless path to update the card, and escalate gracefully. Getting the tone and cadence right is as much a part of the system as the retry logic.

It matters because payment retries interact with the card networks' health metrics for your merchant account. Every authorization attempt, success or failure, is visible to the acquirer and the networks. A merchant that retries dead cards dozens of times drives up its decline-to-approval ratio, which can trigger higher processing fees, fraud reviews, or in extreme cases account termination. The architecture must therefore cap retries, respect hard declines, and honor network rules (such as the limited number of retries schemes permit for a single mandate) — recovery cannot come at the cost of your ability to process payments at all.

Finally it matters because dunning is only trustworthy if every attempt and transition is recorded with strict idempotency. A retry that accidentally double-charges a customer whose payment actually went through — because a timeout hid a success — is a support nightmare and a refund cost. The ledger that records 'charge X for invoice Y was attempted, with idempotency key K, and here is the outcome' is what lets the system retry safely, report recovery accurately, and never bill twice for one period.

Advertisement

The architecture: every piece explained

The recurring charge and the invoice. Dunning begins when a scheduled renewal produces an invoice and the system attempts to collect it from the stored payment method. The invoice — not the card attempt — is the unit of work: an invoice is 'open' until it is paid or written off, and every dunning attempt is an attempt to settle that one invoice. Anchoring on the invoice, rather than on individual charge attempts, is what keeps the system from double-collecting and gives a clean answer to 'does this customer owe us for this period?'

Decline classification. When the gateway returns a decline, it comes with a reason code (and often a richer network response). The classifier is the brain of the system: it maps each code into a category. Soft declines (insufficient funds, temporary hold, issuer unavailable, do-not-honor without a hard reason) are retryable. Hard declines (stolen/lost card, closed account, invalid account number, revoked mandate) are not — they require a new payment method, so the only useful action is to notify the customer, never to retry the same card. A middle band of ambiguous codes may get a small, cautious number of retries. Misclassifying a hard decline as soft is one of the most expensive bugs in the whole system.

The retry scheduler. For retryable declines, the scheduler decides the next attempt time. Naive systems use fixed backoff (retry in 1, 3, 5, 7 days). Sophisticated ones layer in signals: the specific decline code (funds problems recover on paydays), the customer's historical success pattern, day-of-month effects, issuer-specific timing, and 'smart retry' models that predict the highest-probability window. The scheduler also enforces the hard limits — a maximum number of attempts and a maximum dunning window (say four attempts over two to three weeks) — after which the invoice moves to a terminal state.

The notification engine. In parallel with retries, the system communicates with the customer. A typical cadence: a gentle 'we couldn't process your payment, we'll try again' notice on first failure, a 'please update your card' message midway with a one-click link to a hosted card-update page, and a final 'your subscription will be cancelled' warning before the last attempt. The notices are templated, localized, and rate-limited so a customer never gets a storm of messages. Crucially, the update-card link lets the customer fix the root cause themselves, which is the single most effective recovery action — a fresh, valid card almost always charges.

The terminal states and the ledger. Every invoice ends in recovered (a retry or updated card succeeded, the subscription continues, and the ledger records the successful payment) or exhausted (retries and notices are used up; the subscription is cancelled, downgraded to a free tier, or paused per policy). Each transition — declined, scheduled, retried, notified, recovered, cancelled — is written to an append-only ledger keyed by invoice and carrying the idempotency key of each charge attempt. That ledger is the source of truth for both correctness (no double charges) and analytics (recovery rate, time-to-recover, revenue saved).

Dunning — a failed recurring charge enters a retry state machine that schedules smart retries and customer notices until it recovers or churnsRecurring chargesubscription renewal attemptDeclinehard vs soft decline codeDunning state machineclassifies and schedulesRetry schedulerbackoff + optimal timingCustomer noticeemail / in-app update cardPayment gatewayre-attempt authorizationRecoveredsubscription stays activeExhaustedcancel / downgrade / churnLedger + analyticsrecovery rate, MRR impactcoderetryokgive up
Dunning turns a payment failure into a managed recovery process rather than an instant cancellation. When a recurring charge declines, the gateway returns a decline code that the dunning state machine classifies as a soft failure (insufficient funds, temporary hold, issuer timeout) or a hard failure (closed account, stolen card, invalid number). Soft failures enter a retry scheduler that picks the next attempt time using backoff and issuer-informed heuristics, while notifying the customer to update their card. Each retry re-authorizes through the gateway. The loop ends in one of two terminal states — recovered (the charge succeeds and the subscription continues) or exhausted (retries and notices are used up and the account is cancelled or downgraded). Every transition is written to the ledger so recovery rate and revenue impact are measurable.
Advertisement

End-to-end flow

Trace a real involuntary failure. A customer's monthly plan renews on the 3rd. The billing scheduler generates invoice #8842 for $29 and submits a charge against the card on file. The issuer returns decline code 51 — insufficient funds. This is a soft decline: the customer wants the service, they are just short today.

The dunning state machine receives the failure and consults the classifier, which categorizes code 51 as retryable. It creates a dunning record for invoice #8842 in state 'retrying', attempt count 1. The retry scheduler, knowing that funds problems recover after paydays and that immediate retries almost never work, schedules attempt 2 for three days later. Simultaneously the notification engine sends a low-key email: 'We had trouble processing your payment — no action needed yet, we'll automatically try again in a few days.'

Three days later the scheduler fires attempt 2. The charge goes to the gateway with a fresh idempotency key tied to this attempt, but scoped to invoice #8842 so a success can never be double-applied. This time the card declines again — still insufficient funds. Attempt count is now 2. The scheduler picks a longer gap (four days) and the notification engine escalates: 'We still can't process your payment. Please update your card to avoid interruption,' with a one-click link to the hosted update page.

The customer clicks the link and enters a different card. This is the high-value path: the moment a new, valid payment method is saved, the system triggers an immediate retry rather than waiting for the schedule. The charge on the new card succeeds. The state machine transitions invoice #8842 to 'recovered', writes the successful payment to the ledger, keeps the subscription active, and cancels any pending scheduled retries and further notices. The customer's service never lapsed and they received exactly two, appropriately-toned messages.

Contrast the exhausted path. Had the customer never updated the card and the retries kept failing, attempts 3 and 4 would fire over the following week, each with an escalating notice, the last one warning of cancellation. After the fourth failure inside the dunning window, the state machine transitions invoice #8842 to 'exhausted', cancels or downgrades the subscription per policy, and records the outcome. Analytics later roll up every invoice: what fraction recovered, on which attempt, via retry versus card-update, and how much monthly recurring revenue the dunning system saved versus a baseline of giving up on first decline.