Skip to main content

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 deterministic PriceQuote with 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

  1. RatePlan CRUDBAR, 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 of RateRules.
  2. 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.
  3. Discounts — long-stay (LOS), advance-purchase, last-minute, loyalty, group; applied as a structured pipeline so each discount line is auditable.
  4. Promotions — promo codes with usage caps, tenant scoping, redemption tracking, and race-safe redemption increment.
  5. Tax & fee composition — per-jurisdiction TaxRule and FeeRule (rate or flat, inclusive/exclusive) keyed by (country, region, propertyId?, ratePlanCategory?, dateRange).
  6. 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.
  7. PriceQuote issuancePOST /quotes produces a quote with TTL (default 30 minutes) and all derivation context; GET /quotes/{id} re-fetches before its TTL elapses.
  8. 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.
  9. 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.
  10. Multi-currency display — produce displayCurrency totals derived from baseCurrency via the active FxRateSnapshot, with explicit rounding policy per currency (IRR rounds to nearest 1000, AFN to nearest 1, USD to 0.01).

Aggregates owned

AggregateCardinalityPurposeIdentity prefix
RatePlanper tenant + propertyContainer for rate rules, refundability, channel scoperate_
RateRule1..N per RatePlanDate×room-type×DOW priced rulerru_
RatePlanRoomTypelinkageWhich room types this plan covers(composite)
Discount0..N per RatePlanLOS / advance-purchase / loyalty / last-minutedsc_
Promotionper tenantPromo codes with usage capsprm_
PromotionRedemptionper redemptionAudit + race-safe counter row(composite, ULID)
TaxRuleper tenant + jurisdictionVAT / tourism tax / hotel taxtax_
FeeRuleper tenant + propertyResort fee / cleaning feefee_
PriceQuoteshort-lived (TTL 30 min)Pinned price derivation contextqte_
FxRateSnapshotper (base, quote, day)Daily FX rate with provider provenancefxs_

Key APIs (REST, /api/v1)

MethodPathPurpose
POST/rate-plansCreate draft RatePlan
GET/rate-plans/:idRead RatePlan with rules + linkages
PATCH/rate-plans/:idUpdate (draft only)
POST/rate-plans/:id/publishPublish (validates rules cascade)
POST/rate-plans/:id/archiveArchive (active future bookings preserved)
POST/rate-plans/:id/rulesAppend RateRule
PATCH/rate-plans/:id/rules/:ruleIdUpdate RateRule
POST/promotionsCreate Promotion
POST/promotions/:id/activateActivate (gate: validation)
POST/promotions/:id/deactivateDeactivate
POST/tax-rulesCreate/update TaxRule
POST/fee-rulesCreate/update FeeRule
POST/quotesIssue PriceQuote (with TTL)
GET/quotes/:idRe-read live PriceQuote
POST/fx/refreshForce FX snapshot refresh (admin)
GET/fx/snapshots/latestRead latest FX snapshot
POST/dynamic-suggestionsGenerate AI dynamic-pricing suggestion (advisory)
POST/dynamic-suggestions/:id/acceptHITL 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

EventTrigger
melmastoon.pricing.rate_plan.created.v1RatePlan created (draft)
melmastoon.pricing.rate_plan.updated.v1RatePlan mutated
melmastoon.pricing.rate_plan.archived.v1RatePlan archived
melmastoon.pricing.rate_rule.created.v1RateRule appended
melmastoon.pricing.rate_rule.updated.v1RateRule mutated
melmastoon.pricing.promotion.created.v1Promotion created (draft)
melmastoon.pricing.promotion.activated.v1Promotion now applicable
melmastoon.pricing.promotion.deactivated.v1Promotion withdrawn
melmastoon.pricing.quote.requested.v1Quote request received (analytics)
melmastoon.pricing.quote.created.v1Quote derived and pinned
melmastoon.pricing.quote.expired.v1TTL elapsed without redemption
melmastoon.pricing.tax_rule.updated.v1TaxRule mutated (sync target for desktop)
melmastoon.pricing.fx_snapshot.updated.v1FX snapshot refreshed
melmastoon.pricing.dynamic_suggestion.generated.v1AI suggestion produced
melmastoon.pricing.dynamic_suggestion.accepted.v1Revenue manager accepted suggestion

Key events consumed

EventEffect
melmastoon.tenant.config_updated.v1Refresh tenant defaults: billing currency, rounding, default tax jurisdiction
melmastoon.property.room_type.updated.v1Re-validate RatePlanRoomType links; archive orphaned linkages
melmastoon.inventory.allocation.failed.v1Mark in-flight quotes referring to that (property, roomType, date) as stale=true; emit quote.expired.v1 early
melmastoon.ai.suggestion.dynamic_pricing.v1Persist 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

NFRTarget
Quote latency p99< 250 ms (cache-warmed); p99 < 500 ms cold
Quote latency p50< 60 ms
FX snapshot freshness SLOrefresh every 6 h; serve up to 24 h with stale=true flag if provider down
Promo redemption race-safetystrict per-promo serialization (advisory lock or row-version + retry); zero overuse beyond usageCap
Tenant isolationRLS-enforced; tenant-isolation.spec.ts mandatory in CI
Sync footprint to desktopAll published rate plans, active rules, applicable tax/fee rules for tenant jurisdictions, latest FX snapshot
ReplicasMin 3 Cloud Run instances; separate hold-expiry-style quote-expiry worker (every 60 s) and fx-refresh worker (every 6 h)
AI dynamic pricingAdvisory only; HITL gate enforced server-side; no auto-mutation of rate plans

Where to go next