Payment
canonical term:payment | reference format: PAY_{YYMMDD}_NNNN
This document explains how Payments work at House of Legends — how money moves from guest to House of Legends, the OnePay integration, and the payment lifecycle.
What Is a Payment?
A Payment is the financial record of a guest’s transaction for a reservation. It captures:- How much — amount in VND
- Who paid — linked to a reservation
- How — payment provider and transaction ID
- Whether it succeeded — status
- When — timestamps for initiation and confirmation
The Payment Flow
Here is what happens when a guest completes booking:Payment Verification Is Production-Only
Payment flow changes must be verified against the real production OnePay integration. Here is why:- Localhost cannot receive OnePay IPN callbacks
- ngrok URLs are not whitelisted by OnePay
- Mocked callbacks do not test the actual OnePay IPN endpoint
- QueryDR reconciliation behavior can only be observed in production
Payment Statuses
| Status | Meaning |
|---|---|
PENDING | Checkout initiated, guest at OnePay, funds not yet captured |
PAID | IPN received or QueryDR confirmed — funds captured |
FAILED | OnePay declined the card, or timeout |
REFUNDED | Full or partial refund issued |
The PENDING Problem
APENDING payment means OnePay has not yet confirmed the outcome. This can happen if:
- Guest abandoned checkout mid-way
- OnePay API timed out
- IPN callback was delayed
PENDING payments that have not received an IPN. QueryDR asks OnePay directly: “did this transaction succeed?”
The Three Identifiers of a Payment
| Identifier | Example | Purpose |
|---|---|---|
id (ULID) | 01ARZ3NDEKTSV4RRFFQ69G5FAV | Machine identity — joins, API |
reference | PAY_260603_0001 | Internal ops identifier |
receiptReference | RCP_260603_0001 | Guest-facing receipt identifier |
Key Fields
| Field | Type | Notes |
|---|---|---|
id | ULID | Machine identity |
reference | string | Internal ops identifier |
reservationId | ULID | FK to parent reservation |
amount | number | Amount in VND |
currency | string | Always VND |
provider | string | ONEPAY |
providerTransactionId | string | OnePay’s transaction reference |
status | enum | PENDING, PAID, FAILED, REFUNDED |
receiptReference | string | Guest receipt ID |
paidAt | datetime | When confirmed |
createdAt | datetime | When checkout initiated |
Refunds
A payment can be fully or partially refunded. Refunds are tracked separately:status transitions to REFUNDED when a refund is issued.
What the Frontend Must Not Do
Frontend must never recalculate payment amounts. All pricing is computed by the backend and stored in the reservation’spriceSummary. The frontend displays the totals but does not compute them.
This means:
- Never recalculate
totalfrom line items on the frontend - Never trust a payment amount from the frontend — always verify against backend
- Never store payment state derived from frontend calculations
See Also
- Reservation — the booking this payment belongs to
- Payment — the booking flow payment section
- Booking Flow Overview — the current booking journey
