Cancellation flow is complete
Why it matters
A cancellation button that doesn't call the Stripe API leaves the subscription active in Stripe — the user is still charged next month. Conversely, a cancellation that updates Stripe but has no webhook handler leaves your database showing active status after Stripe cancels — former subscribers retain premium access indefinitely. OWASP A01 (Broken Access Control) and CWE-284 (Improper Access Control) cover the access retention side. Failing to provide any self-serve cancellation path is also a compliance concern in many jurisdictions that require equal ease of cancellation as signup.
Severity rationale
High because a broken cancellation flow either continues charging users after they cancel or grants indefinite premium access after cancellation — both are severe business failures.
Remediation
Implement cancellation with end-of-period access (most user-friendly), and ensure both the Stripe API call and the corresponding webhook handler are in place.
// app/api/billing/cancel/route.ts
export async function POST(req: Request) {
const user = await getCurrentUser()
await stripe.subscriptions.update(user.stripe_subscription_id, {
cancel_at_period_end: true
})
await db.user.update({
where: { id: user.id },
data: { cancel_at_period_end: true }
})
return Response.json({ message: 'Subscription will cancel at period end' })
}
Then in your webhook handler, process customer.subscription.deleted to set plan: 'free' and subscription_status: 'canceled' when the billing period actually ends. Without the webhook handler, the cancellation UI is cosmetic only.
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
- cwe · CWE-284 — Improper Access Control
- owasp:2021 · A01 — Broken Access Control
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-billing·automated