UNPKG

stdnum

Version:
239 lines (215 loc) 6.36 kB
/** * New Zealand Bank account number * * In the real bank account format for country - New Zealand is in this format. * As per the standards bank account number should be as the format * BB-RRRR-AAAAAAA-SSS (B- Bank, R-Branch, A-Account,S-Suffix). * BBRRRR is captured as the National bank Code and it is expected to enter a * 10 digit account number, like so: AAAAAAASSS. * * Source * http://www.paymentsnz.co.nz/clearing-systems/bulk-electronic-clearing-system * https://www.ird.govt.nz/resources/d/8/d8e49dce-1bda-4875-8acf-9ebf908c6e17/rwt-nrwt-spec-2014.pdf * * BANK */ import * as exceptions from '../exceptions'; import { strings } from '../util'; import { Validator, ValidateReturn } from '../types'; import { weightedSum } from '../util/checksum'; // Data from pages 10 and 11 of // https://www.ird.govt.nz/resources/d/8/d8e49dce-1bda-4875-8acf-9ebf908c6e17/rwt-nrwt-spec-2014.pdf const bankData: Record< string, { algorithm: string; branches: [number, number][]; } > = { '01': { algorithm: 'AB', branches: [ [1, 999], [1100, 1199], [1800, 1899], ], }, '02': { algorithm: 'AB', branches: [ [1, 999], [1200, 1299], ], }, '03': { algorithm: 'AB', branches: [ [1, 999], [1300, 1399], [1500, 1599], [1700, 1799], [1900, 1999], ], }, '06': { algorithm: 'AB', branches: [ [1, 999], [1400, 1499], ], }, '08': { algorithm: 'D', branches: [[6500, 6599]] }, '09': { algorithm: 'E', branches: [[0, 0]] }, '11': { algorithm: 'AB', branches: [ [5000, 6499], [6600, 8999], ], }, '12': { algorithm: 'AB', branches: [ [3000, 3299], [3400, 3499], [3600, 3699], ], }, '13': { algorithm: 'AB', branches: [[4900, 4999]] }, '14': { algorithm: 'AB', branches: [[4700, 4799]] }, '15': { algorithm: 'AB', branches: [[3900, 3999]] }, '16': { algorithm: 'AB', branches: [[4400, 4499]] }, '17': { algorithm: 'AB', branches: [[3300, 3399]] }, '18': { algorithm: 'AB', branches: [[3500, 3599]] }, '19': { algorithm: 'AB', branches: [[4600, 4649]] }, '20': { algorithm: 'AB', branches: [[4100, 4199]] }, '21': { algorithm: 'AB', branches: [[4800, 4899]] }, '22': { algorithm: 'AB', branches: [[4000, 4049]] }, '23': { algorithm: 'AB', branches: [[3700, 3799]] }, '24': { algorithm: 'AB', branches: [[4300, 4349]] }, '25': { algorithm: 'F', branches: [[2500, 2599]] }, '26': { algorithm: 'G', branches: [[2600, 2699]] }, '27': { algorithm: 'AB', branches: [[3800, 3849]] }, '28': { algorithm: 'G', branches: [[2100, 2149]] }, '29': { algorithm: 'G', branches: [[2150, 2299]] }, '30': { algorithm: 'AB', branches: [[2900, 2949]] }, '31': { algorithm: 'X', branches: [[2800, 2849]] }, '33': { algorithm: 'F', branches: [[6700, 6799]] }, '35': { algorithm: 'AB', branches: [[2400, 2499]] }, '38': { algorithm: 'AB', branches: [[9000, 9499]] }, }; const algorithms: Record<string, { weights: number[]; modulus: number }> = { A: { weights: [0, 0, 6, 3, 7, 9, 0, 0, 10, 5, 8, 4, 2, 1, 0, 0, 0, 0], modulus: 11, }, B: { weights: [0, 0, 0, 0, 0, 0, 0, 0, 10, 5, 8, 4, 2, 1, 0, 0, 0, 0], modulus: 11, }, C: { weights: [3, 7, 0, 0, 0, 0, 9, 1, 10, 5, 3, 4, 2, 1, 0, 0, 0, 0], modulus: 11, }, D: { weights: [0, 0, 0, 0, 0, 0, 0, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0], modulus: 11, }, E: { weights: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 4, 3, 2, 0, 0, 0, 1], modulus: 11, }, F: { weights: [0, 0, 0, 0, 0, 0, 0, 1, 7, 3, 1, 7, 3, 1, 0, 0, 0, 0], modulus: 10, }, G: { weights: [0, 0, 0, 0, 0, 0, 0, 1, 3, 7, 1, 3, 7, 1, 0, 3, 7, 1], modulus: 10, }, X: { weights: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], modulus: 1, }, }; function clean(input: string): ReturnType<typeof strings.cleanUnicode> { // We're going to allow '-' or ' ' separators const [valueA, err] = strings.cleanUnicode(input, ''); if (err !== null) { return [valueA, err]; } const cvalue = valueA.trim(); // No separtor, keep it simple if (!/[ -]/.test(cvalue)) { return [cvalue, null]; } const parts = cvalue.split(/[ -]/g); if (parts.length !== 4) { return [cvalue, new exceptions.InvalidFormat()]; } return [ [ parts[0].padStart(2, '0'), parts[1].padStart(4, '0'), parts[2].padStart(7, '0'), parts[3].padStart(3, '0'), ].join(''), null, ]; } const impl: Validator = { name: 'New Zealand Bank Account Number', localName: 'Bank Account Number', compact(input: string): string { const [value, err] = clean(input); if (err) { throw err; } return value; }, format(input: string): string { const [value] = clean(input); return strings.splitAt(value, 2, 6, -3).join('-'); }, validate(input: string): ValidateReturn { const [value, error] = clean(input); if (error) { return { isValid: false, error }; } if (value.length !== 16) { return { isValid: false, error: new exceptions.InvalidLength() }; } if (!strings.isdigits(value)) { return { isValid: false, error: new exceptions.InvalidFormat() }; } const [bank, branch, account, suffix] = strings.splitAt(value, 2, 6, -3); const bankInfo = bankData[bank]; if (!bankInfo) { return { isValid: false, error: new exceptions.InvalidComponent() }; } const bnum = parseInt(branch, 10); if (!bankInfo.branches.some(pair => bnum >= pair[0] && bnum <= pair[1])) { return { isValid: false, error: new exceptions.InvalidComponent() }; } let alg = bankInfo.algorithm; if (alg === 'AB') { alg = parseInt(account, 10) < 990000 ? 'A' : 'B'; } const algInfo = algorithms[alg]; if (!algInfo) { return { isValid: false, error: new exceptions.InvalidComponent() }; } const sum = weightedSum(`${bank}${branch}0${account}0${suffix}`, algInfo); if (String(sum) !== '0') { return { isValid: false, error: new exceptions.InvalidChecksum() }; } return { isValid: true, compact: value, isIndividual: false, isCompany: false, }; }, }; export const { name, localName, abbreviation, validate, format, compact } = impl;