# Money and payments

Use `Money` for an amount of money and `Currency` for its currency. Both
are standard types. An amount is held as a whole number of the
currency's minor units, such as pence or cents, together with its
`Currency`. It is never a decimal of pounds. Each currency's exponent,
the number of digits after its decimal point, comes from ISO 4217: `jpy`
0, `gbp` 2, `bhd` 3.

`Money` is a type built into the language, as `Duration` is, not an
object: an amount has no fields, and `money.minor` and `money.currency`
read its two parts. Like `Int`, it is a representation, so a signature
names a type built on it
([Signatures use named types](signatures-use-named-types.md)).
A type built on `Money` takes these settings:

- `currency:`, a `Currency` variant, fixes the currency;
- `rounding:`, a `Rounding` variant, says how `*` and `/` round to a
  minor unit;
- `min:` and `max:` are whole numbers of minor units.

An amount holds at most 2^53 - 1 minor units either side of zero,
JavaScript's largest safe integer, so the `amount` sent on the wire is
always a JSON number a client reads exactly. Making or working out an
amount past that is overflow, a defect, as it is for `+` on `Int`.

`Currency` is an enum of the active ISO 4217 codes, written
`Currency.gbp`, and each release ships the list. It is ISO 4217's list
less the codes with no minor unit (`N.A.`: the precious metals, the SDR,
the bond-market units, `XSU`, `XUA`, `XTS` and `XXX`): 164 codes, each
with its exponent. The codes ISO has withdrawn since 2021 (`ANG`, `BGN`,
`CUC`, `HRK`, `SLL`, `ZWL`) are retired variants: your code can read
them but not write them (`type.variant_retired`). Albania's lek is
`Currency.all_`, because `.all` lists an enum's variants; its code is
still `ALL`.

A `Currency` is spelt by its code, `GBP`, on the wire, in settings, in
OpenAPI and in its column, as every standard enum is, so the database
reads as the API does. `text.format` gives its CLDR display name, such as
"Japanese Yen".

A bakery prices its cakes in pounds, takes orders from abroad in the
customer's currency, and shares a bill between friends:

```jadpo
type CakePrice = Money {
    currency: Currency.gbp  // fixed: adding another currency doesn't compile
    min: 0  // in minor units: pence
    rounding: Rounding.half_up  // how `*` and `/` round to a penny
}

type Payment = Money {}  // any currency, known only while running

type Pounds = Decimal {
    scale: 2
}

type Guests = Int {
    min: 1
    max: 12
}

type AmountTooPrecise = Failure {
    kind: FailureKind.invalid_value
}

type MixedCurrencies = Failure {
    kind: FailureKind.invalid_value
}

/// A cake's price, written in pence.
function list_price() -> CakePrice {
    return CakePrice(money.from_minor(1050, Currency.gbp))  // £10.50
}

/// An amount a customer typed, in major units of their currency.
function entered(amount: Pounds, currency: Currency) fails AmountTooPrecise -> Payment {
    var entered_amount: Money = attempt money.of(amount, currency) {
        too_many_places: AmountTooPrecise  // 10.50 in yen
    }
    return Payment(entered_amount)
}

/// A cake delivered: one currency, so a plain `+`.
function delivered(cake: CakePrice, delivery: CakePrice) -> CakePrice {
    return CakePrice(cake + delivery)
}

/// The VAT in a price at 20%, rounded half up to a penny.
function vat_in(total: CakePrice) -> CakePrice {
    return CakePrice(total * 0.2)
}

/// A price before VAT: dividing by a rate.
function before_vat(gross: CakePrice) -> CakePrice {
    return CakePrice(gross / 1.20)
}

/// A bill shared between friends, every penny to someone.
function bill_shares(total: CakePrice, guests: Guests) -> List<Payment> {
    return list.map(money.split(total, guests), share -> Payment(share))
}

/// Payments added up, in whatever currency they share.
function received(first: Payment, second: Payment) fails MixedCurrencies -> Payment {
    var total: Money = attempt first + second {
        currency_mismatch: MixedCurrencies  // a pound and a euro
    }
    return Payment(total)
}

/// A price for people in the reader's locale.
function price_label(cake: CakePrice, customer: Locale) -> PresentationText {
    return text.format(cake, locale: customer)
}
```

```jadpo
test "amounts add, round and share in minor units" {
    var delivery: CakePrice = CakePrice(money.from_minor(250, Currency.gbp))
    var gross: CakePrice = CakePrice(money.from_minor(1200, Currency.gbp))
    var bill: CakePrice = CakePrice(money.from_minor(1000, Currency.gbp))
    assert list_price() == money.from_minor(1050, Currency.gbp)
    assert delivered(list_price(), delivery) == money.from_minor(1300, Currency.gbp)
    assert vat_in(list_price()) == money.from_minor(210, Currency.gbp)
    assert before_vat(gross) == money.from_minor(1000, Currency.gbp)
    var pence: List<Int> = list.map(bill_shares(bill, 3), share -> money.minor(share))
    assert pence == [334, 333, 333]
}

test "a currency known only while running is checked as it runs" {
    var in_yen: Payment = attempt entered(500.00, Currency.jpy)
    assert in_yen == money.from_minor(500, Currency.jpy)
    assert entered(10.50, Currency.jpy) fails AmountTooPrecise
    var euros: Payment = Payment(money.from_minor(500, Currency.eur))
    var pounds: Payment = Payment(money.from_minor(250, Currency.gbp))
    var both: Payment = attempt received(euros, euros)
    assert both == money.from_minor(1000, Currency.eur)
    assert received(euros, pounds) fails MixedCurrencies
}

test "an amount is shown, written and read one way" {
    var large: CakePrice = CakePrice(money.from_minor(123450, Currency.gbp))
    var abroad: Payment = Payment(money.from_minor(123450, Currency.eur))
    assert price_label(large, Locale.en_gb) == "£1,234.50"
    assert text.format(abroad, locale: Locale.en_us) == "€1,234.50"
    assert text.format(list_price(), locale: Locale.en_gb, precision: MoneyPrecision.whole) == "£11"
    assert text.from(list_price()) == "GBP 10.50"
    assert text.format(Currency.jpy, locale: Locale.en_gb) == "Japanese Yen"
}
```

Inside `if (money.currency(a) == money.currency(b))`, the checker knows
the two currencies match, so `+` needs no `attempt`. Comparing a
currency with one written out, as in `money.currency(a) ==
Currency.gbp`, proves that currency, so the amount builds a type that
fixes it with no arm for `currency`:

```jadpo
type Payment = Money {}

type SterlingPayment = Money {
    currency: Currency.gbp
}

/// Two payments added when they share a currency, the first otherwise.
function sum_if_shared(a: Payment, b: Payment) -> Payment {
    if (money.currency(a) == money.currency(b)) {
        return Payment(a + b)
    }
    return a
}

/// A payment in pounds, when it is one.
function sterling(paid: Payment) -> SterlingPayment? {
    if (money.currency(paid) == Currency.gbp) {
        return SterlingPayment(paid)
    }
    return none
}
```
