pricing-service
Bounded Context: Pricing & Rates (Core) · Owner: Revenue · Phase: 1 · Storage: Cloud SQL Postgres (shared schema + RLS) + Memorystore (rate cache) + transactional outbox · Bundle: services/pricing-service/
pricing-service is the authoritative aggregate root for rate plans, rate rules, discounts, promotions, tax composition, fee composition, FX snapshots, and the price-calculation engine that produces a PriceQuote for reservation-service. It is a Core bounded context — every booking funnel, every modification with delta, and every backoffice rate sheet flows through this service. The Electron desktop replicates active rate plans, tax/fee rules, and the latest FX snapshot so front-desk staff can quote walk-ins offline.
The service does not own inventory availability (delegated to inventory-service), money capture (payment-gateway-service), folio/ledger entries (billing-service), reservation lifecycle (reservation-service), or the LLM model itself — AI dynamic-pricing suggestions are advisory and routed through ai-orchestrator-service with mandatory HITL gates.
Purpose
- Be the single authoritative engine that turns a
(tenantId, propertyId, roomTypeId[], stayWindow, channel, promoCode?, ratePlanHint?)request into a deterministicPriceQuotewith line-item breakdown, taxes, fees, discounts, currency, FX snapshot, and TTL. - Own rate-plan lifecycle (
draft → published → archived) with per-tenant audit and event emission so downstream services (search aggregation, BFFs, analytics) can react to rate changes. - Compose taxes and fees from per-jurisdiction rule sets — VAT, tourism tax, resort fee, cleaning fee — with explicit rate-vs-flat semantics and inclusion/exclusion in display price.
- Provide AI-assisted dynamic pricing suggestions that are always advisory and gated on human approval before any rate is mutated. The AI never edits a rate plan directly.
- Replicate rate definitions (plans, rules, taxes, fees, current FX snapshot) to the Electron desktop so walk-in quoting works offline; quotes themselves are not replicated — they are short-lived and must be issued by the cloud authority.
Key responsibilities
- RatePlan CRUD —
BAR,Weekly,Government,Corporate,NonRefundable,Package(room+breakfast or room+halal-meal-plan),Group. Each carries currency, channel scope, refundability, deposit policy, advance-purchase window, max LOS, min LOS, and an ordered set ofRateRules. - RateRule engine — per
(date range × room-type × day-of-week)define a base nightly rate, multiplier, surcharge, and LOS modifier; rules cascade in priority order with a deterministic tie-break. - Discounts — long-stay (LOS), advance-purchase, last-minute, loyalty, group; applied as a structured pipeline so each discount line is auditable.
- Promotions — promo codes with usage caps, tenant scoping, redemption tracking, and race-safe redemption increment.
- Tax & fee composition — per-jurisdiction
TaxRuleandFeeRule(rate or flat, inclusive/exclusive) keyed by(country, region, propertyId?, ratePlanCategory?, dateRange). - FX snapshots — daily refresh from a configured FX provider with cached fallback, freshness flag, and tenant-pinned override; consumed at quote-time and locked on confirm by
reservation-service. - PriceQuote issuance —
POST /quotesproduces a quote with TTL (default 30 minutes) and all derivation context;GET /quotes/{id}re-fetches before its TTL elapses. - AI dynamic-pricing suggestions — generate advisory rate ranges per
(propertyId, roomTypeId, date)from occupancy, booking pace, competitor proxies, and event calendar; surface to the backoffice; require revenue-manager approval before mutation. - Sharia-compliant rate option — flag plans as
shariaCompliant: no compounding deposits, transparent fees only, explicit no-riba clause. The engine refuses to apply percentage interest to deposits on these plans. - Multi-currency display — produce
displayCurrencytotals derived frombaseCurrencyvia the activeFxRateSnapshot, with explicit rounding policy per currency (IRR rounds to nearest 1000, AFN to nearest 1, USD to 0.01).
Aggregates owned
| Aggregate | Cardinality | Purpose | Identity prefix |
|---|---|---|---|
RatePlan | per tenant + property | Container for rate rules, refundability, channel scope | rate_ |
RateRule | 1..N per RatePlan | Date×room-type×DOW priced rule | rru_ |
RatePlanRoomType | linkage | Which room types this plan covers | (composite) |
Discount | 0..N per RatePlan | LOS / advance-purchase / loyalty / last-minute | dsc_ |
Promotion | per tenant | Promo codes with usage caps | prm_ |
PromotionRedemption | per redemption | Audit + race-safe counter row | (composite, ULID) |
TaxRule | per tenant + jurisdiction | VAT / tourism tax / hotel tax | tax_ |
FeeRule | per tenant + property | Resort fee / cleaning fee | fee_ |
PriceQuote | short-lived (TTL 30 min) | Pinned price derivation context | qte_ |
FxRateSnapshot | per (base, quote, day) | Daily FX rate with provider provenance | fxs_ |
Key APIs (REST, /api/v1)
| Method | Path | Purpose |
|---|---|---|
POST | /rate-plans | Create draft RatePlan |
GET | /rate-plans/:id | Read RatePlan with rules + linkages |
PATCH | /rate-plans/:id | Update (draft only) |
POST | /rate-plans/:id/publish | Publish (validates rules cascade) |
POST | /rate-plans/:id/archive | Archive (active future bookings preserved) |
POST | /rate-plans/:id/rules | Append RateRule |
PATCH | /rate-plans/:id/rules/:ruleId | Update RateRule |
POST | /promotions | Create Promotion |
POST | /promotions/:id/activate | Activate (gate: validation) |
POST | /promotions/:id/deactivate | Deactivate |
POST | /tax-rules | Create/update TaxRule |
POST | /fee-rules | Create/update FeeRule |
POST | /quotes | Issue PriceQuote (with TTL) |
GET | /quotes/:id | Re-read live PriceQuote |
POST | /fx/refresh | Force FX snapshot refresh (admin) |
GET | /fx/snapshots/latest | Read latest FX snapshot |
POST | /dynamic-suggestions | Generate AI dynamic-pricing suggestion (advisory) |
POST | /dynamic-suggestions/:id/accept | HITL accept → applies as a pinned RateRule override |
Consumed by bff-tenant-booking-service (guest funnel quote), bff-backoffice-service (revenue manager UI), and reservation-service (server-to-server quote/re-quote).
Key events published
| Event | Trigger |
|---|---|
melmastoon.pricing.rate_plan.created.v1 | RatePlan created (draft) |
melmastoon.pricing.rate_plan.updated.v1 | RatePlan mutated |
melmastoon.pricing.rate_plan.archived.v1 | RatePlan archived |
melmastoon.pricing.rate_rule.created.v1 | RateRule appended |
melmastoon.pricing.rate_rule.updated.v1 | RateRule mutated |
melmastoon.pricing.promotion.created.v1 | Promotion created (draft) |
melmastoon.pricing.promotion.activated.v1 | Promotion now applicable |
melmastoon.pricing.promotion.deactivated.v1 | Promotion withdrawn |
melmastoon.pricing.quote.requested.v1 | Quote request received (analytics) |
melmastoon.pricing.quote.created.v1 | Quote derived and pinned |
melmastoon.pricing.quote.expired.v1 | TTL elapsed without redemption |
melmastoon.pricing.tax_rule.updated.v1 | TaxRule mutated (sync target for desktop) |
melmastoon.pricing.fx_snapshot.updated.v1 | FX snapshot refreshed |
melmastoon.pricing.dynamic_suggestion.generated.v1 | AI suggestion produced |
melmastoon.pricing.dynamic_suggestion.accepted.v1 | Revenue manager accepted suggestion |
Key events consumed
| Event | Effect |
|---|---|
melmastoon.tenant.config_updated.v1 | Refresh tenant defaults: billing currency, rounding, default tax jurisdiction |
melmastoon.property.room_type.updated.v1 | Re-validate RatePlanRoomType links; archive orphaned linkages |
melmastoon.inventory.allocation.failed.v1 | Mark in-flight quotes referring to that (property, roomType, date) as stale=true; emit quote.expired.v1 early |
melmastoon.ai.suggestion.dynamic_pricing.v1 | Persist AI suggestion as a dynamic_suggestion record (advisory, awaiting HITL) |
Upstream / downstream
Upstream (we consume): tenant-service (currency/locale/jurisdiction defaults), property-service (room types), inventory-service (allocation-failed signal), ai-orchestrator-service (dynamic-pricing model output).
Downstream (we publish for): reservation-service (quote requested by booking saga), billing-service (tax/fee snapshot referenced by folio), search-aggregation-service (rate-plan published → re-index property listings), analytics-service, audit-service, sync-service (rate definitions + FX snapshot replicate to desktop), bff-backoffice-service, bff-tenant-booking-service.
Non-functional requirements
| NFR | Target |
|---|---|
| Quote latency p99 | < 250 ms (cache-warmed); p99 < 500 ms cold |
| Quote latency p50 | < 60 ms |
| FX snapshot freshness SLO | refresh every 6 h; serve up to 24 h with stale=true flag if provider down |
| Promo redemption race-safety | strict per-promo serialization (advisory lock or row-version + retry); zero overuse beyond usageCap |
| Tenant isolation | RLS-enforced; tenant-isolation.spec.ts mandatory in CI |
| Sync footprint to desktop | All published rate plans, active rules, applicable tax/fee rules for tenant jurisdictions, latest FX snapshot |
| Replicas | Min 3 Cloud Run instances; separate hold-expiry-style quote-expiry worker (every 60 s) and fx-refresh worker (every 6 h) |
| AI dynamic pricing | Advisory only; HITL gate enforced server-side; no auto-mutation of rate plans |
Where to go next
- Implementation-grade detail:
services/pricing-service/SERVICE_OVERVIEW.mdand the rest of the 17-doc bundle. - Booking saga consumption of quotes:
docs/03-microservices/reservation-service.mdand the bundle. - API conventions:
docs/05-api-design.md. - Data model conventions and ID prefixes:
docs/06-data-models.md,docs/standards/NAMING.md. - Payments architecture (cash on arrival, deposit semantics, refundability hooks):
docs/10-payments-architecture.md. - AI provenance and HITL pattern:
docs/08-ai-architecture.md.