Payment failure handling with grace period
Why it matters
Immediately locking out a user on first payment failure destroys retention for what is often a recoverable situation — expired cards, temporary bank blocks, and 3DS challenges account for a large share of first-failure declines. Stripe's Smart Retries window is typically 4 attempts over 7–28 days. Without handling invoice.payment_failed, you either lose customers unnecessarily on first failure, or retain them indefinitely on final failure because you never processed the cancellation event. ISO 25010 fault-tolerance requires the system to degrade gracefully during partial failures.
Severity rationale
Medium because the failure degrades revenue recovery and user experience significantly but does not create a security vulnerability or data loss.
Remediation
Handle invoice.payment_failed to notify the user without revoking access immediately. Only revoke when Stripe moves the subscription to past_due or canceled after exhausting retries.
case 'invoice.payment_failed': {
const invoice = event.data.object
await db.user.update({
where: { stripe_customer_id: invoice.customer as string },
data: { payment_failed_at: new Date() }
})
// Trigger an email: "Your payment failed — please update your card"
// Do NOT revoke access here — Stripe will retry
break
}
Also handle customer.subscription.updated where status === 'past_due' to show an in-app banner, and revoke access only when status === 'canceled'. Enable Stripe Smart Retries and dunning emails in your Stripe Dashboard to handle the retry cadence automatically.
DetectionPro
The detection method for this pattern is part of AuditBuffet Pro.
The full method (what to look for, pass and fail conditions, and how results get reported) ships inside the Billing & Payments Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- iso-25010:2011 · fault-tolerance — Fault Tolerance — system continues to operate correctly under payment failure conditions
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-billing·automated