4 June 2026
8 min read
Designing billing systems for complex service businesses
Effective-dated pricing, proration, and revenue integrity — engineering lessons from building a billing engine for the marine services sector.

Billing is the part of an operational platform that looks simplest in the proposal and proves hardest in production. Charges multiplied by rates, summed into invoices — until a customer upgrades mid-period, a price change lands mid-contract, a manager grants a discount retroactively, and the finance team asks why March's numbers moved after March closed.
We recently built a billing engine for a multi-tenant marina management platform — berth licenses with seasonal terms, mid-contract transfers, prorated adjustments, and managerial overrides, across multiple operators on one system. The domain is specific, but the engineering lessons generalize to any service business whose contracts change while they are in force. This article records the ones that shaped the architecture.
The real problem is time
The defining question in service billing is not "what does this cost?" but "what did this cost on the date the service was provided?" A price list is not a table of rates; it is a timeline of rates, each row effective from one date until superseded. The same is true of the contract terms, the tax rules, and the customer's own configuration.
This means every pricing entity in the schema carries effective dating from the start — valid-from and valid-to on rates, terms, and assignments — and every charge calculation resolves against the timeline as of the service date, never against "current" values. Retrofitting effective dates onto a schema that stored only current state is among the most expensive migrations we know of; designing for it up front costs almost nothing.
In practice you eventually need two timelines: when a fact was true, and when the system learned it. A rate correction entered in April but effective from February must recalculate February's charges — while preserving the record that February was originally billed differently. You need not implement full bitemporal storage on day one, but the ledger design below is what makes the distinction recoverable later.
Proration is a policy, not a formula
Every team assumes proration is arithmetic. It is actually a set of commercial decisions the business may never have written down: Is a month 30 days, the calendar month's actual length, or 1/12 of a year? Does the day of a mid-period transfer bill at the old rate or the new? When a vessel moves to a larger berth for four days and returns, is that two transfers or a temporary assignment with its own rate?
The engineering answer is to make the policy explicit and pluggable: proration strategies as named, versioned rules selected per contract type, not arithmetic inlined into the charge calculator. The discovery answer matters more — these questions must be put to the operations and finance teams before the first invoice run, because each choice produces defensibly different numbers, and finding out which one the business meant after invoices have gone out is a customer-relations problem, not a bug fix.
Overrides are domain events, not edits
In any real service business, managers override: a negotiated discount, a waived fee, a goodwill credit after a service failure. The naive implementation lets an authorized user edit the charge amount. The consequences surface months later — nobody can say why an invoice differs from the rate card, revenue reports disagree with the price list, and the auditor's questions have no answers.
Overrides should be first-class domain records: who, when, what the calculated amount was, what it became, and a required reason, with the original calculation preserved beneath. The invoice presents the net result; the ledger keeps both. This is what makes managerial flexibility compatible with revenue integrity — the business stays free to exercise judgment, and the system keeps an account of the judgment exercised.
An architecture that holds up
The structural decisions that carried the most weight in production:
- An append-only charge ledger as the source of truth. Charges are calculated, recorded immutably, and corrected by reversal and re-issue — never updated in place. Every account balance is a fold over the ledger.
- Invoices as projections, not records of origin. An invoice is a presentation of ledger entries over a period. Regenerating a document never risks changing an amount, and a mid-period change simply produces adjustment entries that appear on the next invoice.
- Idempotent, resumable billing runs. A run over thousands of contracts will be interrupted eventually. Charge generation is keyed so that re-running a period is safe by construction — the difference between a routine retry and a night of manual reconciliation.
- Calculation as a pure function. Contract terms, rate timeline, events, and period in; charge lines out; no I/O inside. This one property is what makes the testing regime below possible.
- Reconciliation as a feature, not a spreadsheet. Expected-versus-billed reports by contract and period, built into the platform. Revenue leakage in the legacy process was invisible precisely because nothing was positioned to notice it.
Testing revenue integrity
Billing defects are silent. Software that crashes gets fixed; software that under-bills by two percent runs quietly for years. The test suite has to be built for that failure mode, and the pure calculation core makes three layers practical.
Scenario tests encode the awkward cases as executable specifications — the mid-month transfer during a price change, the leap-year February, the contract that starts and ends inside one period — each with expected amounts a finance stakeholder has signed off. Property-based tests assert the invariants that must survive any input: prorated segments sum exactly to the whole-period charge, reversal plus re-issue nets to the correction, no sequence of events strands a day unbilled or bills it twice. And a golden dataset — a full period of realistic contracts with known-correct output — runs on every change, so any altered invoice amount, anywhere, must be explained before it ships.
None of this is exotic. It is the discipline of treating revenue calculation as what it is — the part of the system where correctness is a financial statement — applied with tools every senior team already has. Enterprise billing platforms earn their cost in domains that fit them; where the domain is too specific for one, as it was here, these are the decisions that let a custom engine deliver the same class of integrity.
To discuss how this applies to your organization, contact us.