Billing & Payments
The billing service manages the full financial lifecycle of a policy: generating invoices, collecting premiums, recording payments, maintaining a double-entry ledger, and handling grace periods and delinquency.
Invoice Lifecycle
| Status | Description |
|---|---|
DRAFT | Invoice being assembled; line items may still be added. |
FINALISED | Invoice locked. FinalisedAt timestamp set. Due date communicated to account holder. |
PAID | All line items settled. PaidAt timestamp set. |
VOID | Invoice cancelled; corresponding ledger entries reversed. |
Premium Billing Cycle
When enrollment.completed is consumed, the billing service creates the first premium Invoice for the new policy. Subsequent invoices are generated by a scheduled job that runs on the billing frequency defined in the InstallmentSchedule (monthly, quarterly, annually).
Each invoice references one or more InvoiceLineItem rows, each backed by a Charge record categorised as PREMIUM, TAX, or FEE. The total amount is denormalised on Invoice.TotalAmount for fast retrieval.
Payment Methods
Payments are recorded in the Payment struct. The Method field captures how the premium was collected:
| Method | Description |
|---|---|
DIRECT_DEBIT | Automated bank debit on the due date (most common for employer groups) |
CARD | Credit or debit card payment via payment gateway |
BANK_TRANSFER | Manual BACS/CHAPS transfer; reconciled by reference number |
A Payment is linked to exactly one Invoice. When Payment.Status = "SETTLED" and the total payments for an invoice equal Invoice.TotalAmount, the invoice moves to PAID.
Grace Period and Delinquency Handling
Every Invoice carries a GracePeriodDays field (default 30). If payment is not received by DueDate, the invoice becomes delinquent at DueDate + GracePeriodDays — recorded in Invoice.DelinquentAt.
The billing service publishes a billing.payment.missed Kafka event when an invoice becomes delinquent. The enrollment service consumes this event and transitions the policy to LAPSED. If the lapse is not resolved within the statutory window, the policy moves to CANCELLED.
DueDate → DueDate + GracePeriodDays = DelinquentAt → LapsedAt → CancelledAt
│ │ │
│ ▼ ▼
│ billing.payment.missed policy status: LAPSED
│ event published
▼
payment received → Invoice: PAID → policy remains ACTIVELedger Entries (Double-Entry)
Every financial movement is recorded as a LedgerEntry to provide a complete, auditable double-entry ledger for each account.
| Field | Notes |
|---|---|
EntryType | CHARGE, PAYMENT, ADJUSTMENT_DEBIT, ADJUSTMENT_CREDIT, or REVERSAL |
Direction | DEBIT (money owed) or CREDIT (money received or credited) |
ReferenceID | UUID of the source record (Charge, Payment, or Adjustment) |
ReferenceType | Type discriminator: CHARGE, PAYMENT, or ADJUSTMENT |
Amount | Always positive; direction carries the sign semantics |
Example — monthly premium:
- Premium charge generated →
LedgerEntry(CHARGE,DEBIT, £250.00) - Direct debit received →
LedgerEntry(PAYMENT,CREDIT, £250.00) - Account balance = £0.00
Example — mid-term cancellation credit:
Adjustmentcreated (CREDIT, £75.00 pro-rata refund) →LedgerEntry(ADJUSTMENT_CREDIT,CREDIT, £75.00)
Installment Schedules
For accounts that pay annually but want to spread the cost, an InstallmentSchedule splits the annual premium into equal invoices.
| Field | Notes |
|---|---|
TotalAmount | The full annual premium |
Frequency | MONTHLY, QUARTERLY, ANNUAL |
Installments | Number of invoices to generate (e.g., 12 for monthly) |
StartDate | Date of the first installment invoice |
Status | ACTIVE, COMPLETED, or CANCELLED |
The billing scheduler reads active InstallmentSchedule records and generates the next Invoice on each due date until all installments are exhausted.
Autopay
The AutopayPreference struct records whether a policy has automatic payment enabled. When Enabled = true, the billing service initiates the direct debit automatically on the invoice due date without requiring a manual payment action.
Key Go Struct Fields at a Glance
type Invoice struct {
ID uuid.UUID
Locator string // e.g. "INV-2026-004521"
AccountID uuid.UUID
Status string // DRAFT | FINALISED | PAID | VOID
TotalAmount decimal.Decimal
Currency string // ISO 4217, default "GBP"
DueDate *time.Time
GracePeriodDays int // default 30
DelinquentAt *time.Time // set when grace period expires unpaid
LapsedAt *time.Time // set when policy lapses due to this invoice
}
type Payment struct {
InvoiceID uuid.UUID
AccountID uuid.UUID
Amount decimal.Decimal
Method string // DIRECT_DEBIT | CARD | BANK_TRANSFER
Status string // SETTLED | VOID
SettledAt time.Time
}
type LedgerEntry struct {
EntryType string // CHARGE | PAYMENT | ADJUSTMENT_DEBIT | ADJUSTMENT_CREDIT | REVERSAL
Direction string // DEBIT | CREDIT
ReferenceID uuid.UUID
ReferenceType string
Amount decimal.Decimal
}