Booking record created before payment
Why it matters
Creating the booking record inside the payment success webhook — rather than before initiating payment — creates a window where the customer is charged but receives no booking. Stripe webhook delivery can fail, retry with a delay, or be silently dropped by network issues. CWE-362 (race condition) and CWE-841 (behavioral workflow violation) both apply: the system has implicitly split an atomic operation into two steps with no rollback path. The result is a customer whose card was debited, a slot that may or may not be blocked, and a support queue incident that requires manual reconciliation.
Severity rationale
High because webhook delivery failure after a successful charge leaves customers in a provably charged-but-unbookied state, requiring manual intervention for every incident.
Remediation
Create the booking with status='PENDING_PAYMENT' first, pass its ID to the payment provider, then confirm on success. Wire this in your checkout handler (e.g., src/app/api/checkout/route.ts).
// Step 1: persist the booking before touching the payment API
const booking = await db.booking.create({
data: { ...bookingData, status: 'PENDING_PAYMENT' }
});
// Step 2: attach booking ID so the webhook can find the record
const intent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
metadata: { bookingId: booking.id },
});
Detection
-
ID:
confirmation-record -
Label: Booking record created before payment
-
Severity:
high -
What to look for: For systems with payment, verify that a persistent booking record (with ID) is created before redirecting to a payment gateway or charging the card. Trace the order of operations: locate the payment initiation call (e.g.,
stripe.paymentIntents.create,stripe.checkout.sessions.create) and verify adb.booking.createcall executes before it. The booking ID should be passed to the payment provider (e.g., inmetadataor as a reference). Count all payment initiation paths and verify each one creates a booking record first. -
Pass criteria: A booking database record exists with a unique ID before any payment charge. The booking ID is provided to the payment provider so webhooks can be reconciled later. At least 1 booking insert must precede every payment API call. Report:
"X of Y payment initiation paths create a booking record first." -
Fail criteria: The booking record is only created inside the payment success webhook handler (e.g., inside
stripe.webhooks.constructEventhandler). This leads to "charged but no booking" errors if the webhook fails or network drops. Do NOT pass when the booking record is created after the payment charge, even if it is in the same function. -
Skip (N/A) when: The system has no payment integration (no Stripe, PayPal, Square, or other payment SDK in
package.jsondependencies). -
Detail on fail:
"Booking record is created only inside the Stripe success webhook. If webhook delivery fails, the customer is charged but has no booking record in the system." -
Cross-reference: The payment-capture-timing check in Payment Integration validates the full authorize-then-capture sequence.
-
Cross-reference: The payment-booking-coupling check in Payment Integration verifies bidirectional ID references between booking and payment records.
-
Cross-reference: The failure-handling check in Payment Integration verifies cleanup when payment fails after booking creation.
-
Remediation: Create the booking first with
status='PENDING_PAYMENT', pass the Booking ID to the payment provider, and update status to'CONFIRMED'on payment success. Apply this in your checkout handler (e.g.,src/app/api/checkout/route.ts).// Create booking first const booking = await db.booking.create({ data: { ...data, status: 'PENDING_PAYMENT', } }); // Pass booking ID to payment provider const paymentIntent = await stripe.paymentIntents.create({ amount, currency: 'usd', metadata: { bookingId: booking.id }, }); // Payment webhook updates status to CONFIRMED
External references
- cwe · CWE-362 — Race Condition / TOCTOU
- cwe · CWE-841 — Improper Enforcement of Behavioral Workflow
- iso-25010:2011 · functional-suitability.functional-correctness
Taxons
History
- 2026-04-18·v1.0.0·Initial import from booking-flow-lifecycle·automated