Agent Wranglers

Amounts and orders Required

Hold money as integer minor units everywhere

Hold every amount as an integer count of the currency's minor unit, with its ISO 4217 code, in code, database, provider calls and stored events.

How to do it

The API rule money sets the wire format, and exact types sets the column type. Inside the code, use one money type that carries amount and currency, as in value objects. Refuse to add amounts in two currencies. Look up each currency's number of decimals, since JPY has none and KWD has three. Some providers differ from ISO 4217 for a few currencies, so convert through one tested function. Format for display only at the edge, in the reader's locale.

Example

class Money {
  constructor(minor, currency) {
    if (!Number.isSafeInteger(minor)) throw new TypeError('Amount must be a whole number of minor units')
    Object.assign(this, { minor, currency })
    Object.freeze(this)
  }
  plus(other) {
    if (other.currency !== this.currency) throw new Error(`Cannot add ${other.currency} to ${this.currency}`)
    return new Money(this.minor + other.minor, this.currency)
  }
}
from dataclasses import dataclass

@dataclass(frozen=True)
class Money:
    minor: int
    currency: str

    def __add__(self, other: "Money") -> "Money":
        if other.currency != self.currency:
            raise ValueError(f"Cannot add {other.currency} to {self.currency}")
        return Money(self.minor + other.minor, self.currency)

How to check

A unit test adds SEK to EUR and expects an error. Search the payment code for parseFloat, float( and Number( applied to amounts. Nothing is found.

For agents: if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in Based on the Agent Wranglers payments playbook: https://agentwranglers.se/playbook/payments/amounts-and-orders/money-in-minor-units.