Booking Flow Specification
Status: Legacy — See Booking Experience for canonical spec
Last Updated: 2026-05-12
Source: apps/frontend/components/home/booking-section.tsx
Doc Status: Excellent | ✓ All 6 checks passed
Overview
The booking flow is an inline 5-phase SPA on/booking. No page reloads between phases. Entry is always from a show page where date/time is locked. Guests select an experience, optionally add add-ons, fill in customer info, and pay via OnePay.
Entry: /booking — renders BookingSection component inline (no route change during flow)
Related specs
- Tech Stack — Convex API pattern used here
- Data Model — reservations, bookingDrafts tables
- Booking Experience — Canonical spec for 3-card experience selection
- 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
5-Phase State Machine
TheBookingSection component owns a phase state machine:
GRID — Event Selection
Guest sees upcoming events as cards. Click “Book Now” → locks date/time and advances to EXPERIENCE.
EXPERIENCE — 3-Card Selection
Three experience cards: Ticket Only (650k/person), Dinner Show (990k/1.85m/3.5m by tier), VIP (sold out). Single-select radio behavior.
ADDONS — Optional Extras
Add-on cards filtered by experience type. Signature Bundle + bottles for Ticket Only; bottles only for Dinner Show. Skippable.
CHECKOUT — Customer Info + Payment
Booking summary with edit links. Customer form (name, email, phone). OnePay redirect on “Pay Now”.
Experience Cards
| Card | Price | Add-ons Available |
|---|---|---|
| Ticket Only | 650,000 VND / person | Signature Bundle + all bottles |
| Dinner Show | 990k Solo / 1,850k Duo / 3,500k Table of 4 | Bottles only |
| VIP | SOLD OUT | — |
VIP Card
- Always visible but non-selectable (sold out)
- Red “Sold Out” badge overlay
- “Join the Waiting List” button links to WhatsApp
Booking Footer Bar
- Always visible at bottom when event selected
- Shows: experience name + guest count + real-time total
- Becomes checkout CTA in CHECKOUT phase
Pricing
Ticket Only
Dinner Show
No per-person pricing for Dinner Show. Price is all-inclusive (4-course dinner).
Surcharges
Day-of-week surcharges and small-party surcharges do not apply under the current pricing model. (See Booking Experience for full pricing rules.)Countdown Timer
- Starts on GRID → EXPERIENCE transition
- 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
OnePay Integration
On “Pay Now” click:- Create pending reservation in Convex (
reservations.createPending) - Call OnePay API with return URL
- Redirect to OnePay payment page
- On return, verify signature and update reservation status
Payment Status Flow
| Status | Description |
|---|---|
| PENDING | Awaiting payment |
| PAID | OnePay success |
| FAILED | OnePay fail/cancel |
| CANCELLED | User cancels |
| REFUND_PENDING | Refund requested |
| REFUNDED | Admin/process refund completed |
Key Components
| Component | Location | Purpose |
|---|---|---|
BookingSection | components/home/booking-section.tsx | Owns phase state machine, inline layout |
ExperienceCards | components/booking/experience-cards.tsx | 3-card experience selection |
DinnerTierSelector | components/booking/dinner-tier-selector.tsx | Solo / Duo / Table of 4 radio |
VipSoldOutBadge | components/booking/vip-sold-out-badge.tsx | Sold out overlay |
AddonCheckCard | components/booking/addon-check-card.tsx | Checkable add-on card |
DiscountTag | components/booking/discount-tag.tsx | Green “Save X” / Gold “-X%” badge |
BookingFooterBar | components/booking/booking-footer-bar.tsx | Price bar + CTA |
CheckoutForm | components/booking/checkout-form.tsx | Step 3 form + payment |
ConfirmationDisplay | components/booking/confirmation-display.tsx | Step 4 QR + success |
Deprecated Components (Replaced by Inline System)
The old booking modal pattern has been removed:BookingModal— replaced byBookingSectionBookingModalContext— replaced byBookingSectionstateStepIndicator— no stepper in inline flowTicketTypeOption— replaced byExperienceCardsGuestCountSelector— replaced byDinnerTierSelector