# Booking record created before payment

- **Pattern:** `ab-000539` (`booking-flow-lifecycle.booking-creation.confirmation-record`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000539
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000539)

## 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`).

```typescript
// 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 a `db.booking.create` call executes before it. The booking ID should be passed to the payment provider (e.g., in `metadata` or 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.constructEvent` handler). 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.json` dependencies).
- **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`).

  ```typescript
  // 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
- cwe CWE-841
- iso-25010 functional-suitability.functional-correctness

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000539
