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 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 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 issuance — POST /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​