Skip to content

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

StatusDescription
DRAFTInvoice being assembled; line items may still be added.
FINALISEDInvoice locked. FinalisedAt timestamp set. Due date communicated to account holder.
PAIDAll line items settled. PaidAt timestamp set.
VOIDInvoice 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:

MethodDescription
DIRECT_DEBITAutomated bank debit on the due date (most common for employer groups)
CARDCredit or debit card payment via payment gateway
BANK_TRANSFERManual 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 ACTIVE

Ledger Entries (Double-Entry)

Every financial movement is recorded as a LedgerEntry to provide a complete, auditable double-entry ledger for each account.

FieldNotes
EntryTypeCHARGE, PAYMENT, ADJUSTMENT_DEBIT, ADJUSTMENT_CREDIT, or REVERSAL
DirectionDEBIT (money owed) or CREDIT (money received or credited)
ReferenceIDUUID of the source record (Charge, Payment, or Adjustment)
ReferenceTypeType discriminator: CHARGE, PAYMENT, or ADJUSTMENT
AmountAlways positive; direction carries the sign semantics

Example — monthly premium:

  1. Premium charge generated → LedgerEntry (CHARGE, DEBIT, £250.00)
  2. Direct debit received → LedgerEntry (PAYMENT, CREDIT, £250.00)
  3. Account balance = £0.00

Example — mid-term cancellation credit:

  1. Adjustment created (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.

FieldNotes
TotalAmountThe full annual premium
FrequencyMONTHLY, QUARTERLY, ANNUAL
InstallmentsNumber of invoices to generate (e.g., 12 for monthly)
StartDateDate of the first installment invoice
StatusACTIVE, 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

go
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
}

Olly Health Insurance Platform