# Bank accounts

A `BankAccount` holds the details a payment to a bank account needs. It
is a choice of the three ways an account is written down:

| Variant | What it holds | Sent as |
|---|---|---|
| `uk` | `sort_code`, a `UkSortCode`, and `account_number`, a `UkAccountNumber` | `{"tag": "uk", "sort_code": "089999", "account_number": "66374958"}` |
| `iban` | `iban`, an `Iban` | `{"tag": "iban", "iban": "GB29NWBK60161331926819"}` |
| `other` | `country`, a `Country`; `bank_code`, its bank's code where the country uses one (a US routing number, an Indian IFSC), or `none`; and `account_number`, each up to ISO 20022's lengths in capital letters and digits | `{"tag": "other", "country": "US", "bank_code": "021000021", "account_number": "123456789"}` |

`other` serves every other country, since no standard defines a US or
other national account. The parts of `uk` and `iban` are the standard
payment and identity numbers listed under
[Groups with the baseline only](groups-with-the-baseline-only.md), and
keep their rules: a sort code's six digits, an IBAN's mod-97 check and
its country's length.

In `other`, `bank_code` is `none` where the bank is part of the account
number, as in New Zealand. `bank_code` and `account_number` are capital
letters and digits with no spaces or punctuation, as a document's number
is, up to ISO 20022's lengths, 35 and 34.

Jadpo doesn't check any country's own form in `other` yet, as it doesn't
check a document's number for a country without a published one. A
release adds them as they are needed, such as a US bank code as an
`AbaRoutingNumber`. `other` doesn't refuse a country that uses IBANs.

## Showing and storing an account

A `BankAccount` is `financial`
[personal data](marking-personal-fields.md), as an IBAN
and a UK account number are on their own. `text.format` shows it masked
to the last four characters of its number, as it shows a card number:
`•••• 4958`. That is the last four of the account number for `uk` and
`other`, and of the IBAN for `iban`. The sort code isn't shown, though it
isn't secret, so one form serves every variant.

It has no single text form. It is stored as a choice is, like a
`Discount`: its variant's name in `kind`, then a column for each part,
`sort_code`, `account_number`, `iban`, `country` and `bank_code`. It has
no shape, as no choice has one, so your app's own
`Object { sort_code, account_number }` isn't `type.standard_exists`.

## UK account numbers and the modulus check

A UK account number also has to fit its sort code, by the modulus check
Vocalink publishes for Pay.UK's payment schemes. Jadpo can't ship the
tables that check needs, so a `BankAccount` takes any sort code and
account number that are each well formed. That follows the
specification, which says to presume valid a sort code its tables don't
cover. Neither construction nor `json.read` runs the check. The
[design notes](https://github.com/jadpo-dev/jadpo/blob/main/docs/language/notes/19-standard-library.md#uk-modulus-checking) say why,
and what is ready for when the tables may ship.

A business pays its suppliers by the account on each invoice, reads a
payee's account from a payments form, and shows each account only by its
last four:

```jadpo
type FormText = Text {}

type NotAnAccount = Failure {
    kind: FailureKind.invalid_value
}

type ByFasterPayments = Bool {}

/// A supplier's UK account, from the sort code and number on its invoice.
function supplier_account(
    printed_code: UkSortCode,
    printed_number: UkAccountNumber
) -> BankAccount {
    return BankAccount.uk {
        sort_code: printed_code
        account_number: printed_number
    }
}

/// A payee's account as the payments form sends it.
function payee_account(sent: FormText) fails NotAnAccount -> BankAccount {
    return attempt json.read(sent, as: BankAccount) {
        malformed: NotAnAccount
        mismatch: NotAnAccount
    }
}

/// Whether a payment to the account goes by Faster Payments, which takes
/// a UK sort code and account number.
function paid_by_faster_payments(account: BankAccount) -> ByFasterPayments {
    return match account {
        BankAccount.uk -> true
        BankAccount.iban -> false
        BankAccount.other -> false
    }
}

/// An account as a payment's confirmation shows it.
function account_label(account: BankAccount, viewer: Locale) -> PresentationText {
    return text.format(account, locale: viewer)
}
```

```jadpo
test "an account is paid by its kind and shown by its last four" {
    var supplier: BankAccount = supplier_account("089999", "66374958")
    assert paid_by_faster_payments(supplier)
    assert account_label(supplier, Locale.en_gb) == "•••• 4958"
    var abroad: BankAccount = attempt payee_account(
        "{\"tag\":\"iban\",\"iban\":\"DE89370400440532013000\"}"
    )
    assert not paid_by_faster_payments(abroad)
    assert account_label(abroad, Locale.de_de) == "•••• 3000"
    var in_dollars: BankAccount = attempt payee_account(
        "{\"tag\":\"other\",\"country\":\"US\",\"bank_code\":\"021000021\",\"account_number\":\"123456789\"}"
    )
    assert account_label(in_dollars, Locale.en_us) == "•••• 6789"
}

test "an account whose parts break their rules isn't read" {
    assert payee_account(
        "{\"tag\":\"iban\",\"iban\":\"DE89370400440532013001\"}"
    ) fails NotAnAccount
    assert payee_account(
        "{\"tag\":\"uk\",\"sort_code\":\"08-99-99\",\"account_number\":\"66374958\"}"
    ) fails NotAnAccount
    assert payee_account(
        "{\"tag\":\"other\",\"country\":\"US\",\"bank_code\":\"021000021\",\"account_number\":\"1234-5678\"}"
    ) fails NotAnAccount
}
```

A sort code written out is checked while compiling, as any part's
literal is:

```jadpo
/// The account the business's own payments go out from.
function paying_account() -> BankAccount {
    return BankAccount.uk {
        sort_code: "08-99-99"
        account_number: "66374958"
    }
}
```

```text
error: `"08-99-99"` isn't a valid `BankAccount.uk.sort_code`
 --> main.jadpo:4:20
  |
4 |         sort_code: "08-99-99"
  |                    ^^^^^^^^^^ not a valid `UkSortCode`
  reason: The value isn't a valid `UkSortCode`: its pattern and check digits accept only `UkSortCode`'s canonical text, and a literal is checked while compiling.
  next: Write a valid `UkSortCode` in its canonical text (agent)
  rule: type.invalid_literal
  help: https://jadpo.dev/docs/errors/type.invalid_literal

check failed: 1 error, 1 file
```
