Why This Matters
Payment is the moment a guest commits to checkout. This is where the backend creates the actual reservation record and initiates the OnePay hosted checkout. Every previous section has been a provisional estimate; here the booking becomes real. The Payment section holds the guest’s selection for 15 minutes. If the countdown expires before payment is completed, the reservation is abandoned and the guest must start over. This prevents inventory lock-up from abandoned sessions.Order Summary
The order summary displays the complete breakdown of what the guest is purchasing. The backend computes all prices and line items; the frontend only renders what the server returns. The summary shows:- Event — show name, formatted date and time
- Experience — ticket type label (Dinner Show pack name or “Tickets Only”)
- Guest count — number of guests
- Line items — subtotal, add-ons, discounts
- Total — final amount due in VND
kind field: discount rows render in green with a minus sign, total rows render prominently in gold. The guest-visible fulfillments (experiences, add-ons) render as a pill list below the price breakdown.
Promo & Referral Codes
Two separate code entry fields sit side by side below the order summary:- Promo code — guest-entered promotional discount codes
- Referral code — guest loyalty or referral identifiers
Payment Countdown
When the guest first reaches the payment step, the backend sets apaymentExpiresAt timestamp. A countdown timer displays the remaining minutes and seconds in the format MM:SS.
The countdown runs client-side using countdownSeconds, which decrements every second. Two urgency states apply:
- Normal — border and text in gold tone
- Urgent — at 2 minutes (120 seconds) remaining, the border and text shift to red with a destructive style
- An alert appears with an explanatory message and a Start Over button
- The Pay Now button is disabled
- The guest must restart the booking from Select Date
Payment Policy Approval
Before a guest can proceed to OnePay, they must acknowledge the terms of service. A policy approval panel sits near the bottom of the payment step. The panel shows a summary of the payment policy and a link to the full terms of use. There is an Approve button that the guest must click. Once approved, the button transforms into a green checkmark state and the Pay Now button becomes active. If the guest navigates backward and returns to the payment step, the approval state resets and must be given again.OnePay Hosted Checkout
Clicking Pay Now executes two backend operations in sequence:- Create reservation — the backend creates the actual reservation record with an idempotency key (
Idempotency-Key: <uuid>) to prevent duplicate bookings on network retry - Start payment — the backend creates a OnePay payment session and returns a
paymentUrl
onepay domain. There they enter their card details directly into OnePay’s form. House of Legends never sees or stores card information.
After OnePay processes the payment (or declines it), OnePay redirects the guest back to the booking confirmation page via the configured return URL. The backend reconciles the payment result and marks the reservation as confirmed or failed.
Key Concepts
Backend owns all pricing and row semantics. The frontend never recalculates totals, discounts, row order, or row kind. Every amount displayed in the order summary comes frombackendEstimate.summaryRows. Each row includes a structured label: system labels carry stable semantic codes such as total, promotionCode, and referralCode, while literal labels carry backend/admin-authored item names. The frontend centralizes label rendering from this contract and must not infer labels from ids, parse backend English strings, or use backend English as a display escape hatch.
Idempotency key on reservation creation. The createReservation mutation is called with Idempotency-Key: <uuid> generated via crypto.randomUUID(). If the network drops mid-request and the client retries, the backend detects the duplicate key and returns the original response without creating a second reservation.
Countdown is a reservation hold, not a session timer. The hold is set by the backend at reservation creation time. If the guest closes the browser and returns within the window, the same countdown continues.
Payment policy approval resets on navigation. Leaving the payment step and returning clears the approval state. The guest must re-approve before paying.
Error Recovery
OnePay timeout or failure. If OnePay returns a decline or network error, the guest sees an error message with a recovery hint. They can click Pay Now again — the backend reconciles the existing reservation with the new payment attempt. Expired countdown. The guest sees a full-width alert with a Start Over button. Confirming discards the current booking state and returns the guest to Select Date. No reservation is created for abandoned sessions. See also: Payment concept — how the OnePay integration works, the payment lifecycle, and refund handling. Reservation concept — how the reservation is created and its status lifecycle. Backend estimate failure. If the price summary cannot be loaded, an error banner appears above the order summary. The Pay Now button is disabled until the estimate is successfully retrieved.Technical Details
Technical Details
summaryRows structuresummaryRows is an array of PriceSummaryRow objects returned from the estimate mutation. Each row has:id— unique identifier for the rowlabel— structured row label.systemlabels are localized by the frontend from semanticcodeandparams;literallabels render theirtextas backend/admin-authored content.detail— optional secondary descriptionkind— one ofcharge,discount, ortotalamount— integer amount in VND
kind === "total" renders as the prominent gold total. All other rows with kind !== "total" render as detail lines below it.paymentCountdown statepaymentCountdown is computed from paymentExpiresAt and the client-side countdownSeconds:- Guest enters a code and clicks Apply
- Frontend calls
applyPromoCodemutation orapplyReferralCodemutation - Backend validates the code against active promotions or referral records
- On success: returns a message string and triggers a new
estimatecall to refreshsummaryRows - On failure: returns an error string displayed inline below the input
createReservation mutation, not on the startPayment mutation. This is intentional: the reservation creation is the operation that must not duplicate. The payment initiation is idempotent by nature of OnePay’s session handling.