nz-bank-account-validator
Version:
A small, zero dependency NZ bank account validation library that runs everywhere.
157 lines (119 loc) • 3.69 kB
JavaScript
import {
partConstants,
partIndexes,
partMaxLengths,
bankData,
bankChecksums
} from './constants';
import {
isString,
isNumbersOnly,
padLeft,
inRanges,
sumChars,
getPaddedAccountArray
} from './utils';
export default {
getId(input) {
return this.getPartsObject(input).id;
},
getBranch(input) {
return this.getPartsObject(input).branch;
},
getBase(input) {
return this.getPartsObject(input).base;
},
getSuffix(input) {
return this.getPartsObject(input).suffix;
},
isPartsObject(obj = {}) {
if (typeof obj !== 'object') return false;
const inputsKeys = Object.keys(obj);
const requiredKeys = Object.keys(partConstants);
const filteredKeys = requiredKeys.filter(k => inputsKeys.includes(k));
return requiredKeys.length === filteredKeys.length;
},
splitString(str = '') {
const parts = isString(str) ? str.split(/[^0-9]/) : [];
// If the input string had no delimiters, and its length is
// long enough, manually forge an array.
if (parts.length === 1) {
parts[0] = str.slice(0, 2);
parts[1] = str.slice(2, 5);
parts[2] = str.slice(5, 12);
parts[3] = str.slice(12);
}
return parts.filter(i => i.length);
},
getPartsObject(input) {
if (this.isPartsObject(input)) {
return input;
}
if (!isString(input)) {
return {};
}
const parts = this.splitString(input);
return {
id: parts[partIndexes.id],
branch: parts[partIndexes.branch],
base: parts[partIndexes.base],
suffix: parts[partIndexes.suffix]
};
},
partsObjectValid(obj = {}) {
const keys = Object.keys(obj);
if (keys.length !== 4) {
return false;
}
return keys.reduce((isValid, key) => {
const value = obj[key];
const onlyNumbers = isNumbersOnly(value);
const withinMaxLength = isString(value) && (value.length <= partMaxLengths[key]);
const valueValid = onlyNumbers && withinMaxLength;
return isValid && valueValid;
}, true);
},
validate(input) {
const partsObject = this.getPartsObject(input);
if (!this.partsObjectValid(partsObject)) {
return false;
}
const { id, branch, base } = partsObject;
// VALIDATION - STEP 1
// https://www.ird.govt.nz/resources/d/8/d8e49dce-1bda-4875-8acf-9ebf908c6e17/rwt-nrwt-spec-2014.pdf (PAGE 10)
const bankData = this.getBankData(id, branch);
if (!bankData) { return false; }
const algorithm = this.getChecksum(bankData, base);
if (!algorithm) { return false; }
// VALIDATION - STEP 2
// https://www.ird.govt.nz/resources/d/8/d8e49dce-1bda-4875-8acf-9ebf908c6e17/rwt-nrwt-spec-2014.pdf (PAGE 11)
const { weighting, modulo, specialCase } = algorithm;
const earlyExit = !specialCase;
const result = getPaddedAccountArray(partsObject).reduce((result, num, idx) => {
const multiplied = num * weighting[idx];
if (earlyExit || multiplied < 10) {
return result + multiplied;
}
const summed = sumChars(multiplied);
const summedTwice = sumChars(summed);
const final = summed < 10 ? summed : summedTwice;
return result + final;
}, 0);
// Final modulo test
return result % modulo === 0;
},
getBankData(id, branch) {
const paddedId = padLeft(id, partMaxLengths.id);
return bankData.find(r => {
const ranges = r.branches[paddedId];
return ranges && inRanges(branch, ranges);
});
},
getChecksum(bankData, base) {
let { key } = bankData;
if (key === 'AB') {
key = (parseInt(base, 10) < 990000) ? 'A' : 'B';
}
return bankChecksums[key];
}
};