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)

5-Phase State Machine

The BookingSection component owns a phase state machine:
GRID → EXPERIENCE → ADDONS → CHECKOUT → CONFIRMATION
1

GRID — Event Selection

Guest sees upcoming events as cards. Click “Book Now” → locks date/time and advances to EXPERIENCE.
2

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.
3

ADDONS — Optional Extras

Add-on cards filtered by experience type. Signature Bundle + bottles for Ticket Only; bottles only for Dinner Show. Skippable.
4

CHECKOUT — Customer Info + Payment

Booking summary with edit links. Customer form (name, email, phone). OnePay redirect on “Pay Now”.
5

CONFIRMATION — Success

QR code for check-in. Booking details. Add to Calendar. Download invoice. “A confirmation email has been sent”.

Experience Cards

CardPriceAdd-ons Available
Ticket Only650,000 VND / personSignature Bundle + all bottles
Dinner Show990k Solo / 1,850k Duo / 3,500k Table of 4Bottles only
VIPSOLD OUT

VIP Card

  • Always visible but non-selectable (sold out)
  • Red “Sold Out” badge overlay
  • “Join the Waiting List” button links to WhatsApp
  • Always visible at bottom when event selected
  • Shows: experience name + guest count + real-time total
  • Becomes checkout CTA in CHECKOUT phase

Pricing

Ticket Only

total = (650,000 × guest_count) + sum(addon_prices)

Dinner Show

total = dinner_tier_price + sum(bottle_addon_prices)
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:
  1. Create pending reservation in Convex (reservations.createPending)
  2. Call OnePay API with return URL
  3. Redirect to OnePay payment page
  4. On return, verify signature and update reservation status

Payment Status Flow

StatusDescription
PENDINGAwaiting payment
PAIDOnePay success
FAILEDOnePay fail/cancel
CANCELLEDUser cancels
REFUND_PENDINGRefund requested
REFUNDEDAdmin/process refund completed

Key Components

ComponentLocationPurpose
BookingSectioncomponents/home/booking-section.tsxOwns phase state machine, inline layout
ExperienceCardscomponents/booking/experience-cards.tsx3-card experience selection
DinnerTierSelectorcomponents/booking/dinner-tier-selector.tsxSolo / Duo / Table of 4 radio
VipSoldOutBadgecomponents/booking/vip-sold-out-badge.tsxSold out overlay
AddonCheckCardcomponents/booking/addon-check-card.tsxCheckable add-on card
DiscountTagcomponents/booking/discount-tag.tsxGreen “Save X” / Gold “-X%” badge
BookingFooterBarcomponents/booking/booking-footer-bar.tsxPrice bar + CTA
CheckoutFormcomponents/booking/checkout-form.tsxStep 3 form + payment
ConfirmationDisplaycomponents/booking/confirmation-display.tsxStep 4 QR + success

Deprecated Components (Replaced by Inline System)

The old booking modal pattern has been removed:
  • BookingModal — replaced by BookingSection
  • BookingModalContext — replaced by BookingSection state
  • StepIndicator — no stepper in inline flow
  • TicketTypeOption — replaced by ExperienceCards
  • GuestCountSelector — replaced by DinnerTierSelector