Comparing version 0.1.2 to 1.0.0
{ | ||
"name": "ibankit", | ||
"version": "0.1.2", | ||
"description": "IBAN utilities", | ||
"version": "1.0.0", | ||
"description": "IBAN and BIC utilities", | ||
"main": "lib/index.js", | ||
@@ -6,0 +6,0 @@ "types": "lib/index.d.ts", |
@@ -9,4 +9,13 @@ # ibankit | ||
This library provides full TypesScript support | ||
#### Key Features | ||
- Drop in replacable with iban-js | ||
- Currently conformant with Version 80 of the IBAN registry | ||
- Decodes bank, branch and account numbers from IBAN | ||
- Supports random BBAN / IBAN generation for testing | ||
- Has BIC validation as a bonus | ||
- Supports validation of National Check Digits if part of BBAN format | ||
- This library provides full TypesScript support | ||
- No external dependancies | ||
#### Iban quick examples: | ||
@@ -53,2 +62,7 @@ | ||
### TODO | ||
[ ] For random IBANs the National Check digits is random, rather than "valid" | ||
[ ] Finish writing all national check digit validators (see Oracle spec) | ||
#### References | ||
@@ -58,3 +72,5 @@ | ||
- http://en.wikipedia.org/wiki/ISO_9362 | ||
- https://www.ecb.europa.eu/paym/retpaym/paymint/sepa/shared/pdf/iban_registry.pdf | ||
- https://www.swift.com/resource/iban-registry-pdf | ||
- https://docs.oracle.com/cd/E18727_01/doc.121/e13483/T359831T498954.htm | ||
- https://en.bitcoinwiki.org/wiki/International_Bank_Account_Number | ||
@@ -61,0 +77,0 @@ #### Credits |
@@ -1,5 +0,45 @@ | ||
import { CharacterType, BbanStructurePart } from "./structurePart"; | ||
import { CharacterType, BbanStructurePart, PartType } from "./structurePart"; | ||
import { CountryCode } from "./country"; | ||
import { FormatException, FormatViolation } from "./exceptions"; | ||
/** | ||
* MOD11 check digit computation | ||
*/ | ||
function mod11(value: string, weights: number[]) { | ||
return ( | ||
11 - | ||
(value | ||
.split("") | ||
.reverse() | ||
.reduce((acc, s, idx) => acc + parseInt(s, 10) * weights[idx], 0) % | ||
11) | ||
); | ||
} | ||
/* | ||
** Return a function that is a MOD11 national check digit checker | ||
*/ | ||
function nationalFactory(weights: number[]) { | ||
return (checkdigit: string, bban: string, structure: BbanStructure) => { | ||
const accountNumber = structure.extractValue(bban, PartType.ACCOUNT_NUMBER); | ||
if (accountNumber === null) { | ||
throw new FormatException(FormatViolation.NOT_EMPTY, "account number"); | ||
} | ||
const actual = structure.extractValue(bban, PartType.NATIONAL_CHECK_DIGIT); | ||
const digit = mod11(accountNumber, weights); | ||
if (actual !== String(digit)) { | ||
throw new FormatException( | ||
FormatViolation.NATIONAL_CHECK_DIGIT, | ||
`national check digit(s) don't match expect=[${digit}] actual=[${actual}]`, | ||
String(digit), | ||
String(actual), | ||
); | ||
} | ||
}; | ||
} | ||
/** | ||
* Class which represents bban structure | ||
@@ -30,2 +70,3 @@ * | ||
// Provisional | ||
[CountryCode.AO]: new BbanStructure( | ||
@@ -58,2 +99,3 @@ BbanStructurePart.accountNumber(21, CharacterType.n), | ||
// Provisional | ||
[CountryCode.BF]: new BbanStructure( | ||
@@ -75,2 +117,3 @@ BbanStructurePart.accountNumber(23, CharacterType.n), | ||
// Provisional | ||
[CountryCode.BI]: new BbanStructure( | ||
@@ -80,2 +123,3 @@ BbanStructurePart.accountNumber(12, CharacterType.n), | ||
// Provisional | ||
[CountryCode.BJ]: new BbanStructure( | ||
@@ -102,2 +146,13 @@ BbanStructurePart.bankCode(5, CharacterType.c), | ||
// Provisional | ||
[CountryCode.CG]: new BbanStructure( | ||
BbanStructurePart.accountNumber(23, CharacterType.n), | ||
), | ||
[CountryCode.CH]: new BbanStructure( | ||
BbanStructurePart.bankCode(5, CharacterType.n), | ||
BbanStructurePart.accountNumber(12, CharacterType.c), | ||
), | ||
// Provisional | ||
[CountryCode.CI]: new BbanStructure( | ||
@@ -108,2 +163,3 @@ BbanStructurePart.bankCode(2, CharacterType.c), | ||
// Provisional | ||
[CountryCode.CM]: new BbanStructure( | ||
@@ -118,2 +174,3 @@ BbanStructurePart.accountNumber(23, CharacterType.n), | ||
// Provisional | ||
[CountryCode.CV]: new BbanStructure( | ||
@@ -129,7 +186,2 @@ BbanStructurePart.accountNumber(21, CharacterType.n), | ||
[CountryCode.CH]: new BbanStructure( | ||
BbanStructurePart.bankCode(5, CharacterType.n), | ||
BbanStructurePart.accountNumber(12, CharacterType.c), | ||
), | ||
[CountryCode.CZ]: new BbanStructure( | ||
@@ -155,2 +207,3 @@ BbanStructurePart.bankCode(4, CharacterType.n), | ||
// Provisional | ||
[CountryCode.DZ]: new BbanStructure( | ||
@@ -167,2 +220,7 @@ BbanStructurePart.accountNumber(20, CharacterType.n), | ||
// Provisional | ||
[CountryCode.EG]: new BbanStructure( | ||
BbanStructurePart.accountNumber(23, CharacterType.n), | ||
), | ||
[CountryCode.ES]: new BbanStructure( | ||
@@ -194,2 +252,13 @@ BbanStructurePart.bankCode(4, CharacterType.n), | ||
// Provisional | ||
[CountryCode.GA]: new BbanStructure( | ||
BbanStructurePart.accountNumber(23, CharacterType.n), | ||
), | ||
[CountryCode.GB]: new BbanStructure( | ||
BbanStructurePart.bankCode(4, CharacterType.a), | ||
BbanStructurePart.branchCode(6, CharacterType.n), | ||
BbanStructurePart.accountNumber(8, CharacterType.n), | ||
), | ||
[CountryCode.GE]: new BbanStructure( | ||
@@ -205,8 +274,2 @@ BbanStructurePart.bankCode(2, CharacterType.a), | ||
[CountryCode.GB]: new BbanStructure( | ||
BbanStructurePart.bankCode(4, CharacterType.a), | ||
BbanStructurePart.branchCode(6, CharacterType.n), | ||
BbanStructurePart.accountNumber(8, CharacterType.n), | ||
), | ||
[CountryCode.GL]: new BbanStructure( | ||
@@ -225,3 +288,5 @@ BbanStructurePart.bankCode(4, CharacterType.n), | ||
BbanStructurePart.bankCode(4, CharacterType.c), | ||
BbanStructurePart.accountNumber(20, CharacterType.c), | ||
BbanStructurePart.currencyType(2, CharacterType.n), | ||
BbanStructurePart.accountType(2, CharacterType.n), | ||
BbanStructurePart.accountNumber(16, CharacterType.c), | ||
), | ||
@@ -234,2 +299,8 @@ | ||
// Provisional | ||
[CountryCode.HN]: new BbanStructure( | ||
BbanStructurePart.bankCode(4, CharacterType.a), | ||
BbanStructurePart.accountNumber(20, CharacterType.n), | ||
), | ||
[CountryCode.HU]: new BbanStructure( | ||
@@ -260,2 +331,3 @@ BbanStructurePart.bankCode(3, CharacterType.n), | ||
// Provisional | ||
[CountryCode.IR]: new BbanStructure( | ||
@@ -286,2 +358,7 @@ BbanStructurePart.bankCode(3, CharacterType.n), | ||
// Provisional | ||
[CountryCode.KM]: new BbanStructure( | ||
BbanStructurePart.accountNumber(23, CharacterType.n), | ||
), | ||
[CountryCode.KW]: new BbanStructure( | ||
@@ -327,2 +404,7 @@ BbanStructurePart.bankCode(4, CharacterType.a), | ||
// Provisional | ||
[CountryCode.MA]: new BbanStructure( | ||
BbanStructurePart.accountNumber(24, CharacterType.n), | ||
), | ||
[CountryCode.MC]: new BbanStructure( | ||
@@ -346,2 +428,3 @@ BbanStructurePart.bankCode(5, CharacterType.n), | ||
// Provisional | ||
[CountryCode.MG]: new BbanStructure( | ||
@@ -360,2 +443,3 @@ BbanStructurePart.bankCode(5, CharacterType.n), | ||
// Provisional | ||
[CountryCode.ML]: new BbanStructure( | ||
@@ -385,2 +469,3 @@ BbanStructurePart.bankCode(1, CharacterType.a), | ||
// Provisional | ||
[CountryCode.MZ]: new BbanStructure( | ||
@@ -390,2 +475,14 @@ BbanStructurePart.accountNumber(21, CharacterType.n), | ||
// Provisional | ||
[CountryCode.NE]: new BbanStructure( | ||
BbanStructurePart.bankCode(2, CharacterType.a), | ||
BbanStructurePart.accountNumber(22, CharacterType.n), | ||
), | ||
// Provisional | ||
[CountryCode.NI]: new BbanStructure( | ||
BbanStructurePart.bankCode(4, CharacterType.a), | ||
BbanStructurePart.accountNumber(24, CharacterType.n), | ||
), | ||
[CountryCode.NL]: new BbanStructure( | ||
@@ -399,3 +496,7 @@ BbanStructurePart.bankCode(4, CharacterType.a), | ||
BbanStructurePart.accountNumber(6, CharacterType.n), | ||
BbanStructurePart.nationalCheckDigit(1, CharacterType.n), | ||
BbanStructurePart.nationalCheckDigit( | ||
1, | ||
CharacterType.n, | ||
nationalFactory([5, 4, 3, 2, 7, 6, 5, 4, 3, 2]), | ||
), | ||
), | ||
@@ -452,3 +553,3 @@ | ||
BbanStructurePart.accountNumber(16, CharacterType.n), | ||
BbanStructurePart.accountNumber(3, CharacterType.a), | ||
BbanStructurePart.currencyType(3, CharacterType.a), | ||
), | ||
@@ -480,2 +581,3 @@ | ||
// Provisional | ||
[CountryCode.SN]: new BbanStructure( | ||
@@ -498,2 +600,13 @@ BbanStructurePart.bankCode(1, CharacterType.a), | ||
// Provisional | ||
[CountryCode.TG]: new BbanStructure( | ||
BbanStructurePart.bankCode(2, CharacterType.a), | ||
BbanStructurePart.accountNumber(22, CharacterType.n), | ||
), | ||
// Provisional | ||
[CountryCode.TD]: new BbanStructure( | ||
BbanStructurePart.accountNumber(23, CharacterType.n), | ||
), | ||
[CountryCode.TL]: new BbanStructure( | ||
@@ -522,2 +635,7 @@ BbanStructurePart.bankCode(3, CharacterType.n), | ||
[CountryCode.VA]: new BbanStructure( | ||
BbanStructurePart.bankCode(3, CharacterType.c), | ||
BbanStructurePart.accountNumber(15, CharacterType.n), | ||
), | ||
[CountryCode.VG]: new BbanStructure( | ||
@@ -545,2 +663,27 @@ BbanStructurePart.bankCode(4, CharacterType.c), | ||
validate(bban: string) { | ||
this.validateBbanLength(bban); | ||
this.validateBbanEntries(bban); | ||
} | ||
extractValue(bban: string, partType: PartType): string | null { | ||
let bbanPartOffset = 0; | ||
let result = null; | ||
for (let part of this.getParts()) { | ||
const partLength = part.getLength(); | ||
const partValue = bban.substring( | ||
bbanPartOffset, | ||
bbanPartOffset + partLength, | ||
); | ||
bbanPartOffset = bbanPartOffset + partLength; | ||
if (part.getPartType() == partType) { | ||
result = (result || "") + partValue; | ||
} | ||
} | ||
return result; | ||
} | ||
/** | ||
@@ -575,2 +718,65 @@ * @param countryCode the country code. | ||
} | ||
private validateBbanLength(bban: string) { | ||
const expectedBbanLength = this.getBbanLength(); | ||
const bbanLength = bban.length; | ||
if (expectedBbanLength != bbanLength) { | ||
throw new FormatException( | ||
FormatViolation.BBAN_LENGTH, | ||
`[${bban}] length is ${bbanLength}, expected BBAN length is: ${expectedBbanLength}`, | ||
String(bbanLength), | ||
String(expectedBbanLength), | ||
); | ||
} | ||
} | ||
private validateBbanEntries(bban: string) { | ||
let offset = 0; | ||
for (let part of this.getParts()) { | ||
const partLength = part.getLength(); | ||
const entryValue = bban.substring(offset, offset + partLength); | ||
offset = offset + partLength; | ||
// validate character type | ||
this.validateBbanEntryCharacterType(bban, part, entryValue); | ||
} | ||
} | ||
private validateBbanEntryCharacterType( | ||
bban: string, | ||
part: BbanStructurePart, | ||
entryValue: string, | ||
) { | ||
if (part.validate(entryValue)) { | ||
if (part.validateValue) { | ||
part.validateValue(entryValue, bban, this); | ||
} | ||
return; | ||
} | ||
switch (part.getCharacterType()) { | ||
case CharacterType.a: | ||
throw new FormatException( | ||
FormatViolation.BBAN_ONLY_UPPER_CASE_LETTERS, | ||
`[${entryValue}] must contain only upper case letters.`, | ||
entryValue, | ||
); | ||
case CharacterType.c: | ||
throw new FormatException( | ||
FormatViolation.BBAN_ONLY_DIGITS_OR_LETTERS, | ||
`[${entryValue}] must contain only digits or letters.`, | ||
entryValue, | ||
); | ||
case CharacterType.n: | ||
throw new FormatException( | ||
FormatViolation.BBAN_ONLY_DIGITS, | ||
`[${entryValue}] must contain only digits.`, | ||
entryValue, | ||
); | ||
} | ||
} | ||
} |
import { | ||
UnsupportedCountryException, | ||
BicFormatException, | ||
FormatException, | ||
FormatViolation, | ||
@@ -27,3 +27,3 @@ } from "./exceptions"; | ||
* @param bic to be validated. | ||
* @throws BicFormatException if bic is invalid. | ||
* @throws FormatException if bic is invalid. | ||
* UnsupportedCountryException if bic's country is not supported. | ||
@@ -46,3 +46,3 @@ */ | ||
if (bic == null) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.NOT_NULL, | ||
@@ -54,3 +54,3 @@ "Null can't be a valid Bic.", | ||
if (bic.length === 0) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.NOT_EMPTY, | ||
@@ -64,3 +64,3 @@ "Empty string can't be a valid Bic.", | ||
if (bic.length !== BIC8_LENGTH && bic.length !== BIC11_LENGTH) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.BIC_LENGTH_8_OR_11, | ||
@@ -74,3 +74,3 @@ `Bic length must be ${BIC8_LENGTH} or ${BIC11_LENGTH}`, | ||
if (bic !== bic.toUpperCase()) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.BIC_ONLY_UPPER_CASE_LETTERS, | ||
@@ -86,3 +86,3 @@ "Bic must contain only upper case letters.", | ||
if (!ucRegex.test(bankCode)) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.BANK_CODE_ONLY_LETTERS, | ||
@@ -103,3 +103,3 @@ "Bank code must contain only letters.", | ||
) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.COUNTRY_CODE_ONLY_UPPER_CASE_LETTERS, | ||
@@ -123,3 +123,3 @@ "Bic country code must contain upper case letters", | ||
if (!ucnumRegex.test(locationCode)) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.LOCATION_CODE_ONLY_LETTERS_OR_DIGITS, | ||
@@ -136,3 +136,3 @@ "Location code must contain only letters or digits.", | ||
if (!ucnumRegex.test(branchCode)) { | ||
throw new BicFormatException( | ||
throw new FormatException( | ||
FormatViolation.BRANCH_CODE_ONLY_LETTERS_OR_DIGITS, | ||
@@ -139,0 +139,0 @@ "Branch code must contain only letters or digits.", |
@@ -528,9 +528,13 @@ /** | ||
let info; | ||
if (code.length === 3) { | ||
return by3code[code][0]; | ||
info = by3code[code]; | ||
} else if (code.length === 2) { | ||
return by2code[code][0]; | ||
info = by2code[code]; | ||
} | ||
if (info) { | ||
return info[0]; | ||
} | ||
return null; | ||
} |
@@ -18,2 +18,4 @@ export enum FormatViolation { | ||
NATIONAL_CHECK_DIGIT, | ||
// IBAN Specific | ||
@@ -34,3 +36,3 @@ CHECK_DIGIT_TWO_DIGITS, | ||
export class BicFormatException extends Error { | ||
export class FormatException extends Error { | ||
formatViolation: FormatViolation; | ||
@@ -54,21 +56,2 @@ actual?: string; | ||
export class IbanFormatException extends Error { | ||
formatViolation: FormatViolation; | ||
actual?: string; | ||
expected?: string; | ||
constructor( | ||
formatViolation: FormatViolation, | ||
msg: string, | ||
expected?: string, | ||
actual?: string, | ||
) { | ||
super(msg); | ||
this.formatViolation = formatViolation; | ||
this.expected = expected; | ||
this.actual = actual; | ||
} | ||
} | ||
export class UnsupportedCountryException extends Error { | ||
@@ -75,0 +58,0 @@ actual?: string; |
316
src/iban.ts
import * as ibanUtil from "./ibanUtil"; | ||
import { countryByCode, CountryCode } from "./country"; | ||
import { | ||
FormatViolation, | ||
IbanFormatException, | ||
UnsupportedCountryException, | ||
} from "./exceptions"; | ||
import { BbanStructure } from "./bbanStructure"; | ||
import { PartType } from "./structurePart"; | ||
import { IBANBuilder } from "./ibanBuilder"; | ||
function randInt(maxVal: number, minVal: number = 0) { | ||
return Math.floor(Math.random() * maxVal) + minVal; | ||
} | ||
/** | ||
* Iban Builder Class | ||
*/ | ||
export class IBANBuilder { | ||
private countryCodeValue?: CountryCode; | ||
private bankCodeValue?: string; | ||
private branchCodeValue?: string; | ||
private nationalCheckDigitValue?: string; | ||
private accountTypeValue?: string; | ||
private accountNumberValue?: string; | ||
private ownerAccountTypeValue?: string; | ||
private identificationNumberValue?: string; | ||
/** | ||
* Creates an Iban Builder instance. | ||
*/ | ||
public constructor() {} | ||
/** | ||
* Sets iban's country code. | ||
* | ||
* @param countryCode CountryCode | ||
* @return builder Builder | ||
*/ | ||
countryCode(countryCode: CountryCode): IBANBuilder { | ||
this.countryCodeValue = countryCode; | ||
return this; | ||
} | ||
/** | ||
* Sets iban's bank code. | ||
* | ||
* @param bankCode String | ||
* @return builder Builder | ||
*/ | ||
bankCode(bankCode: string): IBANBuilder { | ||
this.bankCodeValue = bankCode; | ||
return this; | ||
} | ||
/** | ||
* Sets iban's branch code. | ||
* | ||
* @param branchCode String | ||
* @return builder Builder | ||
*/ | ||
branchCode(branchCode: string): IBANBuilder { | ||
this.branchCodeValue = branchCode; | ||
return this; | ||
} | ||
/** | ||
* Sets iban's account number. | ||
* | ||
* @param accountNumber String | ||
* @return builder Builder | ||
*/ | ||
accountNumber(accountNumber: string): IBANBuilder { | ||
this.accountNumberValue = accountNumber; | ||
return this; | ||
} | ||
/** | ||
* Sets iban's national check digit. | ||
* | ||
* @param nationalCheckDigit String | ||
* @return builder Builder | ||
*/ | ||
nationalCheckDigit(nationalCheckDigit: string): IBANBuilder { | ||
this.nationalCheckDigitValue = nationalCheckDigit; | ||
return this; | ||
} | ||
/** | ||
* Sets iban's account type. | ||
* | ||
* @param accountType String | ||
* @return builder Builder | ||
*/ | ||
accountType(accountType: string): IBANBuilder { | ||
this.accountTypeValue = accountType; | ||
return this; | ||
} | ||
/** | ||
* Sets iban's owner account type. | ||
* | ||
* @param ownerAccountType String | ||
* @return builder Builder | ||
*/ | ||
ownerAccountType(ownerAccountType: string): IBANBuilder { | ||
this.ownerAccountTypeValue = ownerAccountType; | ||
return this; | ||
} | ||
/** | ||
* Sets iban's identification number. | ||
* | ||
* @param identificationNumber String | ||
* @return builder Builder | ||
*/ | ||
identificationNumber(identificationNumber: string): IBANBuilder { | ||
this.identificationNumberValue = identificationNumber; | ||
return this; | ||
} | ||
/** | ||
* Builds new iban instance. | ||
* | ||
* @param validate boolean indicates if the generated IBAN needs to be | ||
* validated after generation | ||
* @return new iban instance. | ||
* @exception IbanFormatException if values are not parsable by Iban Specification | ||
* <a href="http://en.wikipedia.org/wiki/ISO_13616">ISO_13616</a> | ||
* @exception UnsupportedCountryException if country is not supported | ||
*/ | ||
build(validate: boolean = true): IBAN { | ||
// null checks | ||
this.require( | ||
this.countryCodeValue, | ||
this.bankCodeValue, | ||
this.accountNumberValue, | ||
); | ||
// iban is formatted with default check digit. | ||
const formattedIban = this.formatIban(); | ||
const checkDigit = ibanUtil.calculateCheckDigit(formattedIban); | ||
// replace default check digit with calculated check digit | ||
const ibanValue = ibanUtil.replaceCheckDigit(formattedIban, checkDigit); | ||
if (validate) { | ||
ibanUtil.validate(ibanValue); | ||
} | ||
return new IBAN(ibanValue); | ||
} | ||
/** | ||
* Builds random iban instance. | ||
* | ||
* @return random iban instance. | ||
* @exception IbanFormatException if values are not parsable by Iban Specification | ||
* <a href="http://en.wikipedia.org/wiki/ISO_13616">ISO_13616</a> | ||
* @exception UnsupportedCountryException if country is not supported | ||
* | ||
*/ | ||
public buildRandom(): IBAN { | ||
if (this.countryCodeValue == null) { | ||
const countryCodes = BbanStructure.supportedCountries(); | ||
this.countryCodeValue = countryCodes[randInt(countryCodes.length)]; | ||
} | ||
this.fillMissingFieldsRandomly(); | ||
return this.build(); | ||
} | ||
/** | ||
* Returns formatted bban string. | ||
*/ | ||
private formatBban(): string { | ||
const parts: string[] = []; | ||
const structure = BbanStructure.forCountry(this.countryCodeValue); | ||
if (structure === null) { | ||
throw new UnsupportedCountryException( | ||
"Country code is not supported.", | ||
this.countryCodeValue, | ||
); | ||
} | ||
for (const part of structure.getParts()) { | ||
switch (part.getPartType()) { | ||
case PartType.BANK_CODE: | ||
parts.push(this.bankCodeValue!); | ||
break; | ||
case PartType.BRANCH_CODE: | ||
parts.push(this.branchCodeValue!); | ||
break; | ||
case PartType.ACCOUNT_NUMBER: | ||
parts.push(this.accountNumberValue!); | ||
break; | ||
case PartType.NATIONAL_CHECK_DIGIT: | ||
parts.push(this.nationalCheckDigitValue!); | ||
break; | ||
case PartType.ACCOUNT_TYPE: | ||
parts.push(this.accountTypeValue!); | ||
break; | ||
case PartType.OWNER_ACCOUNT_NUMBER: | ||
parts.push(this.ownerAccountTypeValue!); | ||
break; | ||
case PartType.IDENTIFICATION_NUMBER: | ||
parts.push(this.identificationNumberValue!); | ||
break; | ||
} | ||
} | ||
return parts.join(); | ||
} | ||
/** | ||
* Returns formatted iban string with default check digit. | ||
*/ | ||
private formatIban(): string { | ||
return `${this.countryCodeValue}${ | ||
ibanUtil.DEFAULT_CHECK_DIGIT | ||
}${this.formatBban()}`; | ||
} | ||
private require( | ||
countryCode: CountryCode | undefined, | ||
bankCode: string | undefined, | ||
accountNumber: string | undefined, | ||
) { | ||
if (countryCode == null) { | ||
throw new IbanFormatException( | ||
FormatViolation.COUNTRY_CODE_NOT_NULL, | ||
"countryCode is required; it cannot be null", | ||
); | ||
} | ||
if (bankCode == null) { | ||
throw new IbanFormatException( | ||
FormatViolation.BANK_CODE_NOT_NULL, | ||
"bankCode is required; it cannot be null", | ||
); | ||
} | ||
if (accountNumber == null) { | ||
throw new IbanFormatException( | ||
FormatViolation.ACCOUNT_NUMBER_NOT_NULL, | ||
"accountNumber is required; it cannot be null", | ||
); | ||
} | ||
} | ||
private fillMissingFieldsRandomly() { | ||
const structure = BbanStructure.forCountry(this.countryCodeValue); | ||
if (structure == null) { | ||
throw new UnsupportedCountryException( | ||
"Country code is not supported.", | ||
this.countryCodeValue, | ||
); | ||
} | ||
for (const entry of structure.getParts()) { | ||
switch (entry.getPartType()) { | ||
case PartType.BANK_CODE: | ||
if (!this.bankCodeValue) { | ||
this.bankCodeValue = entry.getRandom(); | ||
} | ||
break; | ||
case PartType.BRANCH_CODE: | ||
if (!this.branchCodeValue) { | ||
this.branchCodeValue = entry.getRandom(); | ||
} | ||
break; | ||
case PartType.ACCOUNT_NUMBER: | ||
if (!this.accountNumberValue) { | ||
this.accountNumberValue = entry.getRandom(); | ||
} | ||
break; | ||
case PartType.NATIONAL_CHECK_DIGIT: | ||
if (!this.nationalCheckDigitValue) { | ||
this.nationalCheckDigitValue = entry.getRandom(); | ||
} | ||
break; | ||
case PartType.ACCOUNT_TYPE: | ||
if (!this.accountTypeValue) { | ||
this.accountTypeValue = entry.getRandom(); | ||
} | ||
break; | ||
case PartType.OWNER_ACCOUNT_NUMBER: | ||
if (!this.ownerAccountTypeValue) { | ||
this.ownerAccountTypeValue = entry.getRandom(); | ||
} | ||
break; | ||
case PartType.IDENTIFICATION_NUMBER: | ||
if (!this.identificationNumberValue) { | ||
this.identificationNumberValue = entry.getRandom(); | ||
} | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* International Bank Account Number | ||
@@ -311,3 +11,2 @@ * | ||
export class IBAN { | ||
// Cache string value of the iban | ||
private value: string; | ||
@@ -319,3 +18,3 @@ | ||
* @param iban the String to be parsed, any spaces are removed. | ||
* @throws IbanFormatException if the String doesn't contain parsable Iban | ||
* @throws FormatException if the String doesn't contain parsable Iban | ||
* InvalidCheckDigitException if Iban has invalid check digit | ||
@@ -390,2 +89,11 @@ * UnsupportedCountryException if Iban's Country is not supported. | ||
/** | ||
* Returns iban's currency type if encoded separate from account number | ||
* | ||
* @return nationalCheckDigit String | ||
*/ | ||
public getCurrencyType(): string | null { | ||
return ibanUtil.getCurrencyType(this.value); | ||
} | ||
/** | ||
* Returns iban's account type. | ||
@@ -432,3 +140,3 @@ * | ||
* @return an Iban object holding the value represented by the string argument. | ||
* @throws IbanFormatException if the String doesn't contain parsable Iban | ||
* @throws FormatException if the String doesn't contain parsable Iban | ||
* InvalidCheckDigitException if Iban has invalid check digit | ||
@@ -435,0 +143,0 @@ * UnsupportedCountryException if Iban's Country is not supported. |
import { CountryCode, countryByCode } from "./country"; | ||
import { BbanStructure } from "./bbanStructure"; | ||
import { PartType, CharacterType, BbanStructurePart } from "./structurePart"; | ||
import { PartType } from "./structurePart"; | ||
import { | ||
InvalidCheckDigitException, | ||
IbanFormatException, | ||
FormatViolation, | ||
FormatException, | ||
UnsupportedCountryException, | ||
@@ -63,5 +63,7 @@ } from "./exceptions"; | ||
validateBbanLength(iban, structure); | ||
validateBbanEntries(iban, structure); | ||
structure.validate(getBban(iban)); | ||
// validateBbanLength(iban, structure); | ||
// validateBbanEntries(iban, structure); | ||
validateCheckDigit(iban); | ||
@@ -186,2 +188,12 @@ } | ||
/** | ||
* Returns iban's currency type | ||
* | ||
* @param iban String | ||
* @return nationalCheckDigit String | ||
*/ | ||
export function getCurrencyType(iban: string): string | null { | ||
return extractBbanEntry(iban, PartType.CURRENCY_TYPE); | ||
} | ||
/** | ||
* Returns iban's account type. | ||
@@ -256,3 +268,3 @@ * | ||
if (iban == null) { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.NOT_NULL, | ||
@@ -264,3 +276,3 @@ "Null can't be a valid Iban.", | ||
if (iban.length === 0) { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.NOT_EMPTY, | ||
@@ -275,3 +287,3 @@ "Empty string can't be a valid Iban.", | ||
if (iban.length < COUNTRY_CODE_LENGTH) { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.COUNTRY_CODE_TWO_LETTERS, | ||
@@ -287,3 +299,3 @@ "Iban must contain 2 char country code.", | ||
if (countryCode !== countryCode.toUpperCase() || !ucRegex.test(countryCode)) { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.COUNTRY_CODE_ONLY_UPPER_CASE_LETTERS, | ||
@@ -297,3 +309,3 @@ "Iban country code must contain upper case letters.", | ||
if (country == null) { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.COUNTRY_CODE_EXISTS, | ||
@@ -318,3 +330,3 @@ "Iban contains non existing country code.", | ||
if (iban.length < COUNTRY_CODE_LENGTH + CHECK_DIGIT_LENGTH) { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.CHECK_DIGIT_TWO_DIGITS, | ||
@@ -330,3 +342,3 @@ "Iban must contain 2 digit check digit.", | ||
if (!numRegex.test(checkDigit)) { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.CHECK_DIGIT_ONLY_DIGITS, | ||
@@ -339,63 +351,2 @@ "Iban's check digit should contain only digits.", | ||
function validateBbanLength(iban: string, structure: BbanStructure) { | ||
const expectedBbanLength = structure.getBbanLength(); | ||
const bban = getBban(iban); | ||
const bbanLength = bban.length; | ||
if (expectedBbanLength != bbanLength) { | ||
throw new IbanFormatException( | ||
FormatViolation.BBAN_LENGTH, | ||
`[${bban}] length is ${bbanLength}, expected BBAN length is: ${expectedBbanLength}`, | ||
String(bbanLength), | ||
String(expectedBbanLength), | ||
); | ||
} | ||
} | ||
function validateBbanEntries(iban: string, structure: BbanStructure) { | ||
const bban = getBban(iban); | ||
let offset = 0; | ||
for (let part of structure.getParts()) { | ||
const partLength = part.getLength(); | ||
const entryValue = bban.substring(offset, offset + partLength); | ||
offset = offset + partLength; | ||
// validate character type | ||
validateBbanEntryCharacterType(part, entryValue); | ||
} | ||
} | ||
function validateBbanEntryCharacterType( | ||
part: BbanStructurePart, | ||
entryValue: string, | ||
) { | ||
if (part.validate(entryValue)) { | ||
return; | ||
} | ||
switch (part.getCharacterType()) { | ||
case CharacterType.a: | ||
throw new IbanFormatException( | ||
FormatViolation.BBAN_ONLY_UPPER_CASE_LETTERS, | ||
`[${entryValue}] must contain only upper case letters.`, | ||
entryValue, | ||
); | ||
case CharacterType.c: | ||
throw new IbanFormatException( | ||
FormatViolation.BBAN_ONLY_DIGITS_OR_LETTERS, | ||
`[${entryValue}] must contain only digits or letters.`, | ||
entryValue, | ||
); | ||
case CharacterType.n: | ||
throw new IbanFormatException( | ||
FormatViolation.BBAN_ONLY_DIGITS, | ||
`[${entryValue}] must contain only digits.`, | ||
entryValue, | ||
); | ||
} | ||
} | ||
/** | ||
@@ -449,3 +400,3 @@ * Calculates | ||
} else { | ||
throw new IbanFormatException( | ||
throw new FormatException( | ||
FormatViolation.IBAN_VALID_CHARACTERS, | ||
@@ -485,19 +436,3 @@ `Invalid Character[${ch}] = '${code}'`, | ||
let bbanPartOffset = 0; | ||
let result = null; | ||
for (let part of structure.getParts()) { | ||
const partLength = part.getLength(); | ||
const partValue = bban.substring( | ||
bbanPartOffset, | ||
bbanPartOffset + partLength, | ||
); | ||
bbanPartOffset = bbanPartOffset + partLength; | ||
if (part.getPartType() == partType) { | ||
result = (result || "") + partValue; | ||
} | ||
} | ||
return result; | ||
return structure.extractValue(bban, partType); | ||
} |
export { CountryCode } from "./country"; | ||
export { BbanStructure } from "./bbanStructure"; | ||
export { IBAN, IBANBuilder } from "./iban"; | ||
export { IBAN } from "./iban"; | ||
export { IBANBuilder } from "./ibanBuilder"; | ||
export { BIC } from "./bic"; |
@@ -0,1 +1,4 @@ | ||
import { randInt } from "./randInt"; | ||
import { BbanStructure } from "./bbanStructure"; | ||
export enum PartType { | ||
@@ -6,2 +9,3 @@ BANK_CODE, | ||
NATIONAL_CHECK_DIGIT, | ||
CURRENCY_TYPE, | ||
ACCOUNT_TYPE, | ||
@@ -28,10 +32,24 @@ OWNER_ACCOUNT_NUMBER, | ||
c, | ||
/** | ||
* Blank space | ||
*/ | ||
e, | ||
} | ||
const charByCharacterType: Record<CharacterType, string[]> = { | ||
[CharacterType.n]: "0123456789".split(""), | ||
[CharacterType.a]: "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""), | ||
[CharacterType.c]: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".split(""), | ||
// Use by random string generation | ||
const charByCharacterType: Record<CharacterType, string> = { | ||
[CharacterType.n]: "0123456789", | ||
[CharacterType.a]: "ABCDEFGHIJKLMNOPQRSTUVWXYZ", | ||
[CharacterType.c]: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ", | ||
[CharacterType.e]: " ", | ||
}; | ||
// Used by validation | ||
const charByCharacterRE: Record<CharacterType, RegExp> = { | ||
[CharacterType.n]: /^[0-9]+$/, | ||
[CharacterType.a]: /^[A-Z]+$/, | ||
[CharacterType.c]: /^[0-9A-Za-z]+$/, | ||
[CharacterType.e]: /^ +$/, | ||
}; | ||
export class BbanStructurePart { | ||
@@ -42,2 +60,4 @@ private entryType: PartType; | ||
validateValue?(value: string, bban: string, structure: BbanStructure): void; | ||
private constructor( | ||
@@ -47,2 +67,3 @@ entryType: PartType, | ||
length: number, | ||
validate?: (value: string, bban: string, structure: BbanStructure) => void, | ||
) { | ||
@@ -52,2 +73,3 @@ this.entryType = entryType; | ||
this.length = length; | ||
this.validateValue = validate; | ||
} | ||
@@ -83,2 +105,3 @@ | ||
characterType: CharacterType, | ||
validate?: (value: string, bban: string, structure: BbanStructure) => void, | ||
): BbanStructurePart { | ||
@@ -89,2 +112,3 @@ return new BbanStructurePart( | ||
length, | ||
validate, | ||
); | ||
@@ -100,2 +124,9 @@ } | ||
static currencyType( | ||
length: number, | ||
characterType: CharacterType, | ||
): BbanStructurePart { | ||
return new BbanStructurePart(PartType.CURRENCY_TYPE, characterType, length); | ||
} | ||
static ownerAccountNumber( | ||
@@ -136,8 +167,5 @@ length: number, | ||
getRandom(): string { | ||
const charChoices: string[] = charByCharacterType[this.characterType]; | ||
const charChoices = charByCharacterType[this.characterType]; | ||
let s: string[] = []; | ||
const randInt = (maxVal: number, minVal: number = 0) => | ||
Math.floor(Math.random() * maxVal) + minVal; | ||
for (let i = 0; i < this.getLength(); i += 1) { | ||
@@ -154,6 +182,4 @@ s.push(charChoices[randInt(charChoices.length)]); | ||
validate(value: string): boolean { | ||
const validValues = charByCharacterType[this.characterType]; | ||
return value.split("").find(v => !validValues.includes(v)) === undefined; | ||
return charByCharacterRE[this.characterType].test(value); | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
78523
14
2394
0
84