Booking × Payment × Loyalty Status Matrix
Purpose: bức tranh toàn cảnh mọi transition + side effect cho Booking, Payment, Loyalty, ứng với mọi performer (CUSTOMER / STAFF / OWNER / ADMIN / SYSTEM). Dùng để rà soát gap giữa code và spec, track progress hoàn thiện.
Scope: tài liệu này mở rộng (không thay thế) bộ docs flows hiện có:
../booking-status-flow.md— state machine formal + role matrix ngắn gọn../payment-flow.md— end-to-end payment scenarios../booking-flow.md— create/update + settings enforcement../loyalty-flow.md— loyalty bounded context + redemption lifecycle
How to read
Mỗi file ghi theo cùng format:
- Transition / scenario — state machine mô tả
- Performer matrix — ai được phép, ai bị block
- Guards — pre-conditions (code-level + business-level)
- Events emitted — domain event publish vào outbox
- Side effects — payment, loyalty, notification, stats
- Real-world cases — scenario ở salon thật
- Implementation status — checkbox
[x]= shipped + test,[~]= partial / gap,[ ]= chưa - Known issues — link sang
gaps-and-plan.md
File index
| # | File | Covers |
|---|---|---|
| 0 | README.md | Index + state machine overview |
| 1 | 01-create.md | Booking creation (admin / public / walk-in) + initial status + Payment init + Loyalty reserve |
| 2 | 02-happy-path.md | PENDING → CONFIRMED → ARRIVED → IN_PROGRESS → COMPLETED |
| 3 | 03-cancel.md | → CANCELLED từ mọi state × payment × performer |
| 4 | 04-no-show.md | → NO_SHOW + payment forfeit |
| 5 | 05-payment-driven.md | Payment events → Booking transitions (reverse direction) + depositStatus updates |
| 6 | 06-loyalty-coupling.md | Loyalty redemption lifecycle gắn với booking status |
| 7 | 07-edge-cases.md | Races, stuck states, webhook ordering, recovery |
| 8 | gaps-and-plan.md | Consolidated issue checklist + P0/P1/P2 roadmap |
Status legend
[x]— Shipped + tested (unit + e2e).[~]— Partial: code chạy nhưng thiếu test / guard / feature flag / audit.[ ]— Chưa implement hoặc docs nói một đằng code một nẻo.[!]— Gap cảnh báo: hành vi thực tế khác spec, có thể gây production incident.
Booking status machine (overview)
┌──────────┐
create (autoConfirm=false) ─▶ │ PENDING │
└────┬─────┘
│ ┌──────── PaymentAuthorized (event-driven) ────────┐
│ │ │
▼ ▼ │
┌───────────┐ │
│ CONFIRMED │ ◀── create (autoConfirm=true) │
└─┬────┬────┘ │
arrive │ │ start (skip ARRIVED) │
▼ │ │
┌─────────┐│ │
│ ARRIVED ││ │
└────┬────┘│ │
start│ │ │
▼ ▼ │
┌─────────────┐ │
│ IN_PROGRESS │ ◀── walk-in (new booking starts here) │
└──────┬──────┘ │
│ complete │
▼ │
┌───────────┐ │
│ COMPLETED │ │
└───────────┘ │
│
* ─── cancel (PENDING/CONFIRMED/ARRIVED) ──────▶ CANCELLED │
* │
* ─── mark no-show (CONFIRMED/ARRIVED) ────────▶ NO_SHOW │
* │
* ─── PaymentFailed / PaymentExpired (PENDING only) ─────▶ CANCELLED ───────┘
Source of truth: booking-api/src/core/booking/booking-status.constants.ts + booking-web/src/lib/booking-status.constants.ts. 2 file phải luôn sync.
Payment status machine (overview)
┌───────────┐
initiate() ─▶ │ INITIATED │
└───┬───────┘
│ ├──── authorize() (MANUAL + webhook) ──▶ AUTHORIZED
│ ├──── capture() (AUTO + webhook) ──▶ CAPTURED
│ ├──── markFailed() ──▶ FAILED
│ └──── markExpired() (customer abandon)──▶ EXPIRED
│
AUTHORIZED ──┬── capture(amount) ───▶ CAPTURED
├── void() ───▶ VOIDED
├── markExpired (7d) ───▶ EXPIRED
└── markFailed (cap err)▶ FAILED
│
CAPTURED ────┬── refund(partial) ──▶ PARTIALLY_REFUNDED
└── refund(full) ──▶ REFUNDED
│
PARTIALLY_REFUNDED ── refund(rest) ──▶ REFUNDED
Terminal: CAPTURED (nếu không refund), REFUNDED, PARTIALLY_REFUNDED (nếu stop), VOIDED, FAILED, EXPIRED.
Source of truth: booking-api/src/core/payment/domain/enums.ts + booking-api/src/core/payment/domain/policies/cancellation-refund-policy.ts.
Coupling diagram
Booking Context Payment Context Loyalty Context
──────────────── ─────────────── ───────────────
│ │ │
create ──▶│─ BookingCreated ────────────────▶│─ initiate() → INITIATED │
│ │ │─ reserveInTx (if redemption)
│ │ │
│◀─ PaymentAuthorized ─────────────│ │
PENDING │ │ │
→ CONF │ │ │
│◀─ PaymentFailed/Expired ─────────│ │
PENDING │ │ │
→ CANCEL │ │ │
│ │ │
arrive ──▶│─ BookingArrived ────────────────▶│─ capture() (MANUAL+AUTH) │
│ │ │
complete▶ │─ BookingCompleted ──────────────▶│─ capture fallback │─ RESERVED → CONSUMED
│ │ │─ auto-earn stamps/points
│ │ │
cancel ──▶│─ BookingCancelled ──────────────▶│─ decide VOID/REFUND/FORFEIT │─ RESERVED → CANCELLED (pre-cap)
│ │ │ + CLAWBACK for points
│ │ │
no-show▶ │─ BookingMarkedNoShow ───────────▶│─ capture (AUTH = forfeit) │─ RESERVED → CANCELLED
│ │ │
│ │─ depositStatus reactions ◀──│ (Booking listens back)
Cross-context invariants
- Booking KHÔNG direct call Payment/Loyalty service. Tất cả đều qua
DomainEventOutbox+EventBus. - Loyalty reserve = intra-booking-tx. Booking + LoyaltyRedemption + Outbox commit atomic.
- Payment init = async post-commit. Booking commit trước, Payment listener fire sau qua
OutboxPublisher. - depositStatus trên Booking = read-only projection của Payment state. Không drive business logic.
- Role
SYSTEM= event-driven transitions (listener auto-confirm / auto-cancel). Bypass customer-protection guards nhưng không bypass state machine validity. - Role
OWNER/ADMIN= admin override. Bypass state machine (isAdminTransition) nhưng hiện chưa bypass cancellation window — xemgaps-and-plan.md.
Maintenance
- Sửa code status transition → update file tương ứng + re-tick checkbox.
- Thêm event mới → update
README.mdcoupling diagram + file liên quan. - Khi đóng 1 P0/P1 issue → đổi
[!]/[~]thành[x]trong file gốc + mark done tronggaps-and-plan.md.