Fee and Billing Systems
Calculating, charging, and recognizing fees and revenue accurately and idempotently.
Learning outcomes
Every business that takes money has a billing system, even when nobody calls it that. The moment you charge a customer, you have implicitly built one: something decided how much to charge, when, for what, and what to do when the charge fails. Most of the pain in fintech revenue comes from treating that something as an afterthought, a few lines bolted onto a checkout flow, instead of as a system with its own correctness rules that are every bit as strict as a ledger. Get billing right and your revenue numbers are trustworthy, your auditors are calm, and your customers are not surprised. Get it wrong and you leak money you earned, charge money you did not, and discover both only when someone complains or a regulator asks.
After studying this page, you can:
- Explain the difference between a price, a charge, a recognized dollar of revenue, and a collected dollar of cash, and say why a single number can never stand in for all four.
- Name the major fee types in financial services (per-transaction, assets under management, spread and markup, interchange, subscription, interest, and financing) and describe what real event each one meters.
- Trace a charge through the full billing lifecycle: meter the usage, rate it into an amount, accrue it, invoice it, collect it, and dun it when collection fails.
- Distinguish accrual from cash treatment and explain why finance and engineering must agree on when a fee is earned, not just when it is paid.
- Apply the five-step ASC 606 revenue recognition model at a working level to a real billing scenario, including how to handle a subscription paid in advance.
- Solve the rounding and allocation problem so that every cent of a prorated or split charge reconciles, with no penny created or destroyed.
- Use idempotency keys to make charging safe under retries, and design reconciliation that proves charged equals collected.
- Identify the classic failure modes (double billing, revenue leakage, and mis-rating) and the controls that catch each, and explain why fee transparency is a regulatory concern and not just a courtesy.
Before we dive in
You do not need an accounting or billing background to start. We will use a small set of words and define each one the first time it appears.
A fee is a price you charge a customer for something you did or provided. Billing is the whole machinery that turns events into fees, fees into invoices, and invoices into collected cash. A charge is a single instance of applying a fee to a customer. A price or rate is the rule that says how much a charge is, for example two percent of a transaction or nine dollars a month. Metering (also called usage capture) is recording the billable events as they happen, for example each transaction processed or each gigabyte stored. Rating is taking a metered event and computing the money amount it costs, by applying the rate. An invoice is a document that totals what a customer owes for a period and asks them to pay. Collection is actually getting the money. Dunning is the structured sequence of retries and reminders you run when a collection fails.
Two accounting words will recur. Accrual means recognizing a fee as earned when you perform the service, regardless of when cash moves. Recognition is the formal act of recording revenue on the income statement, governed by accounting standards rather than by when you happened to get paid. We will keep money in dollars for readability, but a real system stores money as an integer count of minor units (cents for dollars), never as a decimal float, for exactly the reasons a ledger does. Hold that picture as we go: an event happens, a rule turns it into an amount, the amount becomes a claim on the customer, and the claim is recognized, collected, and reconciled.
Mental Model
The wrong model, and almost every first billing system is built on it, is that billing is a single number per customer that goes up when they owe you and down when they pay. In this picture, “the amount the customer owes” and “the revenue we made” and “the cash we collected” are the same thing, tracked in one place, and charging is just adding to that number at checkout.
That model quietly fuses four things that are genuinely different and that move at different times, and the fusion is the root of most billing bugs. Pull them apart. The price is a rule that exists before any customer is charged. The charge is what the customer owes once an event triggers the rule, and it lives as a receivable until they pay. The recognized revenue is the slice of that charge that accounting rules say you have actually earned in a given period, which can be earlier or later than the charge and earlier or later than the cash. The collected cash is the money that actually arrived in your bank. A customer can owe you money you have not yet earned (an annual plan paid upfront), you can have earned money you have not collected (a fee accrued at month end, invoiced next month), and you can have collected money you must give back (a refund or a disputed charge). Four clocks, four numbers, four sources of truth.
Here is the model to hold instead. Think of billing as a pipeline of distinct stages, each with its own record, where money is conserved as it moves and nothing is allowed to vanish between stages. An event enters, a rate turns it into an amount, the amount is accrued as something earned, invoiced as something owed, collected as cash, and finally reconciled so that what you charged, what you recognized, and what you collected all agree to the cent. Every stage can be inspected on its own, and the gaps between stages (earned but not collected, collected but not earned) are not bugs, they are real positions that finance needs to see. Once you stop collapsing the four numbers into one, most of billing stops looking like special cases and starts looking like one disciplined flow.
Breaking it down
The core teaching runs in eleven steps. The first two establish why billing is hard and what you are actually charging for. The middle steps build the machine (metering, rating, the lifecycle, accrual, and recognition). The last steps cover the correctness problems that separate a toy from a production billing system: rounding, idempotency, reconciliation, failure modes, and scale.
1. Why charging is harder than it looks
Charging a customer once is trivial. Charging millions of customers, for many overlapping things, across plan changes, refunds, retries, currencies, and accounting periods, while every cent reconciles and the revenue number is defensible to an auditor, is one of the harder problems in fintech. The difficulty is not the arithmetic of any single fee. It is that billing sits at the junction of four worlds that each impose their own non-negotiable rules.
The customer world demands that charges be correct, predictable, and explainable. A surprise fee, a double charge, or a number the customer cannot reconstruct from their own activity destroys trust faster than almost anything else a fintech can do. The finance world demands that revenue be recognized in the right period under the right standard, that accruals be accurate, and that the billing system feed clean numbers into the general ledger. The regulatory world demands that fees be disclosed, fair, and not deceptive, and increasingly that certain fees be capped or banned outright. The engineering world demands that all of this be computed at scale, exactly once, under failures and retries, without ever losing or inventing a cent.
These worlds pull against each other. Finance wants to recognize revenue smoothly over a year; the customer paid all of it on day one. Product wants to offer a flexible mid-cycle upgrade; that forces proration, which forces the rounding problem. Engineering wants to retry a failed charge; that risks a double charge unless idempotency is designed in. A billing system is the place where those tensions are resolved concretely, in code, on real money. That is why it deserves the same respect as a ledger and not less.
2. The fee zoo and what each one really meters
Financial services run on a surprisingly small set of fee shapes, and the first skill is to see what underlying event each one meters, because the meter determines everything downstream: what you must capture, how you rate it, and how you recognize it.
Notice the pattern hiding under the variety. Every fee meters one of three things: an event (per-transaction, interchange, origination), a balance over time (assets under management, interest), or time itself (subscription). What you meter dictates how you capture it, how you rate it, and crucially when you recognize the revenue. An event fee is earned at the event; a balance fee accrues as the balance sits; a time fee is earned as time passes. Confuse these and you will recognize revenue in the wrong period, which is the single thing auditors care about most.
3. Metering and the rating engine
Before you can charge for anything, you must capture that it happened. Metering is the discipline of recording billable events completely and exactly once, and it is where revenue leakage most often begins. If a transaction occurs but no meter records it, you will never bill it, and that unbilled event is pure lost revenue that no downstream system can recover because it does not know the event existed. The meter is the headwaters; if it leaks, everything below it is short.
A good meter has three properties. It is complete (every billable event is captured, with no silent drops), it is exactly once (the same event is not counted twice, or you will overbill), and it is attributable (each event carries the customer, the product, the amount, and the timestamp needed to rate it correctly). In practice meters are usually built as an append-only event stream, the same shape as a ledger’s postings, because the same guarantees matter: you want a complete, immutable record you can replay and audit, not a running counter you mutate in place and can never reconstruct.
Once events are metered, the rating engine turns each event into a money amount by applying the price. Rating sounds simple (multiply amount by rate) and is not, because real pricing has tiers, volume discounts, minimums, caps, free allowances, promotional rates, and per-customer negotiated overrides. A rating engine is essentially a pure function from a metered event plus the customer’s contract to an amount, and the discipline that keeps it correct is that it must be deterministic and replayable: given the same event and the same contract, it must always produce the same amount, so a charge can be recomputed and explained months later when a customer or auditor asks how a number was reached.
flowchart LR EV["Billable event<br/>(transaction, balance,<br/>or elapsed time)"] --> M["Meter<br/>(append-only usage<br/>record, exactly once)"] M --> R["Rating engine<br/>(apply contract:<br/>rate, tiers, caps)"] R --> A["Rated amount<br/>(integer minor units,<br/>with currency)"] A --> ACC["Accrue<br/>(record as earned)"]
The same metered event rates to a different amount depending on the contract. Drag the usage and the per-unit rate, then switch the pricing structure to watch the rating engine recompute the charge live: flat pricing, a volume tier that halves the rate past the first ten thousand units, and a hard cap.
The reason to keep metering and rating as distinct stages, rather than computing the charge inline when the event happens, is that prices change and contracts get corrected. If you rated at capture time and stored only the final amount, you could never re-rate after fixing a misconfigured price. By keeping the raw metered events and re-rating them through a deterministic engine, you can correct a pricing bug by re-running rating over the affected events, instead of hunting down and patching thousands of already-computed charges. Separation of metering from rating is what makes a billing system correctable rather than a one-way street.
4. The billing lifecycle from rate to dunning
A charge does not leap from event to cash. It moves through a lifecycle, and each stage produces a record the next stage depends on. The five stages are rate, accrue, invoice, collect, and dun.
Each stage exists because the next one needs what it produces, just like a ledger’s journal, ledger, and trial balance. Rating produces the amount accrual needs. Accrual produces the earned position finance needs and feeds the invoice. The invoice produces the obligation collection acts on. Collection produces the cash, and when it fails, dunning is the structured recovery that decides how hard and how long you chase the money before giving up. The animation below shows the whole lifecycle as one fixed map so you can see how a charge and a failed collection both flow through the same structure.
A subtle point that trips teams: the lifecycle is not strictly linear in time. Accrual can run continuously (interest accrues every day), while invoicing is periodic (monthly), and recognition follows its own schedule independent of both. The stages are a logical pipeline, not a single clock. Keeping them as separate records with their own timestamps is what lets daily accrual, monthly invoicing, and period-based recognition coexist without contradicting each other.
5. Accrual versus cash and why finance cares
There are two fundamentally different ways to answer the question “when did we make this money,” and a billing engineer must understand both because the system has to serve both at once. Under cash accounting, revenue is recorded when cash arrives and expenses when cash leaves. It is simple and it is what a personal checkbook does. Under accrual accounting, revenue is recorded when it is earned and expenses when they are incurred, regardless of when cash moves. Accrual is what essentially every business of consequence uses, because it is required under both US GAAP and IFRS for reporting, and because it is the only way to see the true economic shape of a period.
The gap between the two is the whole reason billing is more than a payment gateway. Consider an annual subscription of one hundred twenty dollars paid upfront in January. Under cash accounting you made one hundred twenty dollars in January and zero for the rest of the year, which is nonsense: you have not yet delivered eleven months of service. Under accrual accounting you earned ten dollars in January and will earn ten dollars in each following month as you deliver. The other hundred and ten dollars you collected in January is not yet revenue; it is deferred revenue, a liability, because you owe the customer eleven more months of service and would have to refund them if you stopped. The cash hit your bank in January, but the revenue is spread across the year.
This is why finance and engineering must agree, explicitly, on the accrual schedule for every fee. Engineering controls the events and the amounts; finance controls when those amounts become recognized revenue. The billing system must produce both the cash facts (what was collected, when) and the accrual facts (what was earned, in which period, and what remains deferred), and it must feed the latter into the general ledger as journal entries. A billing system that only knows about cash is not a billing system; it is a payment collector, and finance will end up rebuilding the accrual layer in spreadsheets, which is exactly the fragile manual process a good billing system exists to eliminate.
6. Revenue recognition under ASC 606 at a working level
Accrual tells you revenue is recognized when earned, but it does not tell you precisely when a fee is earned. That is the job of revenue recognition standards. In US GAAP the governing standard is ASC 606 (its IFRS twin is IFRS 15); the two were issued jointly and are substantially aligned. You do not need to be an accountant to build billing, but you must understand the model at a working level, because the billing system is what produces the data the standard is applied to, and getting the recognition wrong is a restatement-grade error.
ASC 606 prescribes a five-step model for recognizing revenue from contracts with customers. Walk it once, concretely, against a real fintech contract: a customer signs up for a platform at nine dollars a month plus a usage fee of one percent of payment volume processed.
The fifth step carries the engineering weight, because it splits fees into two recognition patterns that the billing system must track separately. A fee for a service delivered over time (a subscription, access, custody) is recognized ratably across that time, which is the deferred-revenue case from the previous section: collect upfront, recognize day by day. A fee for a service delivered at a point in time (a transaction processed, a wire sent) is recognized at that instant. Your system must tag every fee with which pattern it follows and produce the recognition schedule accordingly, because the auditor will test exactly this: did revenue land in the period the service was delivered, and not before.
stateDiagram-v2
[*] --> Booked: charge created
Booked --> Deferred: service delivered over time
Booked --> Recognized: service delivered at a point in time
Deferred --> Recognized: each period, draw down as delivered
Recognized --> [*]
note right of Deferred
Deferred revenue is a liability.
It becomes recognized revenue only
as the obligation is satisfied.
end noteOne more working-level point that engineers underestimate: variable consideration (step three) means you cannot always recognize the full contracted amount immediately, because some of it might reverse (refunds, disputes, usage that does not materialize). ASC 606 says you constrain variable consideration to the amount highly likely not to reverse. In practice this means the billing system must model expected refunds and reversals, and recognition must hold back the portion that could come back. A system that recognizes every charge in full the instant it is made, ignoring expected reversals, will systematically overstate revenue and then claw it back later, which is precisely the jagged, untrustworthy revenue line that recognition standards exist to prevent.
7. Proration rounding and the allocation problem
The moment you let customers change plans mid-cycle, upgrade, downgrade, add seats, or cancel partway, you inherit the proration problem, and proration drags in the rounding and allocation problem, which is where billing systems quietly leak or invent pennies. The rule is absolute and it is the same one a ledger lives by: every cent must reconcile. An allocation must be exhaustive, meaning the parts sum exactly to the whole, never one cent more and never one cent less.
Proration is charging for a partial period. A customer on a thirty dollar monthly plan who upgrades to a sixty dollar plan halfway through the month owes roughly half a month of each. The arithmetic is a fraction of a fee, and fractions of cents do not exist in the ledger. Thirty dollars prorated over, say, fourteen of thirty days is fourteen thirtieths of three thousand cents, which is one thousand four hundred cents exactly here but is rarely so clean. Prorate a fee that does not divide evenly and you get a fractional cent that you must round, and the instant you round, the rounded parts may no longer sum to the original. That mismatch is the leak.
The canonical version is splitting an amount across several parties or periods. Split a one thousand cent fee three ways and each share is three hundred thirty-three and one third cents. You cannot charge a third of a cent, so each rounds to three hundred thirty-three, and three times three hundred thirty-three is nine hundred ninety-nine, leaving one orphan cent. If you simply round each share independently, you have destroyed a cent and your invoice no longer reconciles to the charge. The fix is to allocate the whole, not the parts: compute each share, then deterministically assign the leftover units (the largest-remainder method is the common, fair choice, giving the residual cents to the shares with the largest fractional remainders) so the parts always sum back to the original to the cent.
// Allocate a total across n shares so the parts sum EXACTLY to the total.
// Largest-remainder method: floor each share, then hand the leftover
// minor units to the shares with the biggest fractional remainders.
func Allocate(total int64, weights []int64) []int64 {
var sumW int64
for _, w := range weights {
sumW += w
}
shares := make([]int64, len(weights))
remainders := make([]int64, len(weights))
var distributed int64
for i, w := range weights {
// total*w/sumW floored; remainder is total*w mod sumW
shares[i] = total * w / sumW
remainders[i] = total * w % sumW
distributed += shares[i]
}
leftover := total - distributed // always >= 0 and < len(weights)
// Give one extra unit to the largest remainders, deterministically.
for ; leftover > 0; leftover-- {
best := 0
for i := range remainders {
if remainders[i] > remainders[best] {
best = i
}
}
shares[best]++
remainders[best] = -1 // do not pick the same one twice
}
return shares // sum(shares) == total, guaranteed
}
The principle generalizes beyond proration. Any time billing divides money (splitting a fee across cost centers, allocating a discount across line items, distributing a refund across original charges), it must allocate exhaustively so the pieces reconcile to the whole. Try the division yourself and watch the orphan cent appear and then be placed.
The verdict the slider should leave you with is intuitive once you see it: ten dollars is one thousand cents, and one thousand divides evenly only by a few of those splits (one, two, four, five, eight). Split it three, six, seven, or nine ways and there is a remainder that must be assigned to specific shares, or it vanishes. A billing system that does not handle this explicitly does not “usually” leak a cent; it leaks a cent on exactly the inputs that do not divide evenly, which over millions of invoices is real money and a trial balance that will not close.
8. Idempotency and the war against double billing
Of every way a billing system can betray a customer, charging them twice for one thing is the most damaging, because it is visible, it is infuriating, and it converts a paying customer into a complaint and often a chargeback. And it is alarmingly easy to do, because the systems that move money are distributed and distributed systems retry. A charge request is sent, the network drops the response, the client cannot tell whether the charge succeeded, so it retries. Without protection, the retry creates a second charge, and the customer pays twice for one purchase.
The defense is idempotency: applying the same operation twice has the same effect as applying it once. The mechanism is an idempotency key, a unique identifier the client attaches to the charge request. The billing system records which keys it has already processed; when a request arrives with a key it has seen, it returns the original result instead of charging again. The retry collapses onto the first attempt, and the duplicate charge never exists. This is the same discipline a ledger uses to prevent duplicate postings, applied at the billing boundary.
Idempotency is necessary but not sufficient, and the boundary matters. The key protects against the same logical request being delivered twice, but it cannot protect against two genuinely different requests that happen to represent the same intent, nor against a bug that generates a fresh key for a retry instead of reusing the original. So idempotency keys are paired with two more controls. First, charges are written immutably, like ledger postings: you never edit a charge, and a correction is a new reversing entry (a refund or credit note), so the full history of what was charged and reversed is preserved and auditable. Second, reconciliation runs after the fact to catch any duplicate that slipped through, by comparing charged amounts against collected amounts and against the source events. Idempotency stops most double charges at the door; reconciliation is the net that catches the ones that get past it.
-- Idempotent charge: the key makes a retry a no-op that returns the original.
INSERT INTO charges (idempotency_key, customer_id, amount_minor, currency, created_at)
VALUES ($1, $2, $3, $4, now())
ON CONFLICT (idempotency_key) DO NOTHING
RETURNING id, amount_minor, status;
-- If the row already existed, the INSERT does nothing and the caller
-- reads back the original charge by the same key. One real charge, ever.
9. Reconciling charged against collected
A billing system makes two distinct claims about money, and they are not automatically the same. Charged is what you told customers they owe: the sum of issued invoices and charges. Collected is the cash that actually arrived. The discipline of proving these agree, and explaining every penny where they do not, is reconciliation, and it is the control that turns a billing system from something you hope is right into something you can prove is right.
The gap between charged and collected is not a bug; it is a set of real, expected positions, and the job is to account for each one rather than to make the gap zero. Some charges are issued but not yet due. Some are due but failed collection and are in dunning. Some were collected and then refunded. Some were disputed and clawed back by the card network as a chargeback. A correct reconciliation does not assert charged equals collected; it asserts that charged equals collected plus outstanding receivables plus failed-and-dunning plus refunded plus disputed, with every bucket explained and every individual item traceable. When that identity holds, you can trust the revenue number. When it does not, the residual is exactly the money you have lost track of, and chasing it is how you find leakage and duplicates.
That last bucket is the trap, and it is worth dwelling on. A naive reconciliation compares charged against collected and feels safe when they tie out. But an event that was never metered or never rated is absent from both charged and collected, so the two sides still balance while you quietly bleed revenue. This is the exact analog of a ledger that balances internally while disagreeing with the outside world. The only defense is a three-way reconciliation that ties the metered source events to the charges to the collected cash, so an event that produced no charge surfaces as a discrepancy against the meter, not against the bank.
10. Failure modes leakage and fairness
A billing system fails in a small number of characteristic ways, and a senior engineer should be able to name each one, say what causes it, and say which control catches it. Knowing precisely which failures each control does and does not catch is the difference between trusting your billing blindly and building the right defenses around it.
Above the mechanics sits a constraint that is not an engineering choice at all: fee transparency and fairness are regulatory concerns. Regulators across jurisdictions require that fees be clearly disclosed before a customer incurs them, that they not be deceptive or hidden, and increasingly that certain fees be capped or banned. Consumer protection authorities have specifically targeted what they call junk fees: surprise charges, fees disproportionate to any cost, and fees designed to be hard to avoid. Late fees on credit and certain overdraft and interchange fees sit under active rule-making and caps. The practical consequence for a billing system is that it must be able to prove what a customer was told and when, reproduce exactly how any charge was computed, apply caps and disclosures as first-class rules rather than afterthoughts, and present fees to customers in plain, reconstructable terms. A billing system that cannot explain a charge is not just a support headache; in a regulated product it is a compliance exposure. Transparency is not a feature you add for goodwill; it is a property the system must be architected to guarantee.
11. Scaling a billing system and who watches it
For a startup, the entire billing system can be a few tables and a nightly job: an events table, a prices table, a charges table, an invoices table, and a cron that meters, rates, invoices, and collects. With one database and modest volume, exactness is easy because a single transaction gives it to you, and many companies run a remarkably long way on exactly this. Do not build a distributed billing platform before you must; the simple version is correct and auditable, which matters more than clever.
As volume grows, the same pressures that reshape a ledger reshape billing, and the response is the same: keep the invariants non-negotiable while distributing the work. Metering becomes a high-volume append-only stream because event rates outpace synchronous writes. Rating becomes a separately scaled service because pricing logic is the hot path on every event. Invoicing and recognition become periodic batch jobs that must be idempotent, because a job that reruns after a crash must not double-invoice or double-recognize. And reconciliation graduates from a spreadsheet to an automated control that runs continuously and alerts the moment charged, collected, and metered stop tying out.
flowchart TB
subgraph one["Stage 1: one billing database"]
A["Events, prices, charges, invoices<br/>in one strongly consistent DB,<br/>a nightly meter-rate-invoice-collect job"]
end
subgraph two["Stage 2: split the hot paths"]
B["High-volume metering stream<br/>(append-only, exactly once)"]
C["Rating service scaled on its own<br/>(deterministic, replayable)"]
end
subgraph three["Stage 3: controls as first-class systems"]
D["Idempotent batch invoicing and recognition,<br/>continuous automated reconciliation,<br/>a feed into the general ledger"]
end
one --> two --> threeThe people around the system matter as much as the architecture, because billing serves several participants who each need something different from it, and a design that ignores any one of them fails in that participant’s terms. Hold all four perspectives at once.
The constant across every stage and every participant is the one you should carry away: billing, like a ledger, earns trust by keeping immutable records, deriving its numbers from those records rather than mutating balances in place, and reconciling continuously against the outside world. You can scale the volume, split the services, and batch the jobs, but the moment a charge can be created without a source event, recognized in the wrong period, or collected without reconciling, you no longer have a billing system you can defend. Exactness is not a luxury you add later; it is the product.
Mastery Questions
-
A founder says “our billing is simple, we just charge the card at checkout and that is our revenue.” A new finance hire is alarmed. Who is right, and what specifically is the founder conflating?
Answer. The finance hire is right to be alarmed, because the founder has collapsed four distinct things into one: the price, the charge, the recognized revenue, and the collected cash. Charging the card produces collected cash, but that is not automatically revenue. If any of those charges are for service delivered over time (a subscription, access, custody), then under accrual accounting and ASC 606 the revenue is earned as the service is delivered, not when the card is charged. Cash collected for service not yet delivered is deferred revenue, a liability, not revenue. Treating cash collected as revenue overstates revenue in the collection period and leaves nothing for the periods when the service is actually delivered, producing a jagged, indefensible revenue line that will not survive an audit. The founder’s model also has no place for accruals (revenue earned but not yet collected), refunds and chargebacks (cash that must be returned or recognized as reversing), or reconciliation (proving the numbers tie out). “Charge the card” is the collection stage of a billing system, not the whole of it, and a real system must separately track what was priced, charged, recognized, and collected.
-
Your billing system splits a customer’s monthly platform fee across three internal cost centers for accounting, and finance reports that the cost-center totals do not add up to the invoice total, off by a cent or two on some months but not others. What is happening, why only some months, and how do you fix it?
Answer. This is the rounding and allocation problem. The fee is being split into three shares, each rounded independently to whole cents, and three rounded shares do not always sum back to the original. On months where the fee divides evenly by three the shares sum correctly and nothing is off; on months where it does not, the independent rounding drops or adds an orphan cent, which is why it appears only on some months and not others (exactly the inputs that do not divide evenly). The fix is to allocate the whole rather than round the parts: compute each share, floor it, then deterministically assign the leftover cents (the largest-remainder method gives them to the shares with the biggest fractional remainders) so the shares always sum exactly to the original amount. The rule is that an allocation must be exhaustive, the parts summing to the whole to the cent, every time, not usually. Until the system allocates this way, the invoice will periodically fail to reconcile to its own line items, and a trial balance built from those lines will not close.
-
A payment service calls your billing API to charge fifty dollars, the call times out, and the service retries. Separately, your monthly reconciliation shows charged and collected tie out exactly, and the team concludes billing is healthy. Critique both situations: what protects against the double charge, and why is the clean reconciliation not proof that billing is correct?
Answer. On the retry, what protects against a double charge is an idempotency key: the client attaches a unique key to the charge request, the billing system records which keys it has processed, and the retry carrying the same key returns the original charge instead of creating a second one, so the duplicate never exists. Without that key, the retry produces a second internally valid charge and the customer pays one hundred dollars for a fifty dollar purchase; nothing in the system objects, because each charge is well-formed. On the reconciliation, a clean charged-versus-collected tie is not proof of correctness because it is blind to the most dangerous failure: revenue leakage. An event that was never metered or never rated produces no charge at all, so it is absent from both the charged side and the collected side, and the two still balance while real revenue is lost. This is the same trap as a ledger that balances internally while disagreeing with reality. The only defense is a three-way reconciliation that ties the metered source events to the charges to the collected cash, so an event that produced no charge surfaces as a discrepancy against the meter. Charged equals collected proves the two recorded sides agree; it cannot prove that everything that should have been charged was.
Sources & evidence14 claims · 6 cited
Grounded in ASC 606 / IFRS 15 revenue recognition, standard accrual-vs-cash accounting, and well-established billing-systems engineering practice (metering, deterministic rating, idempotency, exhaustive allocation, three-way reconciliation). Regulatory fairness claims reflect general consumer-protection and fee-disclosure principles; specific current fee caps (interchange, late fees) are referenced at a directional level and would shift with active rule-making, which is the main gap.
- ASC 606 prescribes a five-step model for recognizing revenue from contracts with customers: identify the contract, identify performance obligations, determine the transaction price, allocate the price to obligations, and recognize revenue as obligations are satisfied.verified
- ASC 606 and IFRS 15 were issued jointly by the FASB and IASB and are substantially converged.verified
- ASC 606 requires variable consideration to be constrained to the amount that is highly probable not to result in a significant revenue reversal.verified
- Revenue is recognized either over time (ratably, as for a subscription) or at a point in time (as for a discrete transaction), depending on how the performance obligation is satisfied.verified
- Under accrual accounting, revenue is recorded when earned and expenses when incurred, regardless of when cash moves; accrual accounting is required for financial reporting under US GAAP and IFRS.stable common knowledge
- Cash collected for service not yet delivered is recorded as deferred revenue, a liability, and recognized as the service is delivered.verified
- Money should be stored as an integer count of the currency's minor units rather than a floating-point number, because binary floats cannot represent most decimal fractions exactly.stable common knowledge
- An idempotency key attached to a charge request lets a billing system return the original result on a retry instead of charging again, preventing duplicate charges from retried requests.stable common knowledge
- When an amount does not divide evenly across shares, independently rounding each share can fail to sum back to the total; exhaustive allocation (for example the largest-remainder method) assigns leftover minor units deterministically so the parts sum exactly to the whole.internal reasoning
- Interchange is a fee set by card networks that flows from the merchant's acquirer to the cardholder's issuer on card purchases, and varies by card type, merchant category, and region.stable common knowledge
- Reconciliation that proves charged equals collected cannot detect revenue leakage, because an event that was never charged is absent from both the charged and collected sides; only a three-way reconciliation back to metered source events surfaces it.internal reasoning
- Fee transparency and fairness are regulatory concerns: regulators require fees to be clearly disclosed before they are incurred, prohibit deceptive or hidden fees, and have targeted surprise junk fees, with late fees and certain overdraft and interchange fees under active rule-making and caps.stale risk
- A rating engine should be deterministic and replayable so charges can be recomputed and explained later, and so a pricing error can be corrected by re-rating the raw metered events rather than patching individual computed charges.internal reasoning
- Charges should be written immutably like ledger postings, with corrections recorded as new reversing entries (refunds or credit notes) rather than edits, to preserve an auditable history.internal reasoning
Cited sources
- ASC 606, Revenue from Contracts with Customers · Financial Accounting Standards Board (FASB)
- IFRS 15, Revenue from Contracts with Customers · International Accounting Standards Board (IASB)
- What Every Computer Scientist Should Know About Floating-Point Arithmetic · David Goldberg / ACM
- Idempotent Requests in payment APIs · Stripe
- Interchange fees in card payment systems · Visa and Mastercard
- Consumer protection on fees and disclosures (junk fees, overdraft and late fees) · Consumer Financial Protection Bureau (CFPB)