New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

iban-ts

Package Overview
Dependencies
Maintainers
0
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

iban-ts - npm Package Compare versions

Comparing version

to
0.7.0

2

package.json
{
"name": "iban-ts",
"version": "0.6.0",
"version": "0.7.0",
"description": "A TypeScript library for validating, formatting, and converting IBAN (International Bank Account Number) and BBAN (Basic Bank Account Number), offering comprehensive support for international banking data standards.",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -7,17 +7,32 @@ import { Specification } from './Specification';

/**
* Converts an IBAN to electronic format by removing non-alphanumeric characters
* Removes non-alphanumeric characters from the string and converts it to uppercase.
*
* @param iban - The IBAN string to format.
* @returns The formatted IBAN string.
*/
const electronicFormat = (iban: string): string => iban.replace(NON_ALPHANUM, '').toUpperCase();
// Improve type safety with a dedicated type for the countries map
type CountryMap = Record<string, Specification>;
const countries: CountryMap = {};
/**
* Type guard for checking if a value is a string.
*
* @param value - The value to check.
* @returns True if the value is a string, false otherwise.
*/
const isString = (value: any): value is string => typeof value === 'string' || value instanceof String;
/**
* Map of country codes to their respective IBAN specifications.
*/
const countries: Record<string, Specification> = {};
/**
* Adds a new IBAN specification for a country.
*
* @param IBAN - The IBAN specification to add.
*/
const addSpecification = (spec: Specification): void => {
countries[spec.countryCode] = spec;
const addSpecification = (IBAN: Specification): void => {
countries[IBAN.countryCode] = IBAN;
};
// Add all the country specifications
addSpecification(new Specification('AD', 24, 'F04F04A12', 'AD1200012030200359100100'));

@@ -57,5 +72,5 @@ addSpecification(new Specification('AE', 23, 'F03F16', 'AE070331234567890123456'));

addSpecification(new Specification('IL', 23, 'F03F03F13', 'IL620108000000099999999'));
addSpecification(new Specification('IQ', 23, 'U04F03A12', 'IQ98NBIQ850123456789012'));
addSpecification(new Specification('IS', 26, 'F04F02F06F10', 'IS140159260076545510730339'));
addSpecification(new Specification('IT', 27, 'U01F05F05A12', 'IT60X0542811101000000123456'));
addSpecification(new Specification('IQ', 23, 'U04F03A12', 'IQ98NBIQ850123456789012'));
addSpecification(new Specification('JO', 30, 'A04F22', 'JO15AAAA1234567890123456789012'));

@@ -102,3 +117,3 @@ addSpecification(new Specification('KW', 30, 'U04A22', 'KW81CBKU0000000000001234560101'));

// Non-official IBAN countries (using IBAN-like formats)
// Non-official IBAN countries
addSpecification(new Specification('AO', 25, 'F21', 'AO69123456789012345678901'));

@@ -118,3 +133,3 @@ addSpecification(new Specification('BF', 27, 'F23', 'BF2312345678901234567890123'));

// French territories (same structure as France)
// French territories
addSpecification(new Specification('GF', 27, 'F05F05A11F02', 'GF121234512345123456789AB13'));

@@ -134,18 +149,14 @@ addSpecification(new Specification('GP', 27, 'F05F05A11F02', 'GP791234512345123456789AB13'));

/**
* Type guard for checking if a value is a string.
*/
const isString = (value: unknown): value is string => typeof value === 'string' || value instanceof String;
/**
* Validates an IBAN number.
*
* @param iban - The IBAN to validate.
* @returns True if the IBAN is valid, false otherwise.
*/
const isValid = (iban: unknown): boolean => {
const isValid = (iban: string): boolean => {
if (!isString(iban)) {
return false;
}
const formatted = electronicFormat(iban);
const countryCode = formatted.slice(0, 2);
const countryStructure = countries[countryCode];
return !!countryStructure && countryStructure.isValid(formatted);
iban = electronicFormat(iban);
const countryStructure = countries[iban.slice(0, 2)];
return !!countryStructure && countryStructure.isValid(iban);
};

@@ -155,29 +166,24 @@

* Converts an IBAN to a BBAN.
*
* @param iban - The IBAN to convert.
* @param separator - The separator to use between BBAN blocks, defaults to ' '.
* @returns The BBAN or undefined if conversion fails.
* @throws {Error} If the country code is invalid.
*/
const toBBAN = (iban: string, separator = ' '): string => {
const formatted = electronicFormat(iban);
const countryCode = formatted.slice(0, 2);
const countryStructure = countries[countryCode];
const toBBAN = (iban: string, separator: string = ' '): string | undefined => {
iban = electronicFormat(iban);
const countryStructure = countries[iban.slice(0, 2)];
if (!countryStructure) {
throw new Error(`No country with code ${countryCode}`);
throw new Error('No country with code ' + iban.slice(0, 2));
}
const bban = countryStructure.toBBAN(formatted, separator);
if (!bban) {
throw new Error('Failed to convert IBAN to BBAN');
}
return bban;
return countryStructure.toBBAN(iban, separator);
};
/**
* Convert the passed BBAN to an IBAN for this country specification.
* Please note that <i>"generation of the IBAN shall be the exclusive responsibility of the bank/branch servicing the account"</i>.
* This method implements the preferred algorithm described in http://en.wikipedia.org/wiki/International_Bank_Account_Number#Generating_IBAN_check_digits
*
* @param countryCode the country of the BBAN
* @param bban the BBAN to convert to IBAN
* @returns {string} the IBAN
* Converts the passed BBAN to an IBAN for this country specification.
* @param countryCode - The country code of the BBAN.
* @param bban - The BBAN to convert to IBAN.
* @returns The corresponding IBAN.
* @throws {Error} If the BBAN is invalid or the country code is invalid.
*/

@@ -194,6 +200,7 @@ const fromBBAN = (countryCode: string, bban: string): string => {

/**
* Check the validity of the passed BBAN.
* Checks the validity of the passed BBAN.
*
* @param countryCode the country of the BBAN
* @param bban the BBAN to check the validity of
* @param countryCode - The country code of the BBAN.
* @param bban - The BBAN to check the validity of.
* @returns True if the BBAN is valid, false otherwise.
*/

@@ -205,19 +212,15 @@ const isValidBBAN = (countryCode: string, bban: string): boolean => {

const countryStructure = countries[countryCode];
return countryStructure?.isValidBBAN(electronicFormat(bban));
return !!countryStructure && countryStructure.isValidBBAN(electronicFormat(bban));
};
/**
* Prints an IBAN in a formatted string, separating groups of characters.
*
* @param {string} iban - The IBAN to format
* @param {string} separator - The separator to use between groups (defaults to space)
* @param {number} groupSize - The size of each group of characters (defaults to 4)
* @returns {string} The formatted IBAN string
* Formats the IBAN in print format by inserting spaces every four characters.
*
* @param iban - The IBAN to format.
* @param separator - The separator to use between groups, defaults to ' '.
* @returns The formatted IBAN string.
*/
const printFormat = (iban: string, separator: string = ' ', groupSize: number = 4): string => {
const regex = new RegExp(`(.{${groupSize}})(?!$)`, 'g');
return electronicFormat(iban).replace(regex, `$1${separator}`);
};
const printFormat = (iban: string, separator: string = ' '): string =>
electronicFormat(iban).replace(EVERY_FOUR_CHARS, '$1' + separator);
export { countries, electronicFormat, fromBBAN, isValid, isValidBBAN, printFormat, toBBAN };

@@ -1,19 +0,3 @@

const ASCII_A = 'A'.charCodeAt(0);
type FormatMap = {
readonly [key: string]: string;
};
const FORMATS: FormatMap = {
A: '0-9A-Za-z',
B: '0-9A-Z',
C: 'A-Za-z',
F: '0-9',
L: 'a-z',
U: 'A-Z',
W: '0-9a-z',
} as const;
/**
* Represents a specification for validating and manipulating IBANs.
* Represents a specification for validating and manipulating IBANs (International Bank Account Numbers).
*/

@@ -23,2 +7,9 @@ export class Specification {

/**
* Creates a new instance of Specification.
* @param countryCode - The country code associated with the IBAN.
* @param length - The total length of the IBAN.
* @param structure - The structure of the underlying BBAN (Basic Bank Account Number).
* @param example - An example of a valid IBAN for this specification.
*/
constructor(

@@ -32,4 +23,3 @@ public readonly countryCode: string,

/**
* Validates an IBAN number.
*
* Checks if the given IBAN is valid according to this specification.
* @param iban - The IBAN to validate.

@@ -39,7 +29,6 @@ * @returns True if the IBAN is valid, false otherwise.

isValid(iban: string): boolean {
if (iban.length < 4) return false;
return (
this.length === iban.length &&
this.countryCode === iban.slice(0, 2) &&
this.regex.test(iban.slice(4)) &&
this._regex().test(iban.slice(4)) &&
this.iso7064Mod97_10(this.iso13616Prepare(iban)) === 1

@@ -50,33 +39,2 @@ );

/**
* Gets the cached regex for BBAN validation.
*/
private get regex(): RegExp {
if (!this._cachedRegex) {
this._cachedRegex = this.parseStructure(this.structure);
}
return this._cachedRegex;
}
/**
* Parses the BBAN structure and returns a matching regular expression.
*/
private parseStructure(structure: string): RegExp {
const blocks = structure.match(/.{3}/g) || [];
const pattern = blocks
.map((block) => {
const [format, ...repeat] = block;
const repeatCount = parseInt(repeat.join(''), 10);
if (!(format in FORMATS)) {
throw new Error(`Invalid format pattern: ${format}`);
}
return `([${FORMATS[format]}]{${repeatCount}})`;
})
.join('');
return new RegExp(`^${pattern}$`);
}
/**
* Converts the given IBAN to a country-specific BBAN.

@@ -88,5 +46,4 @@ * @param iban - The IBAN to convert.

toBBAN(iban: string, separator: string = ' '): string | undefined {
const match = this.regex?.exec(iban.slice(4));
if (!match) return undefined;
return match.slice(1).join(separator);
const match = this._regex().exec(iban.slice(4));
return match ? match.slice(1).join(separator) : undefined;
}

@@ -106,3 +63,3 @@

const prepared = this.iso13616Prepare(this.countryCode + '00' + bban);
const checkDigit = ('0' + (98 - this.iso7064Mod97_10(prepared))).slice(-2);
const checkDigit = String(98 - this.iso7064Mod97_10(prepared)).padStart(2, '0');

@@ -118,34 +75,79 @@ return this.countryCode + checkDigit + bban;

isValidBBAN(bban: string): boolean {
return this.length - 4 === bban.length && this.regex.test(bban);
return this.length - 4 === bban.length && this._regex().test(bban);
}
/**
* Prepares an IBAN for mod 97 computation as specified in ISO13616.
* Gets a lazily-loaded regex constructed based on the BBAN structure.
* @returns The regular expression for validating the BBAN.
*/
private _regex(): RegExp {
if (!this._cachedRegex) {
this._cachedRegex = this.parseStructure(this.structure);
}
return this._cachedRegex;
}
/**
* Prepares an IBAN for MOD 97-10 computation as specified in ISO 13616.
* @param iban - The IBAN to prepare.
* @returns The prepared IBAN.
* @returns The prepared numeric IBAN string.
*/
private iso13616Prepare(iban: string): string {
iban = iban.toUpperCase();
iban = iban.substr(4) + iban.substr(0, 4);
const rearranged = (iban.slice(4) + iban.slice(0, 4)).toUpperCase();
return rearranged.replace(/[A-Z]/g, (char) => (char.charCodeAt(0) - 55).toString());
}
return iban.replace(/[A-Z]/g, (match) => (match.charCodeAt(0) - ASCII_A + 10).toString());
/**
* Parses the BBAN structure and returns a matching regular expression.
* @param structure - The structure to parse.
* @returns A RegExp derived from the BBAN structure.
* @throws {Error} If the structure format is invalid.
*/
private parseStructure(structure: string): RegExp {
const formats: { [key: string]: string } = {
A: '0-9A-Za-z',
B: '0-9A-Z',
C: 'A-Za-z',
F: '0-9',
L: 'a-z',
U: 'A-Z',
W: '0-9a-z',
};
const blocks = structure.match(/.{3}/g);
if (!blocks) {
throw new Error(`Invalid structure format: ${structure}`);
}
const regexParts = blocks.map((block) => {
const pattern = block.charAt(0);
const repeats = parseInt(block.slice(1), 10);
const format = formats[pattern];
if (!format) {
throw new Error(`Invalid pattern: ${pattern}`);
}
return `([${format}]{${repeats}})`;
});
return new RegExp(`^${regexParts.join('')}$`);
}
/**
* Calculates the MOD 97 10 of the passed IBAN as specified in ISO7064.
* @param iban - The IBAN to calculate the MOD 97 10 for.
* @returns The MOD 97 10 result.
* Calculates the MOD 97-10 of the passed IBAN as specified in ISO 7064.
* @param iban - The IBAN to calculate the MOD 97-10 for.
* @returns The MOD 97-10 result.
*/
private iso7064Mod97_10(iban: string): number {
let remainder = 0;
let remainder = iban;
let block: string;
for (let i = 0; i < iban.length; i += 9) {
const num = parseInt(remainder + iban.slice(i, i + 9), 10);
if (isNaN(num)) {
throw new Error(`Invalid numeric block: ${iban.slice(i, i + 9)}`);
}
remainder = num % 97;
while (remainder.length > 2) {
block = remainder.slice(0, 9);
remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length);
}
return remainder;
return parseInt(remainder, 10) % 97;
}
}