specs
Booking Flow

Booking Flow Specification

Status: Canonical Last Updated: 2026-05-11 Source: apps/frontend/app/[locale]/booking/ and booking components


Related specs


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:

  1. Create pending reservation in Convex
  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

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 |