EP-011 — Booking Engine RTM
EPIC: EP-011 Booking Engine Reconciled: 2026-05-25 Wave 1 of per-EPIC RTM scale-out (21 parallel EPICs) — see
./PLAYBOOK.mdfor the pattern derived from the BF-001 pilot. Source of truth for "% passing":hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.mduntil Task #12 re-measures.
Status legend
| Status | Meaning |
|---|---|
Built | Code exists + tests exist + tests pass |
Built-Untested | Code exists, no automated test coverage (or test exists but is a stub) |
Partial | Code exists for some acceptance criteria, gaps for others |
Stub | Code is a placeholder / TODO / returns mock data |
Not Started | No code |
Summary
| Metric | Count |
|---|---|
| Total stories (catalog) | 16 |
| Catalog claims "Built" | 12 |
| Catalog claims "Partial" | 3 |
| Catalog claims "Not Started" | 1 |
| Verified-Built | 10 (US-011-001, -002, -003, -005, -009, -010, -011, -012, -014, -016) |
| Built-Untested | 1 (US-011-013 — widget/iframe/standalone modes; no automated cross-mode tests) |
| Partial | 4 (US-011-004, -006, -007, -008) |
| Stub | 0 |
| Not Started | 1 (US-011-015 — conversion funnel analytics) |
| Drift findings | 13 (D-001..D-013; 6 P0 / 7 P1 / 0 P2) |
| [SME-PROPOSED] refinements | 29 |
Major reviewer-stale alert: 8 of 9 review files in
reviews/EP-011/were authored before the 2026-04 to 2026-05 booking-engine maturity wave (runtime host resolution 2026-05-18, ApiKeyService rotation AP-T03-002 2026-04-29, payment-service proxyinitiate-v22026-04-15, SE-Asia locales registered ini18n.ts, LanguageSwitcher shipped, promo-codes module shipped, gateway routes for/booking-engine+/booking-engine-configadded). Many P0 review verdicts are now wrong. Drift D-001..D-006 below capture this.
US-011-001 — Room Search with Availability Calendar
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-booking-engine/src/components/SearchForm.tsx— Date/guest/promo entry (Vite + React)hmp-booking-engine/src/components/booking/DateRangePicker.tsx— Calendar with past-date blockhmp-booking-engine/src/components/booking/GuestSelector.tsx— Adults/children/roomshmp-booking-engine/src/components/booking/RoomList.tsx+RoomCard.tsx— Results renderhmp-booking-engine/src/hooks/useAvailability.ts— TanStack Query, 2-min staleTimehmp-reservation-service/src/booking-engine/booking-engine.controller.ts:70-91—GET /booking-engine/availability+POST /booking-engine/search/availability(both@SetMetadata(REQUIRE_API_KEY, true)per W0.2.2 2026-05-06)hmp-reservation-service/src/booking-engine/booking-engine.service.ts—checkAvailability(),searchAvailability()hmp-property-service/src/booking-engine-support/booking-engine-support.controller.ts— Internal aggregatedGET /properties/:propertyId/booking-engine/availability-snapshot(service-account JWT)hmp-api-gateway/src/config/services.config.ts:1257—/booking-engineprefix → reservation-service
Unit + integration tests:
hmp-booking-engine/src/components/__tests__/SearchForm.test.tsx— 6 specs (header, labels, selector, button disabled, promo)hmp-booking-engine/src/components/__tests__/RoomCard.test.tsx— 22 specs (info, gallery, amenities, urgency badge, rate plan selection)hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts— coverscheckAvailability+searchAvailabilityhmp-property-service/src/booking-engine-support/booking-engine-support.service.spec.ts— aggregation tests
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts:: "search → results" — PARTIAL (per 13-flow doc 2026-05-16; pass count TBD)hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts:: full flow — TBDhmp-infrastructure/testing/flow-tests/src/flows/booking-engine-selfservice.flow.spec.ts— self-service path
E2E (Playwright):
hmp-booking-engine/e2e/booking-flow.spec.ts— search + resultshmp-booking-engine/e2e/booking-full-flow.spec.ts— full booking flowhmp-booking-engine/e2e/host-bootstrap.spec.ts— runtime host resolution
SME notes:
- Review file (
US-011-001-review.md) called this PASS_WITH_NOTES citing "no gateway route" — that gap is closed as ofservices.config.ts:1257. Verdict refresh needed. - Backend path/param naming mismatch flagged in GAP-6 of the review (
/booking-engine/search/availabilityvs/availability) is resolved — both paths exist inbooking-engine.controller.ts. - 30-night max-stay rule mentioned in GAP-3 of review file is still enforced only at backend search time, not at calendar disable. Real-world tenants regularly want to override per-property (long-stay corporate, leisure resort 60-night plans).
- Calendar response uses
staleTime: 2 * 60 * 1000(2 min) — for high-demand peak nights (NYE, Diwali), 2 min is long enough that two guests can both see "1 room left" simultaneously and one will fail at checkout. Lock-or-quote pattern is undocumented.
Drift findings:
D-001 — [P0] [Stale-review + catalog-honest] reviews/EP-011/US-011-006-review.md and …/US-011-007-review.md say "FAIL — no /booking-engine gateway route" but the routes prefix: '/booking-engine' (line 1257) and prefix: '/booking-engine-config' (line 1249) exist in hmp-api-gateway/src/config/services.config.ts, with matching ProxyController @All decorators. The reviews are ~6 months stale. A new reviewer reading these alone reopens closed P0 work.
- [SME-PROPOSED] Refresh all 9 EP-011 review files against current master. Drop FAIL verdicts that depend on the "no gateway route" claim. Add a
Last verified against masterheader on each review file.
D-002 — [P1] [Story-incomplete + India-hospitality-critical] US-011-001 AC body does not mention inventory holds or quote freezing. A high-demand date (NYE/Diwali) lets two browsers both see availableCount=1 from a 2-min-stale React-Query cache and both proceed to checkout. The 2026-05-19 BookingTrip multi-leg cart shipped BookingTrip.expiresAt = now + 10min for chain hub trips, but single-property search has no equivalent.
- [SME-PROPOSED] Add AC: "Search results SHALL display a 'Reserved for 10 minutes' badge for any room-type-night combination with
availableCount ≤ 3. When the guest clicks 'Select',POST /availability/holdSHALL create a Redis-backed inventory hold tied to the session, and the room cards SHALL switch to 'Reserved for you' state. Holds expire 10 min after creation and release back to inventory."
US-011-002 — Rate Plan Display & Comparison
Catalog status: Built (P0, 3 points) Verified status: Built
Code locations:
hmp-booking-engine/src/components/booking/RatePlanSelector.tsx— Rate plan cardshmp-booking-engine/src/components/booking/RoomList.tsx— Compare viewhmp-booking-engine/src/components/booking/RoomCard.tsx— Rate plan childhmp-reservation-service/src/booking-engine/booking-engine.controller.ts:103-110—GET /booking-engine/rate-plans/:propertyIdhmp-property-service/src/booking-engine-support/booking-engine-support.controller.ts—GET /properties/:propertyId/booking-engine/rate-plans
Unit + integration tests:
hmp-booking-engine/src/components/__tests__/PriceBreakdown.test.tsx— 37 specs (BookingSummary, PriceDisplay, currency, GST, discounts)hmp-booking-engine/src/components/__tests__/RoomCard.test.tsx— 22 specs include rate plan selectionhmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts—getRatePlans()+searchAvailabilityrate-plan binding
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts— search results include rate plans
E2E (Playwright):
hmp-booking-engine/e2e/booking-flow.spec.ts— rate plan selection step
SME notes:
BookingSummary.tsxflat-12%-GST hardcoding (US-011-001 review GAP-2; US-011-004 review GAP-6; US-011-008 review AC-11) is real today. GST slab is per-room-tariff: 0% (room ≤ ₹1,000/night), 12% (₹1,001-7,500), 18% (₹7,501+). Mis-quoting GST = guest sees wrong total = post-booking dispute + DPDPA risk on misleading-info ground.- "Member Rate" rate-plan is shown to non-members with a lock icon — but US-011-002 AC body specifies the lock; story is silent on whether the lock click links to login or to membership signup. Today: links to login (
/loyalty/login). - "Best Value" badge logic lives in
RoomCard.tsxas a client-side comparison — no server-side authoritativeisRecommendedflag. A revenue manager can't force-highlight a specific plan.
Drift findings:
D-003 — [P0] [India-hospitality-critical + tests-assert-behavior-story-doesnt-describe] The 22-spec PriceBreakdown.test.tsx asserts INR formatting per Intl.NumberFormat('en-IN') lakhs notation, but no spec asserts the per-tariff GST slab that the story body explicitly states (12% for ≤7,500 INR; 18% for 7,501+). BookingSummary.tsx instead hardcodes subtotal * 0.12. The Razorpay payment amount the guest pays is therefore wrong by 6 percentage points for any room ≥ ₹7,501/night.
- [SME-PROPOSED] Add AC: "GST SHALL be computed per per-night rate using the slab
{ ≤₹1,000: 0%, ₹1,001-7,500: 12%, ≥₹7,501: 18% }. The slab determination SHALL use the post-discount per-night rate (not the pre-discount), so that a promo dropping a ₹8,000 rate to ₹7,000 re-slots to 12%. Coverage: 6 new specs in PriceBreakdown.test.tsx covering boundary cases (999/1000/1001/7499/7500/7501)." - [SME-PROPOSED] Add AC: "RatePlan SHALL expose a tenant-managed
isRecommendedboolean.BookingSummary+RatePlanSelectorSHALL render the 'Best Value' badge from this flag, not from client-side comparison. Revenue managers can override the algorithmic recommendation per property + per season."
US-011-003 — Guest Details Form with Validation
Catalog status: Built (P0, 3 points) Verified status: Built
Code locations:
hmp-booking-engine/src/components/GuestForm.tsx— Guest details form (name, email, phone, ID, GST)hmp-booking-engine/src/components/__tests__/GuestForm.test.tsx— Form spechmp-reservation-service/src/booking-engine/booking-engine.service.ts—lookupGuestByEmail()returning-guest pre-fillhmp-reservation-service/src/booking-engine/dto/booking-engine.dto.ts—CreateBookingDtofield validation
Unit + integration tests:
hmp-booking-engine/src/components/__tests__/GuestForm.test.tsx
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts— guest details step
E2E (Playwright):
hmp-booking-engine/e2e/booking-flow.spec.ts— guest entry
SME notes:
- Aadhaar masking-for-display (
XXXX-XXXX-9012) is a story-body promise — verified in form code, but no automated assertion that the API-submitted body drops the masked digits. If the masked string is what hits guest-service, the row storesXXXX-XXXX-9012which fails C-Form lookup at check-in for international guests. - GSTIN regex enforcement (per US-002-001 D-003 from EP-002 RTM) is owned by property-service / guest-service DTO — booking-engine should reuse the same validator, not roll its own. Today the form's validation is client-side only.
- Phone field auto-prepends
+91per AC-03 — but no India-vs-foreign branching. A guest in the US booking from a US IP cannot enter+1-555-…from the form (auto-prepend overwrites). Today: foreign guests have to switch their browser locale to en-US to bypass.
Drift findings:
D-004 — [P1] [Story-incomplete + DPDPA + India-hospitality-critical] Aadhaar masking is referenced in display but not in API submission. DPDPA art. 8 + Aadhaar Act §29(2) require the last 4 digits only be persisted unless a legitimate purpose-bound consent is captured. Today there is no audit that the booking flow does not transmit + persist full Aadhaar to guest-service.
- [SME-PROPOSED] Add AC: "When
idType=AADHAAR, the booking engine SHALL transmit only the last 4 digits + a SHA-256 hash of the full number (for de-duplication at check-in). Display masking and storage masking SHALL be independently asserted by 2 separate specs (1 frontend transmit spec + 1 backend persist spec). AconsentGivenForFullAadhaarcheckbox is required to transmit the full number." - [SME-PROPOSED] Add AC: "Phone country code SHALL be a separate field (default
+91whenAccept-Language=hi-IN|en-IN, else inferred from browserIntl.DateTimeFormat().resolvedOptions().timeZone). Foreign guests booking Indian properties SHALL be able to enter their home country phone without the+91auto-prepend breaking them."
US-011-004 — Razorpay Payment Integration
Catalog status: Partial (P0, 8 points)
Verified status: Partial — initiate-v2 proxy + payment-service shipped (2026-04-15); legacy direct-Razorpay path still in usePayment hook; webhook + refund + amount-revalidation gaps remain
Code locations:
hmp-booking-engine/src/components/booking/PaymentMethods.tsx— UPI/card/netbanking/wallet selectionhmp-booking-engine/src/components/PaymentForm.tsx— Booking → payment orchestratorhmp-booking-engine/src/hooks/useRazorpayCheckout.ts— Razorpay SDK loader + dev-mockhmp-booking-engine/src/lib/razorpay.ts— SDK adapter + verify callhmp-reservation-service/src/booking-engine/booking-engine.controller.ts:225-237—POST /booking-engine/payments/initiate-v2(Phase 1 proxy to payment-service viaPaymentServiceClient)hmp-reservation-service/src/booking-engine/booking-engine.service.ts:1784—initiatePaymentV2()resolves tenantId+propertyId from reservation, forwards to payment-servicehmp-payment-service/— Owns/payments/initiate,/webhooks/razorpay-gateway(Phase 0 + 0.5 + 1, port 3014)hmp-shared-packages/packages/payments/—@hmp/payments@2.1.0Razorpay adapter (interfaces only after BREAKING 1.x)
Unit + integration tests:
hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts— service-levelhmp-payment-service/.../payments.service.spec.ts— adapter + signature verificationhmp-booking-engine— no PaymentForm/PaymentMethods/useRazorpayCheckout specs (review GAP-2 confirmed)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts— payment stephmp-infrastructure/testing/flow-tests/src/flows/payment-links.flow.spec.ts— payment-link flow (paired to US-011-007)hmp-infrastructure/testing/flow-tests/src/flows/upi-qr.flow.spec.ts— UPI QR variant
E2E (Playwright):
hmp-booking-engine/e2e/booking-full-flow.spec.ts— checkout including payment-modal mockhmp-booking-engine/e2e/payment-link-page.spec.ts,payment-link-otp.spec.ts— payment-link guest flow
SME notes:
- Flag
VITE_FEATURE_PAYMENT_SERVICE_ENABLED(default OFF per platform CLAUDE.md) controls whether the front-end callsinitiate-v2(proxy) vs the legacy direct path. Production has NOT flipped this on per the 2026-05-16 demo-readiness doc. The story is therefore "two paths, only one tested end-to-end against Razorpay sandbox". - Server-side amount-revalidation (review GAP-1) — fixed in
initiate-v2proxy (resolves total fromreservation.totalAmount, NOT from clientamount), but legacy direct path still trusts client total. Re-enabling tampering on the flag-off codepath. - Webhook (review GAP-3) —
hmp-payment-servicehandlespayment.capturedand emitsPAYMENT_CAPTUREDevent;hmp-frontdesk-servicePaymentCapturedConsumer(20/20 specs) flips folio + reservation paymentStatus. End-to-end pipeline shipped 2026-05-19 (folio reconciliation 7-phase plan). Review's "webhook only returns{ status: 'processed' }" is stale. - Refund flow (review GAP-other) — manual refund via dashboard exists; guest-self-service refund-on-cancel via
cancelBooking()triggers but UI feedback is minimal.
Drift findings:
D-005 — [P0] [Catalog-honest + tampering-risk] Legacy direct-Razorpay path (flag OFF) trusts client-supplied amount which can be tampered. Even with VITE_FEATURE_PAYMENT_SERVICE_ENABLED=true shipped, production still runs OFF per the 2026-05-16 readiness snapshot. The fix shipped 2026-04-15 in code is unreachable in production.
- [SME-PROPOSED] Add AC: "
POST /booking-engine/payments/initiate(legacy path) SHALL be deprecated and return 410 GONE onceinitiate-v2reaches GA. Until then, the legacy path SHALL re-deriveamountfromreservation.balanceDueserver-side and reject any client-supplied amount mismatch with 400code=PAYMENT_AMOUNT_TAMPERED." - [SME-PROPOSED] Add AC: "
VITE_FEATURE_PAYMENT_SERVICE_ENABLEDSHALL be promoted to default-ON for all environments by 2026-06-15, with the legacy path removed by 2026-07-15. The flip SHALL be gated by 7 days ofinitiate-v2traffic = 100% in QA + staging witherror_rate < 0.1%."
D-006 — [P1] [Test-vs-code + missing-coverage] Review GAP-2 documented "0 frontend tests for PaymentForm / PaymentMethods / useRazorpayCheckout" — still true at 2026-05-25. The single most error-prone step in the funnel has zero unit coverage. Payment-service has 20+ specs for the consumer side, but a UI regression that breaks Razorpay modal opening would not be caught until E2E.
- [SME-PROPOSED] Add AC: "
PaymentForm.test.tsx,PaymentMethods.test.tsx,useRazorpayCheckout.test.tsSHALL ship with ≥6 specs each covering: method selection, button disabled state, Razorpay open success, Razorpay open failure (network), verify call retry on 5xx, dev-mock-mode short-circuit. Coverage thresholds: ≥80% line + branch on payment-flow files."
US-011-005 — Booking Confirmation Page & Email
Catalog status: Built (P0, 3 points) Verified status: Built
Code locations:
hmp-booking-engine/src/components/booking/ConfirmationPage.tsx— Confirmation renderhmp-booking-engine/src/components/booking/BookingConfirmation.tsx— Inner confirmation UIhmp-reservation-service/src/booking-engine/booking-engine.controller.ts:296-305—POST /booking-engine/notifications/booking-confirmationhmp-reservation-service/src/booking-engine/booking-engine.service.ts—sendBookingConfirmation()publishes Kafka eventhmp-notification-service/— Email render + delivery via SendGrid (now AWS SES per 2026-05-21 memory)hmp-shared-packages/packages/template-engine— MJML block-tree renderer (powers confirmation email template)
Unit + integration tests:
hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts—sendBookingConfirmationKafka publish test
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts— confirmation step
E2E (Playwright):
hmp-booking-engine/e2e/booking-full-flow.spec.ts— verifies confirmation page renders post-payment
SME notes:
- PDF voucher mentioned in story body — generator lives in
hmp-shared-packages/packages/common/src/printing/(per platform CLAUDE.md,generateFolioReceipt/generatePosReceiptare ESC/POS for thermal printers). A guest-facing PDF voucher path is NOT enumerated in source map — likely served fromhmp-frontdesk-serviceinvoice-pdf module. Verify cross-link. - QR code for mobile check-in (story body) — token-mint endpoint exists at
POST /booking-engine/auth/notification-token(notification-token.service.ts) issuing 1-hour JWT for Socket.IO. But QR-on-voucher rendering not enumerated in code paths. - Confirmation email Indian-locale formatting (CGST/SGST/IGST split, IST timestamps, ₹ with lakhs) is the template-engine's responsibility. The story does not specify which CGST/SGST/IGST split logic to use — same gap as EP-002 D-008.
Drift findings: (none net new — feeds D-003 GST gap; deferred to fact-check)
- [SME-PROPOSED] Add AC: "Confirmation email + PDF voucher SHALL break GST into CGST+SGST (intra-state) vs IGST (inter-state) based on (property GSTIN state-code, guest billing-address state). The split SHALL be unit-tested via
confirmation-email.template.spec.tsfor both paths."
US-011-006 — Connect to Live Backend API (Replace Mock)
Catalog status: Not Started (P0, 8 points)
Verified status: Partial — gateway routes shipped, bookingApi.ts toggles via VITE_API_URL, runtime host resolution (2026-05-18) ships the real flow; mock layer still present for dev
Code locations:
hmp-api-gateway/src/config/services.config.ts:1249, 1257—/booking-engine-config+/booking-engineprefixes (line 1257 + line 1249)hmp-api-gateway/src/proxy/proxy.controller.ts—@All('booking-engine')+@All('booking-engine/*')+@All('booking-engine-config')+@All('booking-engine-config/*')(verify per gateway 2-file rule)hmp-booking-engine/src/lib/api/client.ts— Real axios client +setApiKey(),setPropertyId()hmp-booking-engine/src/lib/api/index.ts— Mock/live toggle byVITE_API_URLpresencehmp-booking-engine/src/hooks/useHostBootstrap.ts— Runtime host resolution (calls/booking-engine-config/resolve-host)hmp-booking-engine/src/App.tsx:AppWithTheme— Mode dispatcher (PROPERTY/BRAND/TENANT/CHAIN_HUB)hmp-property-service/src/host-mappings/host-mappings.controller.ts—GET /booking-engine-config/resolve-host(public), CRUD endpoints (tenant admin)
Unit + integration tests:
hmp-booking-engine/src/App.PropertyBootstrap.test.tsx— bootstrap pathhmp-booking-engine/src/hooks/useHostBootstrap.test.ts— bootstrap hookhmp-property-service/src/host-mappings/host-mappings.controller.spec.ts+host-mappings.service.spec.ts— controller + servicehmp-booking-engine/src/lib/api/__tests__/chainSearchApi.test.ts,bookingTripsApi.test.ts,selfCheckinApi.test.ts— API client coverage
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts— full flow against live backendhmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts— booking engine flowhmp-infrastructure/testing/flow-tests/src/flows/booking-engine-selfservice.flow.spec.ts— self-service path
E2E (Playwright):
hmp-booking-engine/e2e/host-bootstrap.spec.ts— runtime host resolutionhmp-booking-engine/e2e/booking-full-flow.spec.ts— end-to-end against live API
SME notes:
- Catalog
Not Startedis wrong by ~12 months. Runtime host resolution (2026-05-18 plan) shipped the real production architecture: one image serves any property/brand/tenant via/booking-engine-config/resolve-host. Mock layer (src/lib/api/mockData.ts,mockSpaApi.ts,mockLoyaltyApi.ts) is dev-only. - A5 (2026-05-22) added runtime
x-api-keydelivery viaresolve-hostreturning a per-(tenant,property)publishableApiKey. Pre-A5 the SPA shipped a statichmp-default-public-booking-engine-key— now DEV-ONLY. - Widget mode (
widget-entry.tsx) bypassesresolve-host— receivespropertyId+apiKeyviaHMPBooking.init({ apiKey }). Tests cover this path independently.
Drift findings:
D-007 — [P0] [Catalog-honest] Catalog says Not Started (0 of 8 points). Reality: 8 of 8 — gateway routes + bootstrap + property-service host-mappings + per-property publishable key all shipped. Status promotion long overdue.
- [SME-PROPOSED] Promote catalog status
Not Started→Built. Title can be updated to "Runtime Host Resolution + Live Backend Integration" to reflect the matured architecture. - [SME-PROPOSED] Add AC: "Booking engine SHALL boot against any host without a code change — onboarding a new tenant property is a single DB row insert (
host_mappings). E2E asserts that addingbook.demo-hotel.commapping → property bootstraps within 30 seconds without redeploy."
US-011-007 — Manage Booking (Lookup, Amend, Cancel)
Catalog status: Partial (P1, 5 points)
Verified status: Partial — lookup + cancel public endpoints shipped (booking-engine.controller.ts:131-161); amend shipped 2026-05-06 with PUT /booking-engine/bookings/:id; payment-link self-service shipped 2026-04-15; amendment deadline + payment-difference flow gaps remain
Code locations:
hmp-booking-engine/src/pages/manage/BookingLookup.tsx— Lookup formhmp-booking-engine/src/pages/manage/BookingDetails.tsx— Render bookinghmp-booking-engine/src/pages/manage/CancellationFlow.tsx— Cancel UIhmp-booking-engine/src/pages/manage/AmendmentFlow.tsx— Amend UIhmp-booking-engine/src/hooks/useManageBooking.ts— TanStack hookhmp-reservation-service/src/booking-engine/booking-engine.controller.ts:131-203—GET /booking-engine/reservations/lookup,POST /booking-engine/reservations/:id/cancel,PUT /booking-engine/bookings/:idamend (AmendBookingDto, W1.A.3 2026-05-06 honorsIdempotency-Key)hmp-reservation-service/src/booking-engine/booking-engine.service.ts—lookupReservation(),cancelBooking(),amendBooking()hmp-reservation-service/src/booking-engine/booking-engine.controller.ts:314-388— Payment-link self-service + OTP endpoints
Unit + integration tests:
hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts— lookup + cancel + amendhmp-reservation-service/src/booking-engine/__tests__/api-key-guard.spec.ts— API key on these routeshmp-booking-engine/src— no AmendmentFlow / CancellationFlow / BookingLookup specs
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/booking-engine-selfservice.flow.spec.ts— self-service amend/cancelhmp-infrastructure/testing/flow-tests/src/flows/payment-links.flow.spec.ts— payment-link path
E2E (Playwright):
hmp-booking-engine/e2e/manage-booking.spec.ts— manage-booking E2Ehmp-booking-engine/e2e/payment-link-page.spec.ts,payment-link-otp.spec.ts— payment-link guest flow
SME notes:
- Review file claimed
FAILon every backend AC citing "no public endpoint". All three are now shipped under/booking-engine. Review is stale. - Amendment-deadline enforcement (review GAP-4 "24-hour cutoff") — service
amendBooking()validates state (RESERVED/CONFIRMEDonly) and property scope, but does NOT enforce a "no amend within 24h of check-in" cutoff. Hospitality SOP universally caps amend at check-in minus 24h or 48h to protect inventory. - Amend payment-difference: when amendment extends nights or upgrades room type, the price goes UP — story body says "I am redirected to Razorpay to pay the difference" but
amendBooking()body does NOT include the differential payment redirect. Today: a guest amending +2 nights gets the longer reservation without paying the delta until check-in. - Cancellation refund:
cancelBooking()returns penalty + refund amount, but does NOT initiate the Razorpay refund. Refund is manual-via-dashboard. Mismatch withUS-011-007 AC-1.
Drift findings:
D-008 — [P0] [Story-vs-code + DPDPA + revenue-leak] Amendment with cost-increase does not collect the differential payment. A guest who amends a 3-night stay to 5 nights gets +2 nights "free" until check-in, when the front desk has to chase. India hospitality SOP says quoted-amount = collected-amount at amendment time.
- [SME-PROPOSED] Add AC: "When
amendBooking()produces apriceDifference > 0, the response SHALL include{ requiresPayment: true, paymentInitiation: { reservationId, amount, currency, paymentLinkSlug? } }. The frontend SHALL block amendment success until the differential payment is captured (use the existingPOST /booking-engine/payments/initiate-v2flow). WhenpriceDifference < 0, the response SHALL include{ requiresRefund: true, refundAmount }and trigger the same refund path as cancellation." - [SME-PROPOSED] Add AC: "Amendment SHALL be blocked when
now ≥ arrivalDate - 24h(configurable per cancellationPolicy.amendDeadlineHours, default 24). Attempt returns 409code=AMEND_DEADLINE_PASSED."
D-009 — [P0] [Story-vs-code + DPDPA] cancelBooking() returns refund amount but does NOT initiate the Razorpay refund. The 2026-05-19 folio-payment reconciliation pipeline (PaymentCapturedConsumer) handles the capture side; the symmetric refund side is dashboard-manual only. DPDPA art. 8(3) "reasonable period" interpretation = 7 business days; without auto-trigger, the SLA depends on staff diligence.
- [SME-PROPOSED] Add AC: "
cancelBooking()SHALL publishRESERVATION_CANCELLEDANDPAYMENT_REFUND_REQUESTEDevents.hmp-payment-serviceSHALL consumePAYMENT_REFUND_REQUESTED, call Razorpay Refunds API, emitPAYMENT_REFUNDED, andhmp-frontdesk-servicePaymentRefundedConsumer(NEW, 7+ specs) SHALL post a folio REFUND transaction. End-to-end SLA: refund initiated within 60 seconds of guest-cancel, settled per Razorpay's 5-7 business day standard."
US-011-008 — Promo Code Validation (Server-Side)
Catalog status: Partial (P1, 3 points)
Verified status: Partial — promo-code module exists with controller + service + spec; POST /booking-engine/promo-codes/validate shipped; per-room-type restriction, max-discount-cap, and GST-slab re-evaluation after discount remain gaps
Code locations:
hmp-reservation-service/src/promo-codes/promo-code.controller.ts— CRUD endpointshmp-reservation-service/src/promo-codes/promo-code.service.ts+ spechmp-reservation-service/src/booking-engine/booking-engine.controller.ts:163-168—POST /booking-engine/promo-codes/validate(public, API-key gated)hmp-reservation-service/src/booking-engine/booking-engine.service.ts:2033—validatePromoCode()hmp-booking-engine/src/components/booking/PromoCodeInput.tsx— Apply/remove UIhmp-booking-engine/src/hooks/usePromoCode.ts— TanStack hookhmp-reservation-service/prisma/schema.prisma—PromoCodemodel (catalog-noted in reservation-service CLAUDE.md table)
Unit + integration tests:
hmp-reservation-service/src/promo-codes/promo-code.service.spec.tshmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts:1283+—validatePromoCodespecs
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts— promo-code step
E2E (Playwright):
hmp-booking-engine/e2e/booking-full-flow.spec.ts— promo entry within booking flow
SME notes:
- Review file
US-011-008-review.mdsays NOT_IMPLEMENTED ("NoPromoCodePrisma model, no validation endpoint…") — all three are shipped perreservation-service/CLAUDE.mdKey Prisma Models table. Review is stale (D-001). - Per-room-type restriction (review GAP) —
validatePromoCode()accepts room/rate context but no spec asserts that codes restricted toroomTypeId=Xreject when applied to aroomTypeId=Ybooking. - Brute-force protection on the validate endpoint — public + API-key-gated only (no rate limit per key). A scraper with one valid
x-api-keycan iterate promo-code space. - Re-validation at booking confirmation (review AC-12) —
createBooking()acceptspromoCodein DTO but does NOT callvalidatePromoCode()again. A code valid at apply-time but invalidated at booking-time (max-uses tripped, expired) won't be caught.
Drift findings:
D-010 — [P1] [Story-incomplete + abandoned-cart-recovery] Discount logic does not handle the GST slab re-evaluation that a discount can trigger. Example: ₹8,000/night room with WELCOME10 (10% off) drops to ₹7,200 — slab moves from 18% to 12%. Today: 18% is applied to the discounted amount. Result: guest is over-charged by 6%, OR the dashboard shows a misleading "discount" that the post-tax delta partially cancels.
- [SME-PROPOSED] Add AC: "When promo discount drops the per-night rate across a GST-slab threshold (₹1,000 or ₹7,500), GST SHALL be re-computed at the new slab. Test fixtures: ₹8,000 → 10% off → ₹7,200 (18% → 12%); ₹1,100 → 20% off → ₹880 (12% → 0%). Both paths covered by
validatePromoCode.spec.ts."
D-011 — [P1] [Story-incomplete + abandoned-cart-recovery] usePromoCode is a synchronous apply on click. No abandoned-cart-recovery flow exists: a guest who applies a code and bounces from the payment step never receives a "you left X behind, code Y still valid for 24h" email/SMS. This is the single highest-ROI lever on the booking-engine conversion funnel (industry: 8-15% lift on emailed abandons).
- [SME-PROPOSED] Add AC (new story
US-011-NEW-ABANDONED-CART): "When a guest reaches/checkoutand leaves without completing payment,hmp-notification-serviceSHALL send (a) abandoned-cart email at +1h, +24h, +72h with the held cart contents + applied promo, AND (b) SMS at +24h (TRAI DND consent-gated). Email links restore the cart viabookingTripsApi.restoreTrip(tripId)."
US-011-009 — 14-Language Support (India + SE Asia)
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-booking-engine/src/locales/{en,hi,ta,te,kn,mr,bn,gu,ml,pa,th,vi,id,ms}.json— 14 locale files presenthmp-booking-engine/src/lib/i18n.ts— registers all 14 locales (verified — review file's claim of "only 10 registered" is stale)hmp-booking-engine/src/components/common/LanguageSwitcher.tsx— Switcher componenthmp-booking-engine/src/components/common/Header.tsx— Mounts switcher (2 ref sites)
Unit + integration tests:
hmp-booking-engine/src/components/common/__tests__/— common-components tests (EmptyState, ErrorState, LoadingState)- No dedicated
LanguageSwitcher.test.tsx, no translation-completeness spec (review GAP-9 still valid)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts— locale param honored (TBD count)
E2E (Playwright):
hmp-booking-engine/e2e/multi-language.spec.ts— covers language switching
SME notes:
- Review
US-011-009-review.mdis wholly stale. SE-Asia locales are registered ini18n.ts(verified via grep — th/vi/id/ms all imported). LanguageSwitcher component exists incomponents/common/. Review's FAIL verdict on every AC needs reset. - Reservation
guestLanguagecolumn gap (review GAP-4) — still real. The confirmation email goes vianotification-service's template engine using whatever locale the request session held; persistence onReservationfor replay would require additive schema work. - PDF voucher Thai font embedding (review GAP-8) — Noto Sans Thai (or fallback) is not enumerated in
hmp-shared-packages/packages/common/src/printing/receipt templates. Thai voucher rendering today = boxes for Thai script unless the OS renderer has the font. Real Thai/Vietnamese guests will see broken PDFs.
Drift findings:
D-012 — [P1] [Story-incomplete + India-hospitality-critical + i18n] Reservation.guestLanguage is not persisted, so the post-checkout notification chain (check-in reminder, post-stay survey, marketing follow-up) cannot replay in the guest's language. The platform regresses to English-default for everything fired after the initial confirmation email.
- [SME-PROPOSED] Add AC: "Booking flow SHALL persist
Reservation.guestLanguage(BCP-47 tag, e.g.th,vi-VN,hi-IN). All downstream notification events (RESERVATION_CONFIRMED,CHECKIN_REMINDER,POST_STAY_SURVEY) SHALL includeguestLanguagein payload.notification-servicetemplate renderer SHALL preferguestLanguageover property'sdefaultLocale." - [SME-PROPOSED] Add AC: "PDF voucher renderer SHALL embed Noto Sans Thai + Noto Sans Vietnamese fallback fonts. Coverage: 4 byte-level specs verifying PDF font-tables contain the script's glyphs (one per th/vi/id/ms). Files in
hmp-shared-packages/packages/common/src/printing/__tests__/pdf-fonts.spec.ts."
US-011-010 — Urgency Indicators & Social Proof
Catalog status: Built (P2, 3 points) Verified status: Built
Code locations:
hmp-booking-engine/src/components/booking/UrgencyIndicator.tsx— "Only X rooms left" bannerhmp-booking-engine/src/components/booking/RoomCard.tsx:113—availableCount <= 3triggers urgency badgehmp-reservation-service/src/booking-engine/booking-engine.controller.ts:285-291—GET /booking-engine/social-proof?propertyId=…hmp-reservation-service/src/booking-engine/booking-engine.service.ts—getSocialProof()(real data: recent reservation counts + occupancy)
Unit + integration tests:
hmp-booking-engine/src/components/__tests__/RoomCard.test.tsx— 22 specs include urgency-badge boundary cases (1/2/3 rooms vs 4)hmp-reservation-service/src/booking-engine/booking-engine.service.spec.ts—getSocialProoftest
Flow tests: none specific
E2E (Playwright): covered via results-page specs
SME notes:
- Code switched from "random numbers" (review GAP) to real data via
getSocialProof()perbooking-engine.service.ts. Story body claim "5 people viewing this room" — the concurrent viewers part is approximated from recent reservation counts (not real WebSocket session tracking). Industry term: "false urgency" — risks TRAI/consumer-protection complaints if challenged in India. - US-011-014 (story-body in EPIC, mapped to US-011-010 in catalog) had AC "no fabricated urgency" — code honors
availableCount <= 5threshold per backend, and<= 3per frontendRoomCard.tsx. Inconsistency: a 4-rooms-left case shows backend social-proof but not frontend urgency badge.
Drift findings: (none net new — consider folding US-011-014 AC-language into US-011-010)
- [SME-PROPOSED] Add AC: "Concurrent-viewer count SHALL be either (a) real (WebSocket session-tracking against
getSocialProofRedis-backed presence) OR (b) suppressed entirely. Approximated counts presented as real are a Consumer Protection Act 2019 §2(28) misleading-advertisement risk."
US-011-011 — Loyalty Dashboard (Points, History, Rewards)
Catalog status: Built (P2, 5 points) Verified status: Built
Code locations:
hmp-booking-engine/src/pages/loyalty/+src/components/loyalty/— TierBadge, PointsDisplay, RewardCard, LoyaltyDashboardhmp-booking-engine/src/hooks/useLoyalty.ts— TanStack Query hookhmp-booking-engine/src/lib/api/loyaltyApi.ts— Real loyalty API clienthmp-booking-engine/src/lib/api/mockLoyaltyApi.ts— Mock fallback (dev)hmp-crm-service/src/loyalty/— Backend loyalty domain (per platform CLAUDE.md, CRM owns guest loyalty)
Unit + integration tests:
hmp-booking-engine/src/components/loyalty/*.test.tsx(if present per Glob — minimal coverage)
Flow tests: none specific to loyalty dashboard within booking engine
E2E (Playwright): minimal — loyalty section in booking-full-flow.spec.ts
SME notes:
- Reward catalog browsing + redemption-in-booking are TWO separate flows. The dashboard (this story) is shipped; redemption-during-payment was originally scoped under EPIC body §US-011-09 but DOES NOT exist as a catalog row. Catalog stops at US-011-011 (dashboard); the "Points Redemption During Booking" story from EPIC body is silently dropped.
- Tier progression visualization ("2,500 points to Platinum" with 83% bar) is a guest-facing pacing nudge that drives membership renewal. The story doesn't enumerate the formula — risk: front-end and crm-service disagree on bar-percentage calculation.
Drift findings:
D-013 — [P1] [Story-incomplete + missing-story + Indian-hospitality-business-critical] The "Points Redemption During Booking" flow (EPIC body §US-011-09) is documented in the EPIC narrative but has no catalog row. This is the most direct loyalty-monetization lever (guest applies points → smaller Razorpay charge → higher engagement). Without a catalog row, it cannot be sprint-planned.
- [SME-PROPOSED] Author new catalog row
US-011-017"Loyalty Points Redemption at Booking Payment" with ACs: (1) Payment page surfaces points balance + max-redeemable cap (e.g. 50% of total); (2) Slider adjusts redeemable amount; (3) Razorpay charge SHALL equaltotal − redemptionInINR; (4) Points are debited ONLY on payment success (not on apply); (5) Failed payment leaves points balance untouched.
US-011-012 — Spa Treatment Booking
Catalog status: Built (P2, 3 points) Verified status: Built
Code locations:
hmp-booking-engine/src/components/spa/SpaPackageCard.tsx,SpaAvailabilityCalendar.tsx,SpaConfirmation.tsxhmp-booking-engine/src/hooks/useSpaAvailability.tshmp-booking-engine/src/lib/api/spaApi.ts+mockSpaApi.ts(dev fallback)hmp-booking-engine/src/stores/spaStore.ts— Zustand storehmp-spa-service/— Backend (port 3015 per platform CLAUDE.md)
Unit + integration tests: minimal frontend coverage
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/spa-booking.flow.spec.ts
E2E (Playwright): covered via spa-section in booking flow
SME notes:
- Indian-market reality: spa booking is a low-conversion add-on on the booking engine. Industry data: <2% of room bookings include a spa add-on at time of booking; the higher-volume path is in-stay add-on via the dashboard / WhatsApp concierge.
- Mock fallback (
mockSpaApi.ts) ships with the production bundle — review what happens whenhmp-spa-serviceis down (today: falls back to mock persrc/lib/api/spaApi.tstoggle). This could show fake therapists. Confirm + lock to real-only in prod.
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "Spa booking SHALL NOT fall back to mock data when
hmp-spa-serviceis unreachable. Failure SHALL render anErrorStatewith 'Spa booking temporarily unavailable, please call the property'. Mock import SHALL be tree-shaken in production builds (Viteimport.meta.env.PRODguard)."
US-011-013 — Widget / Iframe / Standalone Modes
Catalog status: Built (P1, 3 points) Verified status: Built-Untested — three modes ship; widget mode bypasses host-resolution and has limited cross-mode coverage
Code locations:
hmp-booking-engine/src/web-component.tsx— Web component build targethmp-booking-engine/src/widget-entry.tsx— Widget entry (usesHMPBooking.init({ apiKey }))hmp-booking-engine/vite.widget.config.ts— Widget-specific Vite confighmp-booking-engine/test-embed.html— Manual embed test pagehmp-booking-engine/embed/— Embed scripts and assetshmp-booking-engine/INTEGRATION.md— Integration guide- Runtime host resolution (
useHostBootstrap.ts) is the standalone mode; widget mode bypasses it
Unit + integration tests:
hmp-booking-engine/src/App.PropertyBootstrap.test.tsx— standalone mode- No
widget-entry.test.tsxor cross-mode parity test
Flow tests: none
E2E (Playwright): host-bootstrap.spec.ts covers standalone; widget + iframe not enumerated
SME notes:
- Three deployment modes is a textbook example of "code paths > tests". A change to
client.ts:setApiKey()could silently break widget mode (noresolve-hostto refresh it). - Iframe mode
postMessageto parent page — story body claims this but no test asserts the message shape. A hotel's marketing page subscribing to'hmp:booking-completed'events would silently fail on a contract change.
Drift findings:
- [SME-PROPOSED] Add AC: "
widget-entry.test.tsx,iframe-postMessage.test.tsx, andweb-component-mount.test.tsxSHALL each ship with ≥3 specs covering: mount withHMPBooking.init, mount with bare attributes,postMessagecontract{ type: 'hmp:booking-completed', bookingId, confirmationNumber }validation."
US-011-014 — Cookie Consent (GDPR Compliant)
Catalog status: Built (P1, 2 points) Verified status: Built
Code locations:
hmp-booking-engine/src/components/CookieConsent.tsx— Cookie consent bannerhmp-booking-engine/src/App.tsx— Mounts the banner
Unit + integration tests: minimal — no CookieConsent.test.tsx enumerated
Flow tests: none specific
E2E (Playwright): none enumerated for consent
SME notes:
- Granular categories (Essential / Analytics / Marketing) — EPIC body US-011-15 specifies; verify
CookieConsent.tsxrenders all three, not just "Accept all / Reject all" (current implementation likely is the latter per EPICPartialstatus — but catalog saysBuilt— needs fact-check). - DPDPA art. 7 (consent must be free, specific, informed) + GDPR art. 7 BOTH require per-category consent. A single "accept all" violates both.
- Audit trail (server-side persistence of consent record) — review GAP-3 said this is missing. Today: consent stored in localStorage only, so no audit log for "did this guest consent to marketing on Jul 4?".
Drift findings: (consolidate into DPDPA gap surfaced under D-004)
- [SME-PROPOSED] Add AC: "Cookie consent banner SHALL render three distinct toggles: Essential (always on, greyed), Analytics (default off), Marketing (default off). Consent SHALL be persisted server-side via
POST /booking-engine/consentwith{ guestSessionId, propertyId, categories: {essential, analytics, marketing}, ipCountry, userAgent, timestamp }. Retention: 3 years per GDPR art. 5(1)(e). Coverage: 1 backend spec + 3 frontend specs (toggle state, persistence, audit)."
US-011-015 — Booking Engine Analytics (Conversion Funnel)
Catalog status: Not Started (P2, 5 points) Verified status: Not Started
Code locations:
hmp-booking-engine/src/lib/ab-testing/— A/B testing scaffolding exists (ab-testing.service.ts,ab-testing.provider.tsx,use-experiment.ts) but is NOT a conversion-funnel analytics implementation- No
booking-engine.funnel.*events enumerated inhmp-shared-packages/packages/messagingtopics
Unit + integration tests: none
Flow tests: none
E2E (Playwright): none
SME notes:
- "Not Started" is honest. The A/B-testing scaffolding is adjacent (experiment groups, variant assignment) but does not emit funnel events to analytics-service.
- Industry baseline: 4-step funnel (search → results → guest-details → payment). Drop-off is concentrated at payment (60-70% of cart abandons happen in the Razorpay modal or at guest-details). Without funnel measurement, the abandoned-cart-recovery flow (D-011) has no trigger source.
- Forex / currency conversion is a quarter-related question: when can a non-INR guest see prices in USD/EUR? Today the booking engine is INR-only per story. Conversion-funnel data would tell us how many sessions drop because the guest sees ₹ and bounces.
Drift findings: (none — story-vs-reality matches)
- [SME-PROPOSED] Add AC: "Funnel events
booking-engine.funnel.search,…results-viewed,…room-selected,…guest-details-entered,…payment-initiated,…payment-completed,…booking-abandonedSHALL publish to Kafka topicbooking_engine_funnel. Payload:{ sessionId, propertyId, tenantId, step, timestamp, locale, currency, deviceType }.hmp-analytics-serviceSHALL materializemv_booking_funnel_daily(per-property step counts) andmv_abandoned_cart_recovery_targets(sessions stuck at payment step for >30 min)." - [SME-PROPOSED] Add AC (new candidate
US-011-NEW-FOREX): "Booking engine SHALL display prices in the guest's locale currency (USD/EUR/GBP/SGD) using mid-market FX rates refreshed 4x daily from RBI reference rates. Razorpay still charges in INR; the conversion is display-only. Coverage: 5 specs covering INR display, USD display, FX cache TTL, fallback when FX feed unavailable, rounding rules."
US-011-016 — Mobile-Responsive Optimization
Catalog status: Built (P0, 3 points) Verified status: Built
Code locations:
hmp-booking-engine/src/— Mobile-first design perCLAUDE.mddirection-a/b theme presetshmp-booking-engine/src/components/booking/— Cards/grids use mobile-first responsive utilitieshmp-booking-engine/src/components/common/— Sheet primitive (Radix Dialog wrapped as bottom drawer) per platform CLAUDE.md Shared UI Primitiveshmp-booking-engine/src/hooks/useMediaQuery.ts— Breakpoint hook
Unit + integration tests: covered indirectly via component tests
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/booking-mobile.spec.ts(per glob — covers mobile path)
E2E (Playwright):
hmp-booking-engine/e2e/booking-mobile.spec.tshmp-booking-engine/e2e/mobile-responsive.spec.ts
SME notes:
- "Mobile-first" is a design principle — but without a measurement against the Lighthouse > 80 acceptance criterion from EPIC §8 AC-6, "Built" is aspirational. No CI gate on Lighthouse score enumerated.
- Indian-market reality: 70%+ of booking-engine traffic is mobile (per industry data — KPI §9 target "Mobile booking share > 55%"). A 320px width minimum is the right target, but most tooling tests 375px+ default.
- KPI §9 also asks "Page load time (LCP) < 2.5s" — no RUM (Real User Monitoring) integration enumerated in
src/. Without RUM, we don't know if real guests hit the 2.5s target.
Drift findings: (none net new — performance target gap is a CI concern)
- [SME-PROPOSED] Add AC: "Lighthouse CI SHALL run on every booking-engine PR with thresholds Performance ≥ 80, Accessibility ≥ 90, Best Practices ≥ 90 on the standalone-mode bundle. Configured via
hmp-booking-engine/.lighthouserc.jsonand gated in the Azure Pipeline." - [SME-PROPOSED] Add AC: "RUM (Real User Monitoring) SHALL emit
web-vitals(LCP,FID,CLS,INP) tohmp-analytics-servicevia the samebooking_engine_funnelKafka topic withstep='rum'. Threshold alerts: LCP > 4s for p75 of any property over 7 days emits Slack #booking-engine-perf alert."