Skip to main content

Billing Service — Security Model

Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · 13 Security · 14 Extended compliance

1. Trust boundaries

2. AuthN

  • Inbound: Bearer JWT issued by identity-service; validated via JWKS (IDENTITY_JWKS_URL); iss, aud=billing-service, tid, sub required.
  • Outbound: mTLS for service-to-service; service account JWT for terminology / fhir-gateway / tenant-service.
  • Patient portal traffic enters through patient-portal-service BFF; billing sees a downstream service JWT with patient scope claims.

3. AuthZ — RBAC / ABAC matrix

ScopeRoles (default)PurposeABAC
billing:charge:readbilling_clerk, cashier, supervisor, auditorRead chargestid match
billing:charge:writebilling_clerk, system (event handler)Capture chargestid + facility match
billing:charge:reversesupervisorReverse a posted chargethreshold amount per tenant config
billing:account:readbilling_clerk, cashier, supervisor, patient (self via portal)Read accounts + agingPatient sees only patient_id == self
billing:invoice:readbilling_clerk, cashier, supervisor, patient (self)Read invoicesPatient scope filter
billing:invoice:issuebilling_clerkIssue a draft invoicefacility match
billing:invoice:voidsupervisorVoid an invoicereason code required
billing:payment:postcashier, system (remittance)Post paymentIdempotency enforced
billing:refund:requestcashierRequest refundany
billing:refund:approvesupervisor, finance_managerApprove refunddual-approval if amount > tenant threshold
billing:adjustment:postbiller, system (remittance)Apply adjustmentreason code + facility match
billing:statement:runbilling_adminStart batch statementfacility match
billing:pricelist:managetenant_adminManage price listsfacility match
billing:gl:exportfinance_managerGL exporttenant match

4. Licensing gate

Before any route handler executes, LicensingGuard calls tenant-service.checkEntitlement('billing'). If disabled → 403 MODULE_NOT_ACTIVE. Result cached per tenant for 60 s.

5. Data classification

Field classExamplesStorage
PHIpatient_id linkage, encounter contextEncrypted at rest (pgcrypto disk + KMS); RLS by tenant_id
FinancialAmounts, ledger entriesEncrypted at rest; integrity-protected by append-only trigger
PayerCoverage refs passed in (no member IDs stored here)References only, not raw PII
SecretsPayment gateway creds, JWT keysKMS or HashiCorp Vault; never in env files
PCICard dataNever stored — adapter tokens only

6. Encryption

  • At rest: Postgres with LUKS/EBS volume encryption; column-level AES-GCM via pgcrypto for external_ref, reference, PII audit fields.
  • In transit: TLS 1.3 on all links; mTLS inside service mesh.
  • KMS: AWS KMS / Vault Transit for data keys; annual rotation.

7. Audit events (emitted to audit-service)

EventWhen
audit.billing.charge.capturedCharge posted
audit.billing.charge.reversedCharge reversed
audit.billing.invoice.issuedInvoice issued
audit.billing.invoice.voidedInvoice voided
audit.billing.payment.postedPayment posted
audit.billing.refund.requested/approved/rejected/postedRefund lifecycle
audit.billing.adjustment.appliedAdjustment posted
audit.billing.pricelist.publishedPrice list change
audit.billing.gl.exportExport run
audit.billing.access.unauthorized403 / cross-tenant attempt

All audit events include actor_id, tenant_id, correlation_id, request_id, ip_hash, and a minimal resource_ref (no payloads with PII).

8. GDPR participation

RightBilling behaviour
AccessPatient can retrieve their invoices + payments via portal; export as FHIR Bundle
RectificationCharge reversal + new charge; ledger immutability preserved
ErasureRestricted — financial records retained per legal hold (commonly 10 y). Personal identifiers pseudonymised; monetary records retained
PortabilityFHIR Bundle (Account + Invoices + Payments) export
Objection / restrictionFlag on account; block new charges

9. Tenant isolation tests (mandatory)

  • tenant-isolation.spec.ts must attempt cross-tenant read/write on every table; every attempt must return empty / 403 / RLS deny.
  • PostgreSQL app.tenant_id session variable must be set by every request handler before any query.

10. Key security controls

ControlImplementation
Input validationZod schemas on every DTO, rejects unknown fields
SQL injectionDrizzle parameterised queries only; no string concat
XSS in PDFsServer-side rendering with escaping; user-supplied text sanitised
CSRFState-changing endpoints require Origin header match and Bearer auth (no cookies)
Rate limitingKong: 100 rps per client per route; POST /payments 20 rps per tenant
Bot protectionKong bot detector on patient-facing routes
Data loss preventionNo PAN/CVV storage; static analysis blocks logs of sensitive fields
Supply chainnpm audit --production; trivy image scan in CI
Secret rotation90 d for service JWT signing key; 30 d for gateway adapter tokens