Billing Service — Event Schemas
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · Event-driven arch · NAMING
1. Envelope
All events follow the platform EventEnvelope (CloudEvents 1.0 profile):
{
"specversion": "1.0",
"id": "01J0...", // ULID
"source": "ghasi/billing-service",
"type": "billing.invoice.issued.v1",
"subject": "billing.invoice.issued.v1", // NATS subject
"time": "2026-04-17T10:00:00Z",
"datacontenttype": "application/json",
"tenantid": "ten_01J0...",
"actorid": "usr_01J0...",
"correlationid": "req_01J0...",
"data": { ... }
}
- Subject = type per NAMING.
- Versioning:
vNsuffix. Breaking changes openv(N+1)alongsidevNfor a deprecation window.
2. Published events
| Subject / Type | Aggregate | Retention class | Consumers |
|---|---|---|---|
billing.charge.captured.v1 | Charge | 10y (financial) | claims-service, ERP, audit-service, population-health-service |
billing.charge.reversed.v1 | Charge | 10y | claims-service, ERP, audit-service |
billing.invoice.drafted.v1 | Invoice | 30d (operational) | patient-portal-service |
billing.invoice.issued.v1 | Invoice | 10y | claims-service, patient-portal-service, communication-service, audit-service |
billing.invoice.voided.v1 | Invoice | 10y | claims-service, audit-service |
billing.invoice.paid.v1 | Invoice | 10y | claims-service, patient-portal-service, audit-service |
billing.payment.posted.v1 | Payment | 10y | claims-service, ERP, audit-service, patient-portal-service |
billing.payment.reversed.v1 | Payment | 10y | claims-service, ERP, audit-service |
billing.refund.requested.v1 | Refund | 10y | audit-service, communication-service (notify approver) |
billing.refund.approved.v1 | Refund | 10y | audit-service |
billing.refund.rejected.v1 | Refund | 10y | audit-service |
billing.refund.issued.v1 | Refund | 10y | ERP, audit-service |
billing.adjustment.applied.v1 | Adjustment | 10y | claims-service, ERP, audit-service |
billing.statement.started.v1 | StatementRun | 30d | ops dashboards |
billing.statement.generated.v1 | StatementRun | 1y | communication-service, audit-service |
billing.statement.delivered.v1 | StatementRun | 1y | audit-service |
billing.price_list.published.v1 | PriceList | 5y | audit-service, tenant-service |
billing.account.suspended.v1 | Account | 10y | patient-portal-service, audit-service |
3. Consumed events
| Subject | Producer | Handler outcome |
|---|---|---|
registration.encounter.discharged.v1 | registration-service | CaptureCharge for each billable item |
scheduling.appointment.completed.v1 | scheduling-service | CaptureCharge for outpatient visit code |
orders.service_request.completed.v1 | orders-service | CaptureCharge for ordered procedure / lab / radiology |
medication.administration.recorded.v1 | medication-service | CaptureCharge for drug + administration fee |
immunizations.administration.recorded.v1 | immunizations-service | CaptureCharge for vaccine + admin fee |
virtual_care.billing.session_chargeable.v1 | virtual-care-service | CaptureCharge for telehealth encounter |
laboratory.result.finalised.v1 | laboratory-service | CaptureCharge for result fees if not already captured |
radiology.study.finalised.v1 | radiology-service | CaptureCharge for study fees |
claims.remittance.posted.v1 | claims-service | PostPayment(PAYER_REMITTANCE) + ApplyAdjustment(CONTRACTUAL) |
claims.claim.denied.v1 | claims-service | Notify billing queue for rework; no ledger impact |
tenant.facility.updated.v1 | tenant-service | Invalidate price-list cache for facility |
gdpr.subject_request.received.v1 | platform | Participate — anonymize personal fields (retain financial record per law) |
4. Payload schemas
4.1 billing.charge.captured.v1
{
"chargeId": "chr_01J0...",
"accountId": "acc_01J0...",
"patientId": "pat_01J0...",
"encounterId": "enc_01J0...",
"facilityId": "fac_01J0...",
"serviceDate": "2026-04-10",
"code": { "system": "CPT", "code": "99213" },
"modifiers": [{ "system": "CPT-MOD", "code": "25" }],
"units": 1,
"unitPrice": { "currency": "AFN", "minor_units": 250000 },
"taxAmount": { "currency": "AFN", "minor_units": 0 },
"totalAmount": { "currency": "AFN", "minor_units": 250000 }
}
4.2 billing.invoice.issued.v1
{
"invoiceId": "inv_01J0...",
"accountId": "acc_01J0...",
"patientId": "pat_01J0...",
"facilityId": "fac_01J0...",
"issuedAt": "2026-04-17T10:00:00Z",
"currency": "AFN",
"subtotal": { "currency": "AFN", "minor_units": 250000 },
"tax": { "currency": "AFN", "minor_units": 12500 },
"total": { "currency": "AFN", "minor_units": 262500 },
"lineCount": 3
}
4.3 billing.payment.posted.v1
{
"paymentId": "pay_01J0...",
"accountId": "acc_01J0...",
"method": "CASH",
"amount": { "currency": "AFN", "minor_units": 262500 },
"reference": "RCPT-2026-00042",
"allocations": [
{ "invoiceId": "inv_01J0...", "amount": { "currency": "AFN", "minor_units": 262500 } }
],
"postedAt": "2026-04-17T10:02:00Z"
}
4.4 billing.adjustment.applied.v1
{
"adjustmentId": "adj_01J0...",
"accountId": "acc_01J0...",
"amount": { "currency": "AFN", "minor_units": -50000 },
"reason": "CONTRACTUAL",
"relatedInvoiceId": "inv_01J0...",
"claimId": "clm_01J0..."
}
4.5 billing.refund.issued.v1
{
"refundId": "rfd_01J0...",
"originalPaymentId": "pay_01J0...",
"accountId": "acc_01J0...",
"amount": { "currency": "AFN", "minor_units": 100000 },
"reason": "SERVICE_NOT_RENDERED",
"approvedBy": "usr_01J0...",
"postedAt": "2026-04-17T11:00:00Z"
}
4.6 billing.statement.generated.v1
{
"runId": "srun_01J0...",
"facilityId": "fac_01J0...",
"asOfDate": "2026-04-30",
"generated": 482,
"failed": 1,
"completedAt": "2026-05-01T03:14:00Z"
}
4.7 billing.account.suspended.v1
{
"accountId": "acc_01J0...",
"patientId": "pat_01J0...",
"reason": "NON_PAYMENT_90D",
"suspendedAt": "2026-07-02T09:00:00Z"
}
5. NATS JetStream configuration
| Stream | Subjects | Replicas | Retention | Max age | Storage |
|---|---|---|---|---|---|
BILLING | billing.* | 3 | Limits | 30 days operational / mirrored to archive for 10 years | File |
BILLING_DLQ | billing.dlq.> | 3 | Limits | 90 days | File |
6. Delivery semantics
- At-least-once delivery; consumers must idempotently dedup on CloudEvents
id. - Ordering is per
subject-partition; within an account, events are processed in order viasubject-level consumer ack. - Dead letter: 5 redelivery attempts →
billing.dlq.<subject>.
7. Schema registry
All schemas are registered in the platform schema registry under ghasi.billing.*. Consumer schema-conformance tests live in test/contract/*.schema.spec.ts.