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, 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, 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 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:
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)}
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:
/// 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" }}
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_literalcheck failed: 1 error, 1 file
Markdown for agents: /docs/bank-accounts.md.Built from Jadpo main at a707efb.