diagram.mmd — flowchart
Payment Retry Logic flowchart diagram

Payment retry logic governs how a billing system responds to a failed charge — specifically, whether and when to attempt the same charge again. Naive retry strategies waste gateway resources and can trigger fraud flags or cause card issuers to permanently block a merchant. Effective retry logic classifies decline codes and applies targeted remediation.

The foundation of retry logic is decline code classification. Card networks return standardized reason codes with each decline, and these codes fall into two broad categories:

- Hard declines: permanent failures that should not be retried. Examples include do-not-honor for a blocked card, invalid card number, card reported stolen, or account closed. Retrying hard declines will always fail and may worsen the merchant's fraud reputation with the issuer. - Soft declines: transient failures that may succeed on retry. Examples include insufficient funds, card temporarily blocked, issuer unavailable, and try again later. These represent temporary conditions that could resolve within hours or days.

For soft declines, the system schedules retries using an exponential backoff schedule — for example, retry after 1 day, 3 days, and 7 days. Each retry attempt should use the same idempotency key pattern to avoid double-charges on network timeouts. Some gateways (notably Stripe with Smart Retries) use ML to identify the optimal retry timing based on historical success rates for similar decline codes.

When a retry succeeds, the invoice is marked paid and the dunning sequence terminates. When all retries are exhausted, the subscription is suspended and the customer is notified to update their payment method. If the customer updates their method during the retry window, an immediate retry should be triggered rather than waiting for the next scheduled attempt. For the broader dunning workflow see Subscription Billing Workflow. The general retry pattern for HTTP calls is covered in Request Retry Logic.

Free online editor
Edit this diagram in Graphlet
Fork, modify, and export to SVG or PNG. No sign-up required.
Open in Graphlet →

Frequently asked questions

Payment retry logic governs whether and when a billing system re-attempts a failed charge. It classifies the decline code to determine if the failure is permanent (hard decline) or transient (soft decline) and applies a scheduled backoff for retryable failures.
Soft declines trigger a series of retry attempts spaced by increasing intervals — for example, after 1 day, 3 days, and 7 days. Each attempt uses the same idempotency key so that network timeouts do not produce double-charges. Some gateways use ML to choose the optimal retry time for a given decline code and cardholder profile.
Never retry hard declines such as `do-not-honor`, `invalid card number`, `card reported stolen`, or `account closed`. Retrying these wastes gateway resources, increases the merchant's hard-decline rate, and can cause card issuers to flag the merchant for suspicious retry behaviour.
Common mistakes include retrying hard declines, not using idempotency keys (causing double-charges on network timeouts), scheduling too many retries in a short window (triggering issuer velocity flags), and not triggering an immediate retry when a customer updates their payment method mid-dunning.
mermaid
flowchart TD A([Charge attempt]) --> B[Submit to payment gateway] B --> C{Gateway response} C -->|Success| D[Mark invoice paid] D --> E([End — subscription renewed]) C -->|Decline| F[Parse decline code] F --> G{Decline type} G -->|Hard decline: stolen card, closed account| H[Do not retry] H --> I[Suspend subscription immediately] I --> J[Notify customer of permanent failure] G -->|Soft decline: insufficient funds, try again| K[Schedule retry with backoff] K --> L{Retry count < max attempts?} L -->|Yes| M[Wait backoff interval] M --> N{Customer updated payment method?} N -->|Yes| B N -->|No| B L -->|No — all retries exhausted| O[Mark subscription past_due] O --> P[Send final dunning notification] P --> Q[Suspend or cancel subscription] J --> R([End]) Q --> R
Copied to clipboard