Billing Service — Service Overview
Status: populated
Owner: TBD
Last updated: 2026-04-17
Companion: Service Template · 03 platform-services · 02 DDD · FHIR-first
1. Purpose
The billing-service owns the tenant-scoped financial ledger for the Ghasi-eHealth platform. It turns clinical activity (encounters, orders, procedures, dispensed medications, telehealth sessions, immunizations) into charges, aggregates those charges into patient accounts and invoices, posts payments, refunds, and adjustments, and produces statements for delivery. It is the platform's authoritative source for "what was charged, what was paid, and what remains outstanding" per patient, per facility, per tenant.
The service is FHIR-first — Account, ChargeItem, Invoice, PaymentNotice, and PaymentReconciliation are the canonical exchange resources. An immutable local ledger is kept for operational performance, reporting, and ERP export.
2. Bounded Context
Financial Management — Patient Accounting. Classified as Supporting (not a commodity, but not a differentiator either): every national-scale EHR needs an accurate ledger, but the differentiating intelligence lives in clinical and scheduling. The context is bordered by:
| Border | Neighbour | Interaction |
|---|
| Upstream | registration-service, scheduling-service, orders-service, medication-service, laboratory-service, radiology-service, immunizations-service, virtual-care-service | Consumes billable-event streams to capture charges |
| Upstream (payer) | claims-service | Consumes remittance / ClaimResponse events to post adjustments and payer payments |
| Downstream | claims-service, patient-portal-service, communication-service, population-health-service, external ERP | Publishes charge/invoice/payment events for claim submission, statement delivery, patient balance display, revenue analytics, GL export |
3. Responsibilities
| Area | Billing owns |
|---|
| Charge capture | ChargeItem creation from encounter + order + procedure + dispense context, coded (CPT/HCPCS/ICHI/local), modified, priced |
| Price lists | Facility- and program-scoped price books with effective dating |
| Patient accounting | Patient Account aggregate: balance, aging buckets, statements, payment plans |
| Invoice lifecycle | Draft → issued → paid / partially-paid / voided with line-item immutability after issue |
| Payment posting | Cash, card, bank transfer, mobile money (Afghanistan context), payer remittance; idempotent Idempotency-Key required |
| Adjustments | Write-offs, corrections, courtesy discounts, contractual adjustments; approval workflow by threshold |
| Refunds | Controlled, dual-approval above threshold; emits dedicated event |
| Statements | Batch statement runs (PDF, SMS-link, email); per-tenant schedule; delivery tracking |
| Multi-currency + tax | AFN, AED, USD at minimum; per-facility VAT/tax rules with effective dates |
| Ledger export | GL/ERP batch export hooks (CSV/JSON) by tenant/facility/date range |
4. Non-Responsibilities
| Area | Owner | Why not billing |
|---|
| Claim assembly + payer submission | claims-service | Separation between patient accounting and payer lifecycle |
| Insurance coverage + eligibility | claims-service | Coverage checks sit alongside claim authoring; billing consumes results |
| Price quoting UX / portal | patient-portal-service + frontend BFF | Billing provides APIs; portal renders patient balance views |
| Card processor integration | payment gateway adapter (external) | PCI scope stays out of core service; billing speaks an adapter port |
| Accounting journals & financial statements | Customer ERP | Billing exports events; GL lives in customer system |
| Clinical coding education / CDI | clinical-notes + CDI tooling | Billing validates codes at capture; it does not author them |
5. Dependencies
5.1 Upstream
| Dependency | Pattern | Purpose |
|---|
| registration-service | Event consumer | registration.encounter.admitted/discharged.v1 → encounter context for charge capture |
| scheduling-service | Event consumer | scheduling.appointment.completed.v1 → outpatient visit triggers charge |
| orders-service | Event consumer | orders.service_request.completed.v1 → lab/rad/procedure charges |
| medication-service | Event consumer | medication.administration.recorded.v1 / dispense events → drug charges |
| virtual-care-service | Event consumer | virtual_care.billing.session_chargeable.v1 → telehealth encounter charge |
| immunizations-service | Event consumer | immunizations.administration.recorded.v1 → vaccine + admin fee charge |
| terminology-service | API | CPT/HCPCS/ICHI/ICD validation and value-set membership |
| tenant-service | API | Facility metadata, price list ownership, tenant currency defaults |
| identity-service | JWT | Actor identity + permission scopes for refund/adjustment approval |
| claims-service | Event consumer | claims.remittance.posted.v1 → auto-post payer payments + contractual adjustments |
| interop-service (fhir-gateway) | API | Writes ChargeItem, Invoice, Account, PaymentReconciliation when external FHIR consumers request them |
5.2 Downstream consumers
| Consumer | Pattern | What they consume |
|---|
| claims-service | Event consumer | billing.invoice.issued.v1 → candidate for payer claim |
| patient-portal-service | API (BFF) | Account balance, invoice list, statement PDFs, payment submission |
| communication-service | Event consumer | billing.statement.generated.v1 → SMS/email delivery |
| population-health-service | Event consumer | Revenue + utilisation analytics |
| ERP / GL adapter | Event consumer + batch export | billing.charge.captured.v1, billing.payment.posted.v1, billing.adjustment.applied.v1 |
| audit-service | Event consumer | All billing events → retention 10+ years |
6. Slice Involvement
| Slice | Scope | Milestone |
|---|
| S2 — Revenue Capture | Charge capture from encounters, basic patient accounts, simple invoice + cash payment | M1 |
| S3 — Statement & Payer | Invoice issuance, statement runs, payer remittance ingestion, multi-currency, tax | M2 |
| S4 — Enterprise | GL export, payment-plan management, refund approval, dashboards, ERP adapters | M3 |
7. Architectural Freeze Points
| Freeze | What is frozen | By |
|---|
| F-BILL-01 | Monetary representation: Money = { currency: ISO-4217, minor_units: bigint } — no floats | End of M1 |
| F-BILL-02 | Ledger append-only invariant — no in-place mutation on ledger_entries | End of M1 |
| F-BILL-03 | Event schemas v1 (billing.charge.captured.v1, billing.invoice.issued.v1, billing.payment.posted.v1, billing.adjustment.applied.v1, billing.refund.issued.v1, billing.statement.generated.v1) | End of M2 |
| F-BILL-04 | Idempotency-Key contract for POST /payments and POST /refunds | End of M1 |
8. Readiness levels
| Level | Target milestone | Criteria |
|---|
| L2 | M1 | Charge capture, account balance, cash payment posting in a single facility |
| L3 | M2 | Multi-facility, issued invoices, statement runs, payer remittance, SLO tracking |
| L4 | M3 | Refund approvals, GL export, ERP adapters, chaos-tested, dashboards in Grafana |
9. Architecture diagram
10. Key design decisions
- Immutable ledger, derived views.
ledger_entries is append-only. account_balances and aging views are materialised for reads; corrections are new reversing entries, never updates.
- Money is integer minor units. Floats are banned. All arithmetic is in
bigint minor units plus ISO-4217 currency to eliminate rounding bugs across AFN, AED, USD, etc.
- Idempotency-Key on financial mutations.
POST /payments, POST /refunds, and POST /adjustments require Idempotency-Key. Replay returns the original resource; conflicting payload with same key returns IDEMPOTENCY_CONFLICT (409).
- Price list is effective-dated. Charge capture resolves price by
facility_id + code + service_date with lookup precedence (tenant → facility → national fallback).
- Multi-currency per tenant. Each tenant has a default currency; each account is pinned to a currency at creation. Cross-currency invoicing is forbidden.
- VAT / tax is policy-driven. Tax rules live in
tax_rules with effective windows and country/facility scoping; invoices snapshot applied rates at issue time.
- Licensing gate at edge. The service is an optional module; unlicensed tenants receive
MODULE_NOT_ACTIVE (403) before any data access.
- Separation of duties. Refund/write-off approvals are enforced in-service via
billing:refund:approve scope and threshold config; dual-approval for amounts above tenant-defined limits.
- PCI stays out. No PAN/CVV ever enters the service. Card processing is delegated to a gateway adapter that returns a tokenised payment reference.
- FHIR-first exchange. External systems read/write via
Account, ChargeItem, Invoice, PaymentReconciliation through interop-service/fhir-gateway. Internal REST exists for low-friction UX.
11. Source reconciliation
Populated from services/billing-service/_sources/billing/ (SPEC, TECHNICAL_REQUIREMENTS, SOLUTION_DESIGN, API_DOCS, EVENT_MODEL, TRACEABILITY_MATRIX, backlog). Re-framed from the legacy "EHR billing module" framing to platform billing service consumed by every clinical producer (including the EHR sub-product). Event names were renamed from BILLING.* (module subjects) to the platform-canonical billing.*.v1 form per NAMING. Legacy FR-BILL-001..010 are preserved in API_CONTRACTS.md and EPICS/USER_STORIES in the "Legacy ref" columns.