Charging a card before the booking record is safely persisted and confirmed creates a class of support incidents that requires manual refunds: "I was charged but have no booking." CWE-841 (behavioral workflow violation) and CWE-362 both apply — the payment and booking writes are implicitly ordered, and that ordering is not enforced by the code. Stripe's authorize-then-capture flow (capture_method: 'manual') exists precisely for this scenario: authorize the payment (reserve funds) while processing the booking, then capture (collect funds) only after the booking is guaranteed. Any other ordering introduces a window where money moves without a corresponding booking.
High because premature capture means every downstream booking failure — DB error, webhook delay, constraint violation — produces a charged customer with no reservation, requiring manual refund per incident.
Use capture_method: 'manual' on the PaymentIntent and capture only after the booking record is confirmed. Implement this in src/app/api/checkout/route.ts or src/lib/payment-service.ts.
// 1. Persist booking first
const booking = await db.booking.create({
data: { ...bookingData, status: 'PENDING_PAYMENT' }
});
// 2. Authorize (no money moves yet)
const intent = await stripe.paymentIntents.create({
amount,
currency: 'usd',
capture_method: 'manual',
metadata: { bookingId: booking.id },
});
// 3. Confirm booking, then capture via webhook
// stripe.paymentIntents.capture(intent.id) — called in the webhook handler
ID: booking-flow-lifecycle.payment.payment-capture-timing
Label: Payment capture timing
Severity: high
What to look for: Trace the order of operations in the checkout/payment flow: booking creation vs. payment charge. Before evaluating, extract and quote the sequence of API calls in the payment handler — identify which call creates the booking record and which call initiates the payment charge. The ideal pattern is: Create Booking (status=PENDING_PAYMENT) then Authorize Payment then Confirm Booking then Capture Payment. Count the number of steps and verify the ordering.
Pass criteria: Payment is captured (money taken) only after the booking is successfully written and confirmed. At least 1 payment flow must use an "authorize and capture" pattern (Stripe paymentIntents.create with capture_method: 'manual', or equivalent), not "charge immediately" (charges.create with immediate capture). The booking record must exist before the payment API call. Report: "Payment flow: [step1] -> [step2] -> [step3] in [file]. Capture method: [manual|automatic]."
Fail criteria: Card is charged immediately before the booking record is safely created and confirmed. Do NOT pass when capture_method is 'automatic' (the default) and the booking record does not exist before the charge — this means money is taken before the booking is guaranteed.
Skip (N/A) when: No payment integration (no Stripe, PayPal, Square, or other payment SDK in package.json dependencies).
Detail on fail: "Payment is captured immediately upon user form submission. If the subsequent booking record insertion fails, a manual refund is required."
Cross-reference: The confirmation-record check in Booking Creation verifies the booking record is created before payment initiation.
Cross-reference: The failure-handling check in this category verifies cleanup when payment fails after booking creation.
Cross-reference: The payment-booking-coupling check in this category verifies the booking ID is passed to the payment provider.
Remediation: Implement authorize-then-capture flow in your checkout handler (e.g., src/app/api/checkout/route.ts or src/lib/payment-service.ts).
// 1. Create booking (not yet charged)
const booking = await db.booking.create({
data: { ...bookingData, status: 'PENDING_PAYMENT' }
});
// 2. Authorize payment
const paymentIntent = await stripe.paymentIntents.create({
amount,
metadata: { bookingId: booking.id }
});
// 3. Confirm booking
await db.booking.update({
where: { id: booking.id },
data: { status: 'CONFIRMED', paymentIntentId: paymentIntent.id }
});
// 4. Capture happens in webhook after successful confirmation