Booking Flow Specification
Status: Canonical Last Updated: 2026-05-11 Source:
apps/frontend/app/[locale]/booking/and booking components
Related specs
- Tech Stack — Convex API pattern used here
- Data Model — reservations, bookingDrafts tables
- Public Pages — Shows are entry points to booking
- Payments — OnePay integration during checkout
- Notifications — Confirmation emails after payment
- User Stories — US-B01 through US-B03 cover booking
Overview
The booking flow is a 4-step SPA with no page reloads between steps. Entry is always from a show page where date/time is locked.
Entry: /booking/[occurrenceId]/tickets — occurrenceId is the experienceEvent ID
Flow Steps
Step 1: TICKETS
- Select ticket type (Dinner Theatre / Show Only)
- Select quantity (1-32, capped at availability)
- See sticky cart with real-time total
- 10-minute countdown timer starts
- Continue →
Step 2: ADD-ONS (Skippable)
- "Make your evening even more memorable"
- Display add-on cards with price, description
- Toggle add-ons on/off
- Skip & Continue always available
- Continue →
Step 3: CHECKOUT (Single Page)
- Left: Booking recap with edit links
- Right: Customer form (name, email, phone)
- Terms acceptance checkbox
- Payment logos (VNPay, Visa, Mastercard)
- Pay Now → (redirect to OnePay)
Step 4: CONFIRMATION (Post-Payment)
- Animated checkmark
- Booking recap
- QR code for check-in
- Add to Calendar buttons
- Download ticket PDF
- "A confirmation email has been sent"
URL Structure
/booking/[eventId]/tickets → Step 1 (tickets) /booking/[eventId]/addons → Step 2 (add-ons) /booking/[eventId]/checkout → Step 3 (checkout) /booking/[eventId]/confirmation → Step 4 (confirmation)
Sticky Cart
Desktop: Fixed right sidebar Mobile: Fixed bottom bar
Always visible during steps 1-3 with:
- Selected ticket type × quantity
- Selected add-ons
- Real-time total in VND
Countdown Timer
- Starts on Step 1 entry
- 10-minute hold on seats
- Display: "Your seats are reserved for 09:54"
- On expiry: Modal alert "Your reservation expired" with "Restart booking" button
- Seats auto-released on expiry
Pricing Rules
Base Pricing
- Dinner Theatre: From experienceEvents.dinnerPrice
- Show Only: From experienceEvents.experienceOnlyPrice (only if enabled)
Surcharges (added to totalAmount in schema)
- Day of week: Friday + Saturday = +50K VND/guest
- Small party: 1-2 guests = +100K VND
Discounts (subtracted)
- Bundle discounts: Applied via bundleId
- VIP surcharge: +500K VND if VIP bundle
Scarcity Signals
- Show "Only X seats left" when <10 remaining
- Show "Few left" badge when 1-10 seats
- Show "Sold out" and disable button when 0
Trust Signals (Step 3)
- VNPay, Visa, Mastercard logos
- "Secure encrypted payment" badge
- No language selector during booking (locked from entry)
Customer Form Fields (Step 3)
| Field | Required | Validation | | First name | Yes | Non-empty | | Last name | Yes | Non-empty | | Email | Yes | Valid email format | | Phone | No | Optional | | Terms acceptance | Yes | Must check |
OnePay Integration
On "Pay Now" click:
- Create pending reservation in Convex
- Call OnePay API with return URL
- Redirect to OnePay payment page
- On return, verify signature and update reservation status
Payment Status Flow
PENDING → PAID (OnePay success) → FAILED (OnePay fail/cancel) → CANCELLED (user cancels) → REFUND_PENDING → REFUNDED (admin/process)
Key Components
| Component | Location | Purpose |
| BookingProvider | components/booking/booking-provider.tsx | Context for booking state |
| StickyCart | components/booking/sticky-cart.tsx | Persistent cart display |
| CountdownTimer | components/booking/countdown-timer.tsx | 10-min seat hold |
| TicketSelector | components/booking/ticket-selector.tsx | Step 1 |
| AddonPicker | components/booking/addon-picker.tsx | Step 2 |
| CheckoutForm | components/booking/checkout-form.tsx | Step 3 |
| ConfirmationView | components/booking/confirmation-view.tsx | Step 4 |
| QRCode | components/booking/qr-code-image.tsx | QR generation |