Money is exact, never floating
Every amount in Disbursement is an integer. There are no floats anywhere in the config or the math, because the output is a file a bank executes — a rounding error creates or destroys real money.- Percentages are basis points (bps). 1% = 100 bps, so 100.00% = 10000 bps.
A 90/10 split is
9000/1000. - Amounts are minor units. Values are stored and computed in the currency’s minor unit (x100 convention). The exporter owns display formatting per the bank spec; storage never changes.
- Round half up for MDR, service charge, and tax.
The split algorithm
For a gross amountG and a resolved config, the engine computes:
1
Fees
mdr = rhu(G × mdr_bps / 10000), then the service charge (a percentage of
gross or a fixed minor-unit amount). fee = mdr + service_charge.2
Tax
Tax is configurable: a
base (fees or gross) × mode (include or exclude) ×
payer (merchant or klikit). exclude adds tax on top; include backs it
out so net_of_tax + tax == base.3
Distributable pool
Deduct the merchant-borne fee and tax from gross to get the pool
P.
klikit-borne portions are reported separately as klikit_absorbed_* and
never touch the merchant pool. A negative pool (fees exceed gross) is
flagged, never clamped — the batch can’t be approved until it’s resolved.4
Largest-remainder split
Each party gets
floor(P × bps / 10000); the leftover minor units are handed
out one at a time by descending fractional remainder. This guarantees the
shares sum to P exactly — independent per-line rounding is forbidden.Worked example (IDR). Gross Rp 100,000.00 (
10,000,000 minor units), MDR
150 bps, fixed service charge 250,000, 11% tax excluded on fees, merchant
bears both. Fee = 400,000, tax = 44,000, pool = 9,556,000. A 9000/1000
split → 8,600,400 + 955,600 = 9,556,000 exactly.Config validation
A template’s config is rejected unless:Σ split_bps == 10000(exactly 100%),- exactly one party has role
merchant, at most one has roleklikit, - no negative or non-integer values.
Line flags
After compute, each line may carry flags that block approval until resolved:Permissions
Three distinct permissions gate the workflow (approval is always separate from operation — maker-checker):Data safety
- Bank account numbers live only on the instance’s parties, encrypted at rest, masked (last-4) in ordinary reads, full access role-gated and audited.
- A batch’s config is frozen at compute — later template edits can never change a computed batch. The beneficiary bank details are snapshotted onto each line too, so a post-approval bank edit can’t silently redirect a payment.
- An exported batch can never be voided (money may have moved; the record is permanent).