Failure handling releases resources
Why it matters
A failed payment that leaves a PENDING_PAYMENT or HELD booking blocks the slot indefinitely: the booking appears in capacity counts, the slot shows as unavailable to other users, and the original customer cannot retry because the slot appears taken. CWE-459 (incomplete cleanup on error) names this pattern precisely. Every payment failure path — declined card, network timeout, webhook failure — must release both the booking status and the slot hold atomically. A slot left blocked by a failed payment is functionally equivalent to a permanently deleted slot from the user's perspective.
Severity rationale
High because unrecovered failed-payment state permanently blocks slots for all future customers, directly reducing available inventory without any corresponding revenue.
Remediation
Handle payment failures in your Stripe webhook handler (e.g., src/app/api/webhooks/stripe/route.ts) with an atomic transaction that updates both the booking and the slot.
// In payment_intent.payment_failed webhook handler
const bookingId = event.data.object.metadata.bookingId;
const booking = await db.booking.findUniqueOrThrow({ where: { id: bookingId } });
await db.$transaction([
db.booking.update({
where: { id: bookingId },
data: { status: 'FAILED', failureReason: 'payment_declined' }
}),
db.slot.update({
where: { id: booking.slotId },
data: { bookedCount: { decrement: 1 } }
}),
]);
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 Booking Flow & Lifecycle Audit. The pattern definition, severity rationale, and remediation remain openly licensed under CC-BY-4.0.
External references
- cwe · CWE-459 — Incomplete Cleanup
- iso-25010:2011 · reliability.recoverability — Recoverability — failed payments release held slot capacity atomically
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-flow-lifecycle·automated