Skip to main content

billing-service

Bounded Context: Billing & Folio (Core) · Owner: Finance · Phase: 0 · Storage: Cloud SQL Postgres schema-per-tenant for guest folios + central schema for tenant subscription billing + transactional outbox · Bundle: services/billing-service/

billing-service owns two distinct sub-domains under one service: (a) the Guest Folio — the financial ledger attached to every reservation, with charges, taxes, payments, refunds, multi-currency settlement, and a per-stay invoice; and (b) Tenant Subscription Billing — the platform's own billing of its tenants for using Ghasi Melmastoon (per-room-month or flat plan, plus AI / storage usage overage). The two sub-domains share storage tooling, the outbox, the tax engine, and the invoice renderer, but they live in different schemas, different RBAC scopes, and different RLS modes because they protect different things.

The service does not capture money (payment-gateway-service) and does not compute base prices (pricing-service). It owns the ledger truth: every monetary fact about a stay or about a tenant's platform bill, in the order it occurred, with the audit chain regulators expect.

Purpose

  • Be the single authoritative ledger for money owed and money paid against a reservation, and for money owed and money paid by a tenant for the platform.
  • Generate tax-correct invoices in the right locale (Pashto / Dari / Arabic RTL / EN / FR) with jurisdiction-specific numbering, multi-currency display, and Sharia-compliant variants.
  • Provide first-class cash workflows on the Electron desktop: cash drawer open / record / close with two-staff sign-off, online-only close, variance detection, daily reconciliation against the folio.
  • Provide automated subscription billing for tenants: usage metering (rooms, AI tokens, storage), monthly invoice generation, dunning for unpaid invoices, automatic suspension past hard caps.

Key responsibilities

  1. Folio lifecyclepending → open → balance_due → settled → closed with re-open under supervisor override; first-class cash, multi-currency, partial-refund, and credit-note paths.
  2. Open folio at melmastoon.reservation.confirmed.v1 (eager) or melmastoon.reservation.checked_in.v1 (deferred, per tenant policy); apply room-night charges from pricing-service's rate-plan snapshot.
  3. Add charges — room nights, taxes, fees, mini-bar, restaurant, laundry, additional services. Each charge carries a taxCode resolved through the per-tenant tax engine; tax computed at post-time, not invoice-time.
  4. Record payments — cash payments captured via the desktop cash-drawer flow; card / PayPal / MFS payments linked to payment-gateway-service paymentIds via consumed events. Cash-on-arrival is first-class, not a degraded path.
  5. Process refunds — partial and full; compute refund amount per the rate plan's refund policy; emit credit notes; release tax appropriately; reconcile with payment-gateway-service for the wire transfer.
  6. Close folio at checkout — settle balance, emit folio.closed.v1, generate guest invoice (PDF stored in file-storage-service), publish invoice.generated.v1.
  7. Multi-currency settlement — folios may carry charges in tenant currency and payments in guest currency; the FX snapshot pinned on the reservation governs settlement; rounding is half-up banker's at currency minor-unit precision.
  8. Cash drawer session management — desktop opens a session with a counted opening float; every cash receipt and refund posts to the session; close requires two operators authenticated against iam-service plus a counted closing float; variance over the tenant-configured threshold flags cash_drawer.discrepancy_found.v1 and blocks the next session start until reconciled.
  9. Government / corporate / agent invoices — different invoice templates with the correct VAT registration display, customer-class fields, and signature blocks; bilingual layout (Latin + Arabic numerals on the same Saudi/UAE document).
  10. Tenant subscription billingSubscription aggregate per tenant with plan (per-room-month or flat) plus metered usage overage; monthly cycle generates SubscriptionInvoice; dunning state machine (current → grace → past_due → suspended) with automatic platform-suspension past hard cap.
  11. Usage metering — consume melmastoon.property.room.activated.v1, melmastoon.ai_orchestrator.completion.recorded.v1, melmastoon.file_storage.bytes.measured.v1 and aggregate per (tenantId, billingPeriod, meter) in a partitioned usage_records table.

Aggregates owned

AggregateCardinalityPurposeID prefix
Folio1 per reservationLedger root for one stayfol_
FolioCharge0..N per folioOne posted charge with taxchg_
FolioPayment0..N per folioOne recorded payment (cash or external)fpm_ (new)
FolioRefund0..N per folioOne processed refundfrd_ (new)
Invoice0..N per folio (typically 1)Issued invoice documentinv_doc_
InvoiceLine1..N per invoiceLine items with tax(composite)
CreditNote0..N per folioCompensating doc for refunds / correctionscnt_ (new)
Subscription1 per tenantPlatform subscription statesub_ (new)
SubscriptionInvoice0..N per subscriptionMonthly platform invoicesin_ (new)
UsageRecordM per (tenantId, period, meter)Aggregated metered usageusg_ (new)
CashDrawer1 per propertyDrawer registrycdr_ (new)
CashDrawerSession0..N per drawerOne shift/session with floatcds_ (new)
Settlement1 per closed folioSettlement summaryset_ (new)

New ID prefixes are declared in the service DATA_MODEL.md and added to standards/NAMING.md in the same PR.

Key APIs (REST, /api/v1)

MethodPathPurpose
POST/foliosOpen a folio (idempotent via Idempotency-Key)
GET/folios/:idRead folio + balance
POST/folios/:id/chargesPost a charge (server computes tax)
POST/folios/:id/paymentsRecord a payment (cash via desktop or link a paymentId)
POST/folios/:id/refundsIssue a refund (policy-checked)
POST/folios/:id/closeClose folio + generate invoice
POST/folios/:id/reopenReopen (supervisor override; audited)
POST/invoices/:id/credit-notesIssue a credit note
GET/invoices/:id.pdfDownload invoice PDF (signed URL via file-storage)
POST/cash-drawers/:id/sessionsOpen a session (counted opening float)
POST/cash-sessions/:id/closeClose session (two-staff sign-off; online only)
GET/cash-sessions/:id/reconciliationCash reconciliation view
GET/subscriptions/:tenantIdRead subscription + usage
POST/subscriptions/:tenantId/cycle(Platform-admin) trigger billing cycle
POST/subscriptions/:tenantId/reactivateReactivate suspended subscription

Consumed by bff-backoffice-service for the desktop, by bff-tenant-booking-service for the post-booking receipt, and by an internal platform-admin BFF for subscription operations.

Key events published

EventTrigger
melmastoon.billing.folio.opened.v1Folio created on reservation.confirmed.v1 or reservation.checked_in.v1
melmastoon.billing.folio.charge_added.v1Any charge posted
melmastoon.billing.folio.payment_recorded.v1Any payment recorded
melmastoon.billing.folio.refund_recorded.v1Refund processed
melmastoon.billing.folio.closed.v1Folio settled and closed
melmastoon.billing.folio.balance_due.v1Folio still owes at close attempt
melmastoon.billing.invoice.generated.v1Invoice document persisted
melmastoon.billing.invoice.sent.v1Invoice delivered (via notification-service)
melmastoon.billing.credit_note.generated.v1Credit note issued
melmastoon.billing.cash_drawer.opened.v1Cash drawer session opened
melmastoon.billing.cash_drawer.closed.v1Cash drawer session closed
melmastoon.billing.cash_drawer.discrepancy_found.v1Variance > tenant threshold
melmastoon.billing.subscription.created.v1Subscription created on tenant.created.v1
melmastoon.billing.subscription.invoice_generated.v1Monthly subscription invoice created
melmastoon.billing.subscription.payment_failed.v1Subscription invoice payment failed
melmastoon.billing.subscription.cancelled.v1Subscription cancelled
melmastoon.billing.subscription.reactivated.v1Subscription resumed
melmastoon.billing.usage.recorded.v1Usage aggregation snapshot persisted

Key events consumed

EventEffect
melmastoon.reservation.confirmed.v1Open folio (eager mode) or store eligibility (deferred)
melmastoon.reservation.checked_in.v1Open folio if not yet; post arrival-day charges per rate plan
melmastoon.reservation.checked_out.v1Close folio + generate invoice
melmastoon.reservation.cancelled.v1Refund per policy; emit credit note where applicable
melmastoon.payment.transaction.captured.v1Record FolioPayment linked to paymentId
melmastoon.payment.transaction.refunded.v1Reconcile refund against folio refund record
melmastoon.tenant.created.v1Initialize Subscription (default plan); provision per-tenant billing schema
melmastoon.property.room.activated.v1Increment room_count usage meter
melmastoon.ai_orchestrator.completion.recorded.v1Increment AI-token usage meter
melmastoon.file_storage.bytes.measured.v1Increment storage usage meter

Upstream / downstream

Upstream (we consume): reservation-service, payment-gateway-service, tenant-service, pricing-service, property-service, ai-orchestrator-service, file-storage-service.

Downstream (we publish for): notification-service (invoice send, dunning), analytics-service, reporting-service, audit-service, search-aggregation-service (none — no PII / financial detail leaves to that service), bff-backoffice-service (desktop folio, cash drawer), bff-tenant-booking-service (post-booking receipt), sync-service (folio replication for active stays).

Non-functional requirements

NFRTarget
Invoice generation latency p95< 2 s end-to-end (charge tally → tax computation → PDF render → file-storage URL)
Cash drawer close latency p95< 5 s (counted close → variance compute → two-staff sign-off → event publish)
Folio balance read p99< 200 ms
Tax computation correctness100% match against jurisdictional reference tables (CI gated)
Cash variance thresholdtenant-configured (default 0.5% or AFN 100, whichever is greater)
Tenant isolationschema-per-tenant (per ADR-0002); RLS enabled inside each schema as defense in depth
API availability99.95% monthly
Subscription dunning correctness100% — no double-charge, no missed dunning step
ReplicasMin 3 Cloud Run instances (hot path); separate per-tenant schema migration job + monthly subscription cycle worker
Audit retention7 years for financial events (per 07-security §11.2)

Where to go next