├── .eslintrc.js
├── .github
└── workflows
│ ├── main.yaml
│ └── release.yaml
├── .gitignore
├── .prettierrc
├── LICENSE.txt
├── README.md
├── package-lock.json
├── package.json
├── release.config.js
├── src
├── bbanStructure.ts
├── bic.ts
├── bicUtil.ts
├── country.ts
├── exceptions.ts
├── iban.ts
├── ibanBuilder.ts
├── ibanUtil.ts
├── index.ts
├── randInt.ts
└── structurePart.ts
├── test
├── bic.spec.ts
├── exceptions.spec.ts
├── iban.spec.ts
└── ibanBuilder.spec.ts
├── tsconfig-cjs.json
├── tsconfig-lint.json
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parser: '@typescript-eslint/parser',
4 | plugins: ['@typescript-eslint', 'prettier'],
5 | extends: [
6 | 'airbnb-typescript/base',
7 | 'plugin:@typescript-eslint/recommended',
8 | 'plugin:import/recommended',
9 | 'prettier',
10 | ],
11 | parserOptions: {
12 | project: './tsconfig-lint.json',
13 | ecmaVersion: 2020,
14 | sourceType: 'module',
15 | },
16 | env: {
17 | es6: true,
18 | browser: true,
19 | node: true,
20 | },
21 | rules: {
22 | 'import/prefer-default-export': 'off',
23 | 'no-console': 'error',
24 | },
25 | };
--------------------------------------------------------------------------------
/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | name: Build and Test
2 |
3 | on:
4 | push:
5 | branches: [ main ]
6 | pull_request:
7 | branches: [ main ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [20.x]
16 |
17 | steps:
18 | - uses: actions/checkout@v3
19 | - name: Use Node.js ${{ matrix.node-version }}
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: ${{ matrix.node-version }}
23 | - name: Cache Node.js modules
24 | uses: actions/cache@v3
25 | with:
26 | # npm cache files are stored in `~/.npm` on Linux/macOS
27 | path: ~/.npm
28 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
29 | restore-keys: |
30 | ${{ runner.OS }}-node-
31 | ${{ runner.OS }}-
32 | - run: npm install
33 | - run: npm run build --if-present
34 | - run: npm run lint
35 | - run: npm test
36 | env:
37 | CI: true
38 | # - name: Publish to npm
39 | # uses: pascalgn/npm-publish-action@1.2.0
40 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | #push:
5 | # branches:
6 | # - main
7 |
8 | # Only run if the general build for the main branch has completed
9 | workflow_run:
10 | workflows: ["Build and Test"]
11 | branches: [main]
12 | types:
13 | - completed
14 |
15 | jobs:
16 | release:
17 | name: Release
18 | runs-on: ubuntu-latest
19 | outputs:
20 | version: ${{ steps.version.outputs.test }}
21 |
22 |
23 | steps:
24 | - name: Checkout
25 | uses: actions/checkout@v3
26 | with:
27 | fetch-depth: 0
28 | persist-credentials: false
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v3
31 | with:
32 | node-version: 20
33 | - name: Install dependencies
34 | run: npm ci
35 | - name: Install semantic-release extra plugins
36 | run: npm install --save-dev @semantic-release/changelog @semantic-release/git
37 | # - name: Lint
38 | # run: npm run lint-fix
39 | # - name: Test
40 | # run: npm run test:unit --if-present
41 | - name: Build
42 | run: npm run build
43 | - name: Release
44 | id: release
45 | env:
46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47 | NPM_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
48 | run: |
49 | npx semantic-release
50 | echo "version=$(npx semantic-release --version)" >> $GITHUB_OUTPUT
51 | - name: GitHub Release update
52 | uses: softprops/action-gh-release@v1
53 | #if: startsWith(github.ref, 'refs/tags/')
54 | with:
55 | # body_path: ${{ github.workspace }}-CHANGELOG.txt
56 | # note you'll typically need to create a personal access token
57 | # with permissions to create releases in the other repoj
58 | #token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
59 | tag_name: ${{ steps.release.outputs.version }}
60 | files: |
61 | LICENSE.md
62 | README.md
63 | lib
64 | package.json
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .sass-cache
2 | postgresql
3 | *.swp
4 | /dump.rdb
5 | /node_modules
6 | npm-debug.log
7 | .idea
8 | .DS_Store
9 | /.vagrant
10 | jsonconfig.json
11 | test.log
12 | server.log
13 | install.log
14 | dist
15 | env
16 | mochawesome-reports/
17 | tmp/*
18 | build
19 | /swagger.json
20 | /doc
21 | /coverage
22 | config/conf/local.js
23 | config/conf/local.yaml
24 | distSpec
25 | tsc.output
26 |
27 | *\.http
28 | **/pr_local_bucket
29 | **/pr_local_public
30 | **/pr_local_private
31 |
32 | # Local files
33 | .env
34 | junk/
35 | .nyc_output
36 |
37 | # Ignore
38 | config/conf/local.yaml
39 | config/conf/local.json
40 | config/conf/local.js
41 |
42 | # Ignore gnerated files
43 | /lib/
44 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | printWidth: 120
2 | trailingComma: all
3 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2018 David Koblas
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ibankit
2 |
3 | [](https://badge.fury.io/js/ibankit)
4 | [](https://github.com/koblas/ibankit-js/blob/master/LICENSE.txt)
5 |
6 | A library for generation and validation of International Bank Account Numbers
7 | (IBAN, [ISO 13616](http://en.wikipedia.org/wiki/ISO_13616)) and Business
8 | Identifier Codes (BIC, [ISO_9362](http://en.wikipedia.org/wiki/ISO_9362)).
9 |
10 | #### Key Features
11 |
12 | - Drop-in replaceable with [iban-js](https://www.npmjs.com/package/iban)
13 | - Currently conformant with Version 95 (July 2023) of the IBAN registry
14 | - Decodes bank, branch and account numbers from IBAN
15 | - Supports random BBAN / IBAN generation for testing
16 | - Has BIC validation as a bonus
17 | - Supports validation of National Check Digits if part of BBAN format
18 | - Full TypesScript support
19 | - No external dependencies
20 |
21 | ## SWIFT IBAN Registry
22 |
23 | This release should be compatible with the [SWIFT IBAN Registry
24 | Version 95](https://www.swift.com/swift-resource/9606/download). There may be a limited number
25 | of value differences, some countries in the Registry doesn't describe bank/branch information
26 | but may expose it as `3!n4!n` but leave the branch description as a blank.
27 |
28 | #### IBAN quick examples
29 |
30 | ```javascript
31 | // How to generate IBAN
32 | const ibanStr = new IBANBuilder()
33 | .countryCode(CountryCode.AT)
34 | .bankCode("19043")
35 | .accountNumber("00234573201")
36 | .build()
37 | .toString();
38 |
39 | // How to create IBAN object from String
40 | const iban = new IBAN("DE89370400440532013000");
41 |
42 | // The library ignores spaces in IBANs, this is valid
43 | const iban = new IBAN("DE89 3704 0044 0532 0130 00");
44 |
45 | // For testing, the library will also generate random IBANs
46 | const iban = IBAN.random(CountryCode.AT);
47 | const iban = IBAN.random();
48 | const iban = new IBANBuilder().countryCode(CountryCode.AT).bankCode("19043").build();
49 |
50 | // For simplicity in porting from iban-js applications
51 | // you can quickly check validity
52 | IBAN.isValid("AT611904300234573201"); // === true
53 | IBAN.isValid("DE89 3704 0044 0532 0130 00"); // == true
54 | IBAN.isValid("hello world"); // == false
55 | ```
56 |
57 | #### BIC quick examples
58 |
59 | ```typescript
60 | // How to create BIC object from String
61 | const bic = BIC("DEUTDEFF");
62 |
63 | // Check to see is BIC code is valid
64 | BIC.isValid("DEUTDEFF500"); // === true
65 | ```
66 |
67 | ### TODO
68 |
69 | - [ ] Finish writing all national check digit generators (see Oracle spec)
70 | - [x] For random IBANs the National Check digits is random, rather than "valid"
71 |
72 | #### References
73 |
74 | - http://en.wikipedia.org/wiki/ISO_13616
75 | - http://en.wikipedia.org/wiki/ISO_9362
76 | - https://www.swift.com/resource/iban-registry-pdf
77 | - https://docs.oracle.com/cd/E18727_01/doc.121/e13483/T359831T498954.htm
78 | - https://en.bitcoinwiki.org/wiki/International_Bank_Account_Number
79 | - https://github.com/globalcitizen/php-iban/issues/39
80 |
81 | #### Credits
82 |
83 | - [iban-js](https://www.npmjs.com/package/iban) by ARHS Group
84 | - [iban4j](https://github.com/arturmkrtchyan/iban4j) by Artur Mkrtchyan
85 |
86 | ## License
87 |
88 | Copyright 2018-2023 David Koblas
89 |
90 | Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
91 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ibankit",
3 | "version": "1.6.5",
4 | "description": "Validation, field extraction and creation of IBAN, BBAN, BIC numbers",
5 | "main": "./lib/cjs/index.js",
6 | "module": "./lib/esm/index.js",
7 | "types": "./lib/cjs/index.d.ts",
8 | "keywords": [
9 | "IBAN",
10 | "BBAN",
11 | "BIC",
12 | "validator",
13 | "iban-validator",
14 | "ISO 3136-1 alpha-2"
15 | ],
16 | "files": [
17 | "README.md",
18 | "LICENSE.txt",
19 | "lib",
20 | "src"
21 | ],
22 | "release": {
23 | "branches": [
24 | "main"
25 | ],
26 | "plugins": [
27 | "@semantic-release/commit-analyzer",
28 | "@semantic-release/npm",
29 | [
30 | "@semantic-release/git",
31 | {
32 | "assets": [
33 | "package.json"
34 | ],
35 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
36 | }
37 | ]
38 | ]
39 | },
40 | "scripts": {
41 | "format": "prettier --write \"src/**/*.ts\"",
42 | "lint": "eslint --ext .js,.jsx,.ts,.tsx src",
43 | "test": "jest",
44 | "build": "tsc",
45 | "prepublishOnly:esm": "tsc -p tsconfig.json",
46 | "prepublishOnly:cjs": "tsc -p tsconfig-cjs.json",
47 | "prepublishOnly": "npm run prepublishOnly:esm && npm run prepublishOnly:cjs"
48 | },
49 | "repository": {
50 | "type": "git",
51 | "url": "https://github.com/koblas/ibankit.git"
52 | },
53 | "author": "David Koblas",
54 | "license": "ISC",
55 | "homepage": "https://github.com/koblas/ibankit",
56 | "jest": {
57 | "moduleFileExtensions": [
58 | "ts",
59 | "tsx",
60 | "js"
61 | ],
62 | "transform": {
63 | "^.+\\.(ts|tsx)$": "ts-jest"
64 | },
65 | "testMatch": [
66 | "/test/.*\\.(ts|tsx|js)$",
67 | "**/__tests__/**/*.+(ts|tsx|js)",
68 | "**/?(*.)+(spec|test).+(ts|tsx|js)"
69 | ]
70 | },
71 | "devDependencies": {
72 | "@semantic-release/changelog": "^6.0.3",
73 | "@semantic-release/git": "^10.0.1",
74 | "@types/jest": "^29.5.4",
75 | "@typescript-eslint/eslint-plugin": "^6.5.0",
76 | "@typescript-eslint/parser": "^6.5.0",
77 | "eslint": "^8.48.0",
78 | "eslint-config-airbnb-base": "^15.0.0",
79 | "eslint-config-airbnb-typescript": "^17.1.0",
80 | "eslint-config-prettier": "^9.0.0",
81 | "eslint-plugin-import": "^2.28.1",
82 | "eslint-plugin-prettier": "^5.0.0",
83 | "jest": "^29.6.4",
84 | "prettier": "^3.0.3",
85 | "ts-jest": "^29.1.1",
86 | "typescript": "^5.2.2"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/release.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | branches: [
3 | 'main',
4 | ],
5 | plugins: [
6 | '@semantic-release/commit-analyzer',
7 | '@semantic-release/release-notes-generator',
8 | [
9 | '@semantic-release/changelog',
10 | {
11 | changelogFile: 'CHANGELOG.md'
12 | }
13 | ],
14 | '@semantic-release/npm',
15 | '@semantic-release/github',
16 | [
17 | '@semantic-release/git',
18 | {
19 | assets: ['CHANGELOG.md', 'dist/**'],
20 | message: 'chore(release): set `package.json` to ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}'
21 | }
22 | ]
23 | ]
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/src/bbanStructure.ts:
--------------------------------------------------------------------------------
1 | import { CharacterType, BbanStructurePart, PartType } from "./structurePart";
2 | import { CountryCode } from "./country";
3 | import { FormatException, FormatViolation, RequiredPartTypeMissing } from "./exceptions";
4 |
5 | /**
6 | * MOD11 check digit computation
7 | */
8 | function mod11(value: string, weights: number[]) {
9 | return (
10 | (11 - (value.split("").reduce((acc, s, idx) => acc + parseInt(s, 10) * weights[idx % weights.length], 0) % 11)) % 11
11 | );
12 | }
13 |
14 | function nationalES(bban: string, structure: BbanStructure) {
15 | const weights = [1, 2, 4, 8, 5, 10, 9, 7, 3, 6];
16 | const combined = [PartType.BANK_CODE, PartType.BRANCH_CODE].map((p) => structure.extractValueMust(bban, p)).join("");
17 |
18 | function to11(v: number) {
19 | if (v === 10) {
20 | return 1;
21 | } else if (v === 11) {
22 | return 0;
23 | }
24 | return v;
25 | }
26 |
27 | const d1 = to11(mod11(`00${combined}`, weights));
28 | const d2 = to11(mod11(structure.extractValueMust(bban, PartType.ACCOUNT_NUMBER), weights));
29 |
30 | return `${d1}${d2}`;
31 | }
32 |
33 | /**
34 | * France Checksum (shared)
35 | */
36 | function nationalFR(bban: string, structure: BbanStructure) {
37 | const replaceChars = {
38 | ["[AJ]"]: "1",
39 | ["[BKS]"]: "2",
40 | ["[CLT]"]: "3",
41 | ["[DMU]"]: "4",
42 | ["[ENV]"]: "5",
43 | ["[FOW]"]: "6",
44 | ["[GPX]"]: "7",
45 | ["[HQY]"]: "8",
46 | ["[IRZ]"]: "9",
47 | };
48 | let combined =
49 | [PartType.BANK_CODE, PartType.BRANCH_CODE, PartType.ACCOUNT_NUMBER]
50 | .map((p) => String(structure.extractValue(bban, p)))
51 | .join("") + "00";
52 | Object.entries(replaceChars).map(([k, v]) => (combined = combined.replace(new RegExp(k, "g"), v)));
53 |
54 | // Number is bigger than max integer, take the mod%97 by hand
55 | const expected = 97 - combined.split("").reduce((acc, v) => (acc * 10 + parseInt(v)) % 97, 0);
56 |
57 | return String(expected).padStart(2, "0");
58 | }
59 |
60 | function nationalIT(bban: string, structure: BbanStructure) {
61 | const even = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
62 | const odd = [1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 2, 4, 18, 20, 11, 3, 6, 8, 12, 14, 16, 10, 22, 25, 24, 23];
63 | const V0 = "0".charCodeAt(0);
64 | const V9 = "9".charCodeAt(0);
65 | const VA = "A".charCodeAt(0);
66 | const value =
67 | [PartType.BANK_CODE, PartType.BRANCH_CODE, PartType.ACCOUNT_NUMBER]
68 | .map((p) => structure.extractValueMust(bban, p))
69 | .join("")
70 | .split("")
71 | .map((v) => v.toUpperCase().charCodeAt(0))
72 | .map((v) => v - (V0 <= v && v <= V9 ? V0 : VA))
73 | .reduce((acc, v, idx) => acc + (idx % 2 === 0 ? odd[v] : even[v]), 0) % 26;
74 |
75 | return String.fromCharCode(VA + value);
76 | }
77 |
78 | function nationalNO(bban: string, structure: BbanStructure) {
79 | const value = [PartType.BANK_CODE, PartType.ACCOUNT_NUMBER].map((p) => structure.extractValueMust(bban, p)).join("");
80 |
81 | return String(mod11(value, [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]) % 10);
82 | }
83 |
84 | // ISO 7064 MOD 10
85 | function nationalPT(bban: string, structure: BbanStructure) {
86 | const V0 = "0".charCodeAt(0);
87 | const weights = [73, 17, 89, 38, 62, 45, 53, 15, 50, 5, 49, 34, 81, 76, 27, 90, 9, 30, 3];
88 | const remainder = [PartType.BANK_CODE, PartType.BRANCH_CODE, PartType.ACCOUNT_NUMBER]
89 | .map((p) => structure.extractValueMust(bban, p))
90 | .join("")
91 | .split("")
92 | .map((v) => v.charCodeAt(0))
93 | .reduce((acc, v, idx) => (acc + (v - V0) * weights[idx]) % 97, 0);
94 |
95 | return String(98 - remainder).padStart(2, "0");
96 | }
97 |
98 | /**
99 | * Class which represents bban structure
100 | *
101 | * Useful references --
102 | * https://www.mobilefish.com/services/bban_iban/bban_iban.php
103 | */
104 | export class BbanStructure {
105 | private static bbanFR = new BbanStructure(
106 | BbanStructurePart.bankCode(5, CharacterType.n),
107 | BbanStructurePart.branchCode(5, CharacterType.n),
108 | BbanStructurePart.accountNumber(11, CharacterType.c),
109 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n, nationalFR),
110 | );
111 |
112 | static structures: { [key in CountryCode]?: BbanStructure } = {
113 | [CountryCode.AD]: new BbanStructure(
114 | // AD2!n4!n4!n12!c
115 | BbanStructurePart.bankCode(4, CharacterType.n),
116 | BbanStructurePart.branchCode(4, CharacterType.n),
117 | BbanStructurePart.accountNumber(12, CharacterType.c),
118 | ),
119 |
120 | [CountryCode.AE]: new BbanStructure(
121 | // AE2!n3!n16!n
122 | BbanStructurePart.bankCode(3, CharacterType.n),
123 | BbanStructurePart.accountNumber(16, CharacterType.c),
124 | ),
125 |
126 | [CountryCode.AL]: new BbanStructure(
127 | BbanStructurePart.bankCode(3, CharacterType.n),
128 | BbanStructurePart.branchCode(4, CharacterType.n),
129 | BbanStructurePart.nationalCheckDigit(1, CharacterType.n),
130 | BbanStructurePart.accountNumber(16, CharacterType.c),
131 | ),
132 |
133 | // Provisional
134 | [CountryCode.AO]: new BbanStructure(BbanStructurePart.accountNumber(21, CharacterType.n)),
135 |
136 | [CountryCode.AT]: new BbanStructure(
137 | BbanStructurePart.bankCode(5, CharacterType.n),
138 | BbanStructurePart.accountNumber(11, CharacterType.n),
139 | ),
140 |
141 | [CountryCode.AZ]: new BbanStructure(
142 | BbanStructurePart.bankCode(4, CharacterType.a),
143 | BbanStructurePart.accountNumber(20, CharacterType.c),
144 | ),
145 |
146 | [CountryCode.BA]: new BbanStructure(
147 | BbanStructurePart.bankCode(3, CharacterType.n),
148 | BbanStructurePart.branchCode(3, CharacterType.n),
149 | BbanStructurePart.accountNumber(8, CharacterType.n),
150 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
151 | ),
152 |
153 | [CountryCode.BE]: new BbanStructure(
154 | BbanStructurePart.bankCode(3, CharacterType.n),
155 | BbanStructurePart.accountNumber(7, CharacterType.n),
156 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n, (bban: string, structure: BbanStructure) => {
157 | const accountNumber = structure.extractValue(bban, PartType.ACCOUNT_NUMBER);
158 | const bankCode = structure.extractValue(bban, PartType.BANK_CODE);
159 |
160 | if (accountNumber === null || bankCode === null) {
161 | throw new FormatException(FormatViolation.NOT_EMPTY, "account number or bank code missing");
162 | }
163 |
164 | const value = parseInt(`${bankCode}${accountNumber}`, 10);
165 |
166 | const remainder = Math.floor(value / 97);
167 |
168 | let expected = value - remainder * 97;
169 | if (expected === 0) {
170 | expected = 97;
171 | }
172 |
173 | return String(expected).padStart(2, "0");
174 | }),
175 | ),
176 |
177 | // Provisional
178 | [CountryCode.BF]: new BbanStructure(BbanStructurePart.accountNumber(23, CharacterType.n)),
179 |
180 | [CountryCode.BG]: new BbanStructure(
181 | BbanStructurePart.bankCode(4, CharacterType.a),
182 | BbanStructurePart.branchCode(4, CharacterType.n),
183 | BbanStructurePart.accountType(2, CharacterType.n),
184 | BbanStructurePart.accountNumber(8, CharacterType.c),
185 | ),
186 |
187 | [CountryCode.BH]: new BbanStructure(
188 | BbanStructurePart.bankCode(4, CharacterType.a),
189 | BbanStructurePart.accountNumber(14, CharacterType.c),
190 | ),
191 |
192 | // Provisional
193 | [CountryCode.BI]: new BbanStructure(
194 | // BI2!n5!n5!n11!n2!n
195 | // Changed on October 21 (from 12!n)
196 | BbanStructurePart.bankCode(5, CharacterType.n),
197 | BbanStructurePart.branchCode(5, CharacterType.n),
198 | BbanStructurePart.accountNumber(11, CharacterType.n),
199 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
200 | // BbanStructurePart.accountNumber(12, CharacterType.n),
201 | ),
202 |
203 | // Provisional
204 | [CountryCode.BJ]: new BbanStructure(
205 | BbanStructurePart.bankCode(5, CharacterType.c),
206 | BbanStructurePart.branchCode(5, CharacterType.n),
207 | BbanStructurePart.accountNumber(12, CharacterType.n),
208 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n, nationalFR),
209 | ),
210 |
211 | [CountryCode.BR]: new BbanStructure(
212 | BbanStructurePart.bankCode(8, CharacterType.n),
213 | BbanStructurePart.branchCode(5, CharacterType.n),
214 | BbanStructurePart.accountNumber(10, CharacterType.n),
215 | BbanStructurePart.accountType(1, CharacterType.a),
216 | BbanStructurePart.ownerAccountNumber(1, CharacterType.c),
217 | ),
218 |
219 | // https://www.nbrb.by/payment/ibanbic/ais-pbi_v2-7.pdf
220 | // 4c - symbolic code of the bank from the BIC directory (SI029);
221 | // 4n - balance sheet account according to the Chart of accounts of
222 | // accounting in banks and non-bank financial institutions of the
223 | // Republic of Belarus and according to the Chart of accounts of
224 | // accounting in the National Bank. Corresponds to the directory of
225 | // balance sheet accounts of RB banks (SI002) and the directory of
226 | // balance sheet accounts of the National Bank (SI001)
227 | [CountryCode.BY]: new BbanStructure(
228 | BbanStructurePart.bankCode(4, CharacterType.c),
229 | BbanStructurePart.accountType(4, CharacterType.n),
230 | BbanStructurePart.accountNumber(16, CharacterType.c),
231 | ),
232 |
233 | // Provisional
234 | [CountryCode.CF]: new BbanStructure(
235 | BbanStructurePart.accountNumber(23, CharacterType.n),
236 | // @TODO is this france?
237 | ),
238 |
239 | // Provisional
240 | [CountryCode.CG]: new BbanStructure(
241 | BbanStructurePart.accountNumber(23, CharacterType.n),
242 | // @TODO is this france?
243 | ),
244 |
245 | [CountryCode.CH]: new BbanStructure(
246 | BbanStructurePart.bankCode(5, CharacterType.n),
247 | BbanStructurePart.accountNumber(12, CharacterType.c),
248 | ),
249 |
250 | // Provisional
251 | [CountryCode.CI]: new BbanStructure(
252 | BbanStructurePart.bankCode(2, CharacterType.c),
253 | BbanStructurePart.accountNumber(22, CharacterType.n),
254 | ),
255 |
256 | // Provisional
257 | [CountryCode.CM]: new BbanStructure(BbanStructurePart.accountNumber(23, CharacterType.n)),
258 |
259 | [CountryCode.CR]: new BbanStructure(
260 | BbanStructurePart.bankCode(4, CharacterType.n),
261 | BbanStructurePart.accountNumber(14, CharacterType.n),
262 | ),
263 |
264 | // Provisional
265 | [CountryCode.CV]: new BbanStructure(BbanStructurePart.accountNumber(21, CharacterType.n)),
266 |
267 | [CountryCode.CY]: new BbanStructure(
268 | BbanStructurePart.bankCode(3, CharacterType.n),
269 | BbanStructurePart.branchCode(5, CharacterType.n),
270 | BbanStructurePart.accountNumber(16, CharacterType.c),
271 | ),
272 |
273 | // Registry defines this as 4!n6!n10!n -- but does not discuss branch information
274 | // This is improved with info from
275 | // https://www.cnb.cz/en/payments/iban/iban-international-bank-account-number-basic-information/
276 | [CountryCode.CZ]: new BbanStructure(
277 | BbanStructurePart.bankCode(4, CharacterType.n),
278 | BbanStructurePart.branchCode(6, CharacterType.n),
279 | BbanStructurePart.accountNumber(10, CharacterType.n),
280 | ),
281 |
282 | [CountryCode.DE]: new BbanStructure(
283 | BbanStructurePart.bankCode(8, CharacterType.n),
284 | BbanStructurePart.accountNumber(10, CharacterType.n),
285 | ),
286 |
287 | // Provisional
288 | [CountryCode.DJ]: new BbanStructure(
289 | // BI2!n5!n5!n11!n2!n
290 | // Changed on May 22 (from France's standard)
291 | BbanStructurePart.bankCode(5, CharacterType.n),
292 | BbanStructurePart.branchCode(5, CharacterType.n),
293 | BbanStructurePart.accountNumber(11, CharacterType.n),
294 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
295 | ),
296 |
297 | // Registry defines 4!n9!n1!n -- however no information on
298 | // nationalCheckDigit exist and all documentation discusses
299 | // that the account number is "10 digits"
300 | //
301 | // This mentions checksum
302 | // https://www.finanssiala.fi/maksujenvalitys/dokumentit/IBAN_in_payments.pdf
303 | [CountryCode.DK]: new BbanStructure(
304 | BbanStructurePart.bankCode(4, CharacterType.n),
305 | BbanStructurePart.accountNumber(10, CharacterType.n),
306 | ),
307 |
308 | [CountryCode.DO]: new BbanStructure(
309 | BbanStructurePart.bankCode(4, CharacterType.c),
310 | BbanStructurePart.accountNumber(20, CharacterType.n),
311 | ),
312 |
313 | // Provisional
314 | [CountryCode.DZ]: new BbanStructure(BbanStructurePart.accountNumber(20, CharacterType.n)),
315 |
316 | [CountryCode.EE]: new BbanStructure(
317 | BbanStructurePart.bankCode(2, CharacterType.n),
318 | BbanStructurePart.branchCode(2, CharacterType.n),
319 | BbanStructurePart.accountNumber(11, CharacterType.n),
320 | BbanStructurePart.nationalCheckDigit(1, CharacterType.n),
321 | ),
322 |
323 | [CountryCode.EG]: new BbanStructure(
324 | BbanStructurePart.bankCode(4, CharacterType.n),
325 | BbanStructurePart.branchCode(4, CharacterType.n),
326 | BbanStructurePart.accountNumber(17, CharacterType.n),
327 | ),
328 |
329 | // Spain is 4!n4!n1!n1!n10!n -- but the check digit is 2 digits?
330 | [CountryCode.ES]: new BbanStructure(
331 | BbanStructurePart.bankCode(4, CharacterType.n),
332 | BbanStructurePart.branchCode(4, CharacterType.n),
333 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n, nationalES),
334 | BbanStructurePart.accountNumber(10, CharacterType.n),
335 | ),
336 |
337 | // Additional details:
338 | // https://www.finanssiala.fi/maksujenvalitys/dokumentit/IBAN_in_payments.pdf
339 | [CountryCode.FI]: new BbanStructure(
340 | BbanStructurePart.bankCode(3, CharacterType.n),
341 | BbanStructurePart.accountNumber(11, CharacterType.n),
342 | ),
343 |
344 | [CountryCode.FK]: new BbanStructure(
345 | // Added July 23
346 | BbanStructurePart.bankCode(2, CharacterType.a),
347 | BbanStructurePart.accountNumber(12, CharacterType.n),
348 | ),
349 |
350 | [CountryCode.FO]: new BbanStructure(
351 | BbanStructurePart.bankCode(4, CharacterType.n),
352 | BbanStructurePart.accountNumber(9, CharacterType.n),
353 | BbanStructurePart.nationalCheckDigit(1, CharacterType.n),
354 | ),
355 |
356 | // FR IBAN covers:
357 | // GF, GP, MQ, RE, PF, TF, YT, NC, BL, MF, PM, WF
358 | [CountryCode.FR]: BbanStructure.bbanFR,
359 |
360 | // Provisional
361 | [CountryCode.GA]: BbanStructure.bbanFR,
362 |
363 | // GB IBAN covers:
364 | // IM, JE, GG
365 | [CountryCode.GB]: new BbanStructure(
366 | BbanStructurePart.bankCode(4, CharacterType.a),
367 | BbanStructurePart.branchCode(6, CharacterType.n),
368 | BbanStructurePart.accountNumber(8, CharacterType.n),
369 | ),
370 |
371 | [CountryCode.GE]: new BbanStructure(
372 | // Added Apr 23
373 | BbanStructurePart.bankCode(2, CharacterType.a),
374 | BbanStructurePart.accountNumber(16, CharacterType.n),
375 | ),
376 |
377 | [CountryCode.GI]: new BbanStructure(
378 | BbanStructurePart.bankCode(4, CharacterType.a),
379 | BbanStructurePart.accountNumber(15, CharacterType.c),
380 | ),
381 |
382 | // Same as DK (same issues)
383 | [CountryCode.GL]: new BbanStructure(
384 | BbanStructurePart.bankCode(4, CharacterType.n),
385 | BbanStructurePart.accountNumber(10, CharacterType.n),
386 | ),
387 |
388 | // Provisional
389 | [CountryCode.GQ]: BbanStructure.bbanFR,
390 |
391 | [CountryCode.GR]: new BbanStructure(
392 | BbanStructurePart.bankCode(3, CharacterType.n),
393 | BbanStructurePart.branchCode(4, CharacterType.n),
394 | BbanStructurePart.accountNumber(16, CharacterType.c),
395 | ),
396 |
397 | [CountryCode.GT]: new BbanStructure(
398 | BbanStructurePart.bankCode(4, CharacterType.c),
399 | BbanStructurePart.currencyType(2, CharacterType.n),
400 | BbanStructurePart.accountType(2, CharacterType.n),
401 | BbanStructurePart.accountNumber(16, CharacterType.c),
402 | ),
403 |
404 | [CountryCode.HR]: new BbanStructure(
405 | BbanStructurePart.bankCode(7, CharacterType.n),
406 | BbanStructurePart.accountNumber(10, CharacterType.n),
407 | ),
408 |
409 | // Provisional
410 | [CountryCode.HN]: new BbanStructure(
411 | BbanStructurePart.bankCode(4, CharacterType.a),
412 | BbanStructurePart.accountNumber(20, CharacterType.n),
413 | ),
414 |
415 | // Spec says account number is 1!n15!n
416 | // no information on 1!n exists -- most likely a bank/branch check digit
417 | // https://stackoverflow.com/questions/40282199/hungarian-bban-validation
418 | [CountryCode.HU]: new BbanStructure(
419 | BbanStructurePart.bankCode(3, CharacterType.n),
420 | BbanStructurePart.branchCode(4, CharacterType.n),
421 | BbanStructurePart.branchCheckDigit(1, CharacterType.n),
422 | BbanStructurePart.accountNumber(15, CharacterType.n),
423 | BbanStructurePart.nationalCheckDigit(1, CharacterType.n),
424 | ),
425 |
426 | [CountryCode.IE]: new BbanStructure(
427 | BbanStructurePart.bankCode(4, CharacterType.a),
428 | BbanStructurePart.branchCode(6, CharacterType.n),
429 | BbanStructurePart.accountNumber(8, CharacterType.n),
430 | ),
431 |
432 | [CountryCode.IL]: new BbanStructure(
433 | BbanStructurePart.bankCode(3, CharacterType.n),
434 | BbanStructurePart.branchCode(3, CharacterType.n),
435 | BbanStructurePart.accountNumber(13, CharacterType.n),
436 | ),
437 |
438 | [CountryCode.IQ]: new BbanStructure(
439 | BbanStructurePart.bankCode(4, CharacterType.a),
440 | BbanStructurePart.branchCode(3, CharacterType.n),
441 | BbanStructurePart.accountNumber(12, CharacterType.n),
442 | ),
443 |
444 | // Provisional
445 | [CountryCode.IR]: new BbanStructure(
446 | BbanStructurePart.bankCode(3, CharacterType.n),
447 | BbanStructurePart.accountNumber(19, CharacterType.n),
448 | ),
449 |
450 | [CountryCode.IS]: new BbanStructure(
451 | BbanStructurePart.bankCode(4, CharacterType.n),
452 | BbanStructurePart.branchCode(2, CharacterType.n),
453 | BbanStructurePart.accountNumber(6, CharacterType.n),
454 | BbanStructurePart.identificationNumber(10, CharacterType.n),
455 | ),
456 |
457 | [CountryCode.IT]: new BbanStructure(
458 | BbanStructurePart.nationalCheckDigit(1, CharacterType.a, nationalIT),
459 | BbanStructurePart.bankCode(5, CharacterType.n),
460 | BbanStructurePart.branchCode(5, CharacterType.n),
461 | BbanStructurePart.accountNumber(12, CharacterType.c),
462 | ),
463 |
464 | [CountryCode.JO]: new BbanStructure(
465 | BbanStructurePart.bankCode(4, CharacterType.a),
466 | BbanStructurePart.branchCode(4, CharacterType.n),
467 | BbanStructurePart.accountNumber(18, CharacterType.c),
468 | ),
469 |
470 | // Provisional
471 | [CountryCode.KM]: new BbanStructure(BbanStructurePart.accountNumber(23, CharacterType.n)),
472 |
473 | [CountryCode.KW]: new BbanStructure(
474 | BbanStructurePart.bankCode(4, CharacterType.a),
475 | BbanStructurePart.accountNumber(22, CharacterType.c),
476 | ),
477 |
478 | [CountryCode.KZ]: new BbanStructure(
479 | BbanStructurePart.bankCode(3, CharacterType.n),
480 | BbanStructurePart.accountNumber(13, CharacterType.c),
481 | ),
482 |
483 | [CountryCode.LB]: new BbanStructure(
484 | BbanStructurePart.bankCode(4, CharacterType.n),
485 | BbanStructurePart.accountNumber(20, CharacterType.c),
486 | ),
487 |
488 | [CountryCode.LC]: new BbanStructure(
489 | BbanStructurePart.bankCode(4, CharacterType.a),
490 | BbanStructurePart.accountNumber(24, CharacterType.n),
491 | ),
492 |
493 | [CountryCode.LI]: new BbanStructure(
494 | BbanStructurePart.bankCode(5, CharacterType.n),
495 | BbanStructurePart.accountNumber(12, CharacterType.c),
496 | ),
497 |
498 | [CountryCode.LT]: new BbanStructure(
499 | BbanStructurePart.bankCode(5, CharacterType.n),
500 | BbanStructurePart.accountNumber(11, CharacterType.n),
501 | ),
502 |
503 | [CountryCode.LU]: new BbanStructure(
504 | BbanStructurePart.bankCode(3, CharacterType.n),
505 | BbanStructurePart.accountNumber(13, CharacterType.c),
506 | ),
507 |
508 | [CountryCode.LV]: new BbanStructure(
509 | BbanStructurePart.bankCode(4, CharacterType.a),
510 | BbanStructurePart.accountNumber(13, CharacterType.c),
511 | ),
512 |
513 | [CountryCode.LY]: new BbanStructure(
514 | BbanStructurePart.bankCode(3, CharacterType.n),
515 | BbanStructurePart.branchCode(3, CharacterType.n),
516 | BbanStructurePart.accountNumber(15, CharacterType.n),
517 | ),
518 |
519 | // Provisional
520 | [CountryCode.MA]: new BbanStructure(BbanStructurePart.accountNumber(24, CharacterType.n)),
521 |
522 | [CountryCode.MC]: new BbanStructure(
523 | BbanStructurePart.bankCode(5, CharacterType.n),
524 | BbanStructurePart.branchCode(5, CharacterType.n),
525 | BbanStructurePart.accountNumber(11, CharacterType.c),
526 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n, nationalFR),
527 | ),
528 |
529 | [CountryCode.MD]: new BbanStructure(
530 | BbanStructurePart.bankCode(2, CharacterType.c),
531 | BbanStructurePart.accountNumber(18, CharacterType.c),
532 | ),
533 |
534 | [CountryCode.ME]: new BbanStructure(
535 | BbanStructurePart.bankCode(3, CharacterType.n),
536 | BbanStructurePart.accountNumber(13, CharacterType.n),
537 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n), // @TODO checkdigit
538 | ),
539 |
540 | // Provisional
541 | [CountryCode.MG]: new BbanStructure(
542 | BbanStructurePart.bankCode(5, CharacterType.n),
543 | BbanStructurePart.branchCode(5, CharacterType.n),
544 | BbanStructurePart.accountNumber(11, CharacterType.c),
545 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
546 | ),
547 |
548 | [CountryCode.MK]: new BbanStructure(
549 | BbanStructurePart.bankCode(3, CharacterType.n),
550 | BbanStructurePart.accountNumber(10, CharacterType.c),
551 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
552 | // @TODO checkdigit
553 | ),
554 |
555 | // Provisional
556 | [CountryCode.ML]: new BbanStructure(
557 | BbanStructurePart.bankCode(1, CharacterType.a),
558 | BbanStructurePart.accountNumber(25, CharacterType.n),
559 | ),
560 |
561 | [CountryCode.MN]: new BbanStructure(
562 | // MN2!n4!n12!n
563 | // Added April 2023
564 | BbanStructurePart.bankCode(4, CharacterType.n),
565 | BbanStructurePart.accountNumber(12, CharacterType.n),
566 | ),
567 |
568 | [CountryCode.MR]: new BbanStructure(
569 | BbanStructurePart.bankCode(5, CharacterType.n),
570 | BbanStructurePart.branchCode(5, CharacterType.n),
571 | BbanStructurePart.accountNumber(11, CharacterType.n),
572 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
573 | ),
574 |
575 | [CountryCode.MT]: new BbanStructure(
576 | BbanStructurePart.bankCode(4, CharacterType.a),
577 | BbanStructurePart.branchCode(5, CharacterType.n),
578 | BbanStructurePart.accountNumber(18, CharacterType.c),
579 | ),
580 |
581 | // Spec: 4!a2!n2!n12!n3!n3!a
582 | // No docs on the last 3!n -- assuming account type
583 | // all found IBANs have '000'
584 | [CountryCode.MU]: new BbanStructure(
585 | BbanStructurePart.bankCode(6, CharacterType.c), // 4!a2!n
586 | BbanStructurePart.branchCode(2, CharacterType.n),
587 | BbanStructurePart.accountNumber(12, CharacterType.c),
588 | BbanStructurePart.accountType(3, CharacterType.n),
589 | BbanStructurePart.currencyType(3, CharacterType.a),
590 | ),
591 |
592 | // Provisional
593 | [CountryCode.MZ]: new BbanStructure(BbanStructurePart.accountNumber(21, CharacterType.n)),
594 |
595 | // Provisional
596 | [CountryCode.NE]: new BbanStructure(
597 | BbanStructurePart.bankCode(2, CharacterType.a),
598 | BbanStructurePart.accountNumber(22, CharacterType.n),
599 | ),
600 |
601 | [CountryCode.NI]: new BbanStructure(
602 | // NI2!n4!a20!n
603 | // Added April 2023
604 | BbanStructurePart.bankCode(4, CharacterType.a),
605 | BbanStructurePart.accountNumber(20, CharacterType.n),
606 | ),
607 |
608 | [CountryCode.NL]: new BbanStructure(
609 | BbanStructurePart.bankCode(4, CharacterType.a),
610 | BbanStructurePart.accountNumber(10, CharacterType.n),
611 | ),
612 |
613 | [CountryCode.NO]: new BbanStructure(
614 | BbanStructurePart.bankCode(4, CharacterType.n),
615 | BbanStructurePart.accountNumber(6, CharacterType.n),
616 | BbanStructurePart.nationalCheckDigit(1, CharacterType.n, nationalNO),
617 | ),
618 | /**
619 | * According to the SWIFT IBAN registry, the Account Number length for Oman is specified as 16!c.
620 | * However, the Central Bank of Oman specifies it as 16!n.
621 | *
622 | * References:
623 | * - SWIFT IBAN Registry (Release 99 – Dec 2024):
624 | * https://www.swift.com/swift-resource/22851/download
625 | * - Central Bank of Oman IBAN Checker:
626 | * https://cbo.gov.om/Pages/InternationalIBANChecker.aspx
627 | */
628 | [CountryCode.OM]: new BbanStructure(
629 | BbanStructurePart.bankCode(3, CharacterType.n),
630 | BbanStructurePart.accountNumber(16, CharacterType.n),
631 | ),
632 |
633 | [CountryCode.PK]: new BbanStructure(
634 | BbanStructurePart.bankCode(4, CharacterType.c),
635 | BbanStructurePart.accountNumber(16, CharacterType.c),
636 | ),
637 |
638 | // 8!n16!n
639 | [CountryCode.PL]: new BbanStructure(
640 | BbanStructurePart.bankCode(3, CharacterType.n),
641 | BbanStructurePart.branchCode(4, CharacterType.n),
642 | BbanStructurePart.nationalCheckDigit(1, CharacterType.n),
643 | BbanStructurePart.accountNumber(16, CharacterType.n),
644 | ),
645 |
646 | [CountryCode.PS]: new BbanStructure(
647 | BbanStructurePart.bankCode(4, CharacterType.a),
648 | BbanStructurePart.accountNumber(21, CharacterType.c),
649 | ),
650 |
651 | [CountryCode.PT]: new BbanStructure(
652 | BbanStructurePart.bankCode(4, CharacterType.n),
653 | BbanStructurePart.branchCode(4, CharacterType.n),
654 | BbanStructurePart.accountNumber(11, CharacterType.n),
655 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n, nationalPT),
656 | ),
657 |
658 | [CountryCode.QA]: new BbanStructure(
659 | BbanStructurePart.bankCode(4, CharacterType.a),
660 | BbanStructurePart.accountNumber(21, CharacterType.c),
661 | ),
662 |
663 | [CountryCode.RO]: new BbanStructure(
664 | BbanStructurePart.bankCode(4, CharacterType.a),
665 | BbanStructurePart.accountNumber(16, CharacterType.c),
666 | ),
667 |
668 | [CountryCode.RS]: new BbanStructure(
669 | BbanStructurePart.bankCode(3, CharacterType.n),
670 | BbanStructurePart.accountNumber(13, CharacterType.n),
671 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
672 | ),
673 |
674 | [CountryCode.RU]: new BbanStructure(
675 | // RU2!n9!n5!n15!c
676 | // Added May 2022
677 | BbanStructurePart.bankCode(9, CharacterType.n),
678 | BbanStructurePart.branchCode(5, CharacterType.n),
679 | BbanStructurePart.accountNumber(15, CharacterType.c),
680 | ),
681 |
682 | [CountryCode.SA]: new BbanStructure(
683 | BbanStructurePart.bankCode(2, CharacterType.n),
684 | BbanStructurePart.accountNumber(18, CharacterType.c),
685 | ),
686 |
687 | [CountryCode.SC]: new BbanStructure(
688 | BbanStructurePart.bankCode(4, CharacterType.a),
689 | BbanStructurePart.branchCode(2, CharacterType.n),
690 | BbanStructurePart.branchCheckDigit(2, CharacterType.n),
691 | BbanStructurePart.accountNumber(16, CharacterType.n),
692 | BbanStructurePart.currencyType(3, CharacterType.a),
693 | ),
694 |
695 | [CountryCode.SD]: new BbanStructure(
696 | // SD2!n2!n12!n
697 | // Added October 2021
698 | BbanStructurePart.bankCode(2, CharacterType.n),
699 | BbanStructurePart.accountNumber(12, CharacterType.n),
700 | ),
701 |
702 | [CountryCode.SE]: new BbanStructure(
703 | BbanStructurePart.bankCode(3, CharacterType.n),
704 | BbanStructurePart.accountNumber(16, CharacterType.n),
705 | BbanStructurePart.nationalCheckDigit(1, CharacterType.n),
706 | ),
707 |
708 | [CountryCode.SI]: new BbanStructure(
709 | BbanStructurePart.bankCode(2, CharacterType.n),
710 | BbanStructurePart.branchCode(3, CharacterType.n),
711 | BbanStructurePart.accountNumber(8, CharacterType.n),
712 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
713 | ),
714 |
715 | [CountryCode.SK]: new BbanStructure(
716 | BbanStructurePart.bankCode(4, CharacterType.n),
717 | BbanStructurePart.accountNumber(16, CharacterType.n),
718 | ),
719 |
720 | [CountryCode.SM]: new BbanStructure(
721 | BbanStructurePart.nationalCheckDigit(1, CharacterType.a, nationalIT),
722 | BbanStructurePart.bankCode(5, CharacterType.n),
723 | BbanStructurePart.branchCode(5, CharacterType.n),
724 | BbanStructurePart.accountNumber(12, CharacterType.c),
725 | ),
726 |
727 | // Provisional
728 | [CountryCode.SN]: new BbanStructure(
729 | BbanStructurePart.bankCode(5, CharacterType.c),
730 | BbanStructurePart.branchCode(5, CharacterType.n),
731 | BbanStructurePart.accountNumber(14, CharacterType.n),
732 | ),
733 |
734 | [CountryCode.SO]: new BbanStructure(
735 | // SO2!n4!n3!n12!n
736 | // Added Feb 2023
737 | BbanStructurePart.bankCode(4, CharacterType.n),
738 | BbanStructurePart.branchCode(3, CharacterType.n),
739 | BbanStructurePart.accountNumber(12, CharacterType.n),
740 | ),
741 |
742 | [CountryCode.ST]: new BbanStructure(
743 | BbanStructurePart.bankCode(4, CharacterType.n),
744 | BbanStructurePart.branchCode(4, CharacterType.n),
745 | BbanStructurePart.accountNumber(13, CharacterType.n),
746 | ),
747 |
748 | [CountryCode.SV]: new BbanStructure(
749 | // SV2!n4!a20!n
750 | // Added March 2021
751 | BbanStructurePart.bankCode(4, CharacterType.a),
752 | BbanStructurePart.branchCode(4, CharacterType.n),
753 | BbanStructurePart.accountNumber(16, CharacterType.n),
754 | ),
755 |
756 | // Provisional
757 | [CountryCode.TG]: new BbanStructure(
758 | BbanStructurePart.bankCode(2, CharacterType.a),
759 | BbanStructurePart.accountNumber(22, CharacterType.n),
760 | ),
761 |
762 | // Provisional
763 | [CountryCode.TD]: new BbanStructure(
764 | BbanStructurePart.accountNumber(23, CharacterType.n),
765 | // @TODO is this france?
766 | ),
767 |
768 | [CountryCode.TL]: new BbanStructure(
769 | BbanStructurePart.bankCode(3, CharacterType.n),
770 | BbanStructurePart.accountNumber(14, CharacterType.n),
771 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
772 | ),
773 |
774 | [CountryCode.TN]: new BbanStructure(
775 | BbanStructurePart.bankCode(2, CharacterType.n),
776 | BbanStructurePart.branchCode(3, CharacterType.n),
777 | BbanStructurePart.accountNumber(13, CharacterType.c),
778 | BbanStructurePart.nationalCheckDigit(2, CharacterType.c),
779 | ),
780 |
781 | [CountryCode.TR]: new BbanStructure(
782 | BbanStructurePart.bankCode(5, CharacterType.n),
783 | BbanStructurePart.nationalCheckDigit(1, CharacterType.c),
784 | BbanStructurePart.accountNumber(16, CharacterType.c),
785 | ),
786 |
787 | [CountryCode.UA]: new BbanStructure(
788 | BbanStructurePart.bankCode(6, CharacterType.n),
789 | BbanStructurePart.accountNumber(19, CharacterType.n),
790 | ),
791 |
792 | [CountryCode.VA]: new BbanStructure(
793 | BbanStructurePart.bankCode(3, CharacterType.c),
794 | BbanStructurePart.accountNumber(15, CharacterType.n),
795 | ),
796 |
797 | [CountryCode.VG]: new BbanStructure(
798 | BbanStructurePart.bankCode(4, CharacterType.a),
799 | BbanStructurePart.accountNumber(16, CharacterType.n),
800 | ),
801 |
802 | [CountryCode.XK]: new BbanStructure(
803 | BbanStructurePart.bankCode(2, CharacterType.n),
804 | BbanStructurePart.branchCode(2, CharacterType.n),
805 | BbanStructurePart.accountNumber(10, CharacterType.n),
806 | BbanStructurePart.nationalCheckDigit(2, CharacterType.n),
807 | ),
808 | };
809 |
810 | private entries: BbanStructurePart[];
811 |
812 | private constructor(...entries: BbanStructurePart[]) {
813 | this.entries = entries;
814 | }
815 |
816 | getParts(): BbanStructurePart[] {
817 | return this.entries;
818 | }
819 |
820 | validate(bban: string): void {
821 | this.validateBbanLength(bban);
822 | this.validateBbanEntries(bban);
823 | }
824 |
825 | extractValue(bban: string, partType: PartType): string | null {
826 | let bbanPartOffset = 0;
827 | let result = null;
828 |
829 | for (const part of this.getParts()) {
830 | const partLength = part.getLength();
831 | const partValue = bban.substring(bbanPartOffset, bbanPartOffset + partLength);
832 |
833 | bbanPartOffset = bbanPartOffset + partLength;
834 | if (part.getPartType() == partType) {
835 | result = (result || "") + partValue;
836 | }
837 | }
838 |
839 | return result;
840 | }
841 |
842 | /**
843 | * Return part type or fail
844 | */
845 | extractValueMust(bban: string, partType: PartType): string {
846 | const value = this.extractValue(bban, partType);
847 |
848 | if (value === null) {
849 | throw new RequiredPartTypeMissing(`Required part type [${partType}] missing`);
850 | }
851 |
852 | return value;
853 | }
854 |
855 | /**
856 | * @param countryCode the country code.
857 | * @return BbanStructure for specified country or null if country is not supported.
858 | */
859 | static forCountry(countryCode: CountryCode | string | undefined): BbanStructure | null {
860 | if (!countryCode) {
861 | return null;
862 | }
863 | return this.structures[countryCode as CountryCode] || null;
864 | }
865 |
866 | static getEntries(): BbanStructure[] {
867 | return Object.values(this.structures) as BbanStructure[];
868 | }
869 |
870 | static supportedCountries(): CountryCode[] {
871 | return Object.keys(this.structures) as CountryCode[];
872 | }
873 |
874 | /**
875 | * Returns the length of bban.
876 | *
877 | * @return int length
878 | */
879 | public getBbanLength(): number {
880 | return this.entries.reduce((acc, e) => acc + e.getLength(), 0);
881 | }
882 |
883 | private validateBbanLength(bban: string) {
884 | const expectedBbanLength = this.getBbanLength();
885 | const bbanLength = bban.length;
886 |
887 | if (expectedBbanLength != bbanLength) {
888 | throw new FormatException(
889 | FormatViolation.BBAN_LENGTH,
890 | `[${bban}] length is ${bbanLength}, expected BBAN length is: ${expectedBbanLength}`,
891 | String(bbanLength),
892 | String(expectedBbanLength),
893 | );
894 | }
895 | }
896 |
897 | private validateBbanEntries(bban: string) {
898 | let offset = 0;
899 |
900 | for (const part of this.getParts()) {
901 | const partLength = part.getLength();
902 | const entryValue = bban.substring(offset, offset + partLength);
903 |
904 | offset = offset + partLength;
905 |
906 | // validate character type
907 | this.validateBbanEntryCharacterType(bban, part, entryValue);
908 | }
909 | }
910 |
911 | private validateBbanEntryCharacterType(bban: string, part: BbanStructurePart, entryValue: string) {
912 | if (!part.validate(entryValue)) {
913 | switch (part.getCharacterType()) {
914 | case CharacterType.a:
915 | throw new FormatException(
916 | FormatViolation.BBAN_ONLY_UPPER_CASE_LETTERS,
917 | `[${entryValue}] must contain only upper case letters.`,
918 | entryValue,
919 | );
920 | case CharacterType.c:
921 | throw new FormatException(
922 | FormatViolation.BBAN_ONLY_DIGITS_OR_LETTERS,
923 | `[${entryValue}] must contain only digits or letters.`,
924 | entryValue,
925 | );
926 | case CharacterType.n:
927 | throw new FormatException(
928 | FormatViolation.BBAN_ONLY_DIGITS,
929 | `[${entryValue}] must contain only digits.`,
930 | entryValue,
931 | );
932 | }
933 | }
934 |
935 | if (part.getPartType() === PartType.NATIONAL_CHECK_DIGIT && part.hasGenerator) {
936 | const expected = part.generate(bban, this);
937 |
938 | if (entryValue !== expected) {
939 | throw new FormatException(
940 | FormatViolation.NATIONAL_CHECK_DIGIT,
941 | `national check digit(s) don't match expect=[${expected}] actual=[${entryValue}]`,
942 | expected,
943 | entryValue,
944 | );
945 | }
946 | }
947 | }
948 | }
949 |
--------------------------------------------------------------------------------
/src/bic.ts:
--------------------------------------------------------------------------------
1 | import { CountryCode, countryByCode } from "./country";
2 | import * as bicUtil from "./bicUtil";
3 |
4 | /**
5 | * Business Identifier Codes (also known as SWIFT-BIC, BIC code, SWIFT ID or SWIFT code).
6 | *
7 | * ISO_9362.
8 | */
9 | export class BIC {
10 | private value: string;
11 |
12 | /**
13 | * Returns a Bic object holding the value of the specified String.
14 | *
15 | * @param bic the String to be parsed.
16 | * @return a Bic object holding the value represented by the string argument.
17 | * @throws BicFormatException if the String doesn't contain parsable Bic.
18 | * UnsupportedCountryException if bic's country is not supported.
19 | */
20 | constructor(bic: string) {
21 | bicUtil.validate(bic);
22 |
23 | this.value = bic;
24 | }
25 |
26 | /**
27 | * Returns the bank code from the Bic.
28 | *
29 | * @return string representation of Bic's institution code.
30 | */
31 | getBankCode(): string {
32 | return bicUtil.getBankCode(this.value);
33 | }
34 |
35 | /**
36 | * Returns the country code from the Bic.
37 | *
38 | * @return CountryCode representation of Bic's country code.
39 | */
40 | getCountryCode(): CountryCode | null {
41 | return countryByCode(bicUtil.getCountryCode(this.value));
42 | }
43 |
44 | /**
45 | * Returns the location code from the Bic.
46 | *
47 | * @return string representation of Bic's location code.
48 | */
49 | getLocationCode(): string {
50 | return bicUtil.getLocationCode(this.value);
51 | }
52 |
53 | /**
54 | * Returns the branch code from the Bic.
55 | *
56 | * @return string representation of Bic's branch code, null if Bic has no branch code.
57 | */
58 | getBranchCode(): string | null {
59 | if (bicUtil.hasBranchCode(this.value)) {
60 | return bicUtil.getBranchCode(this.value);
61 | }
62 | return null;
63 | }
64 |
65 | /**
66 | * override for the String() method
67 | */
68 | toString(): string {
69 | return this.value;
70 | }
71 |
72 | /**
73 | * Check to see if a BIC is valid
74 | *
75 | * @param {string} bic code to check
76 | */
77 | static isValid(bic: string): boolean {
78 | try {
79 | bicUtil.validate(bic);
80 | } catch {
81 | return false;
82 | }
83 | return true;
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/bicUtil.ts:
--------------------------------------------------------------------------------
1 | import { UnsupportedCountryException, FormatException, FormatViolation } from "./exceptions";
2 | import { countryByCode } from "./country";
3 |
4 | const BIC8_LENGTH = 8;
5 | const BIC11_LENGTH = 11;
6 |
7 | const BANK_CODE_INDEX = 0;
8 | const BANK_CODE_LENGTH = 4;
9 | const COUNTRY_CODE_INDEX = BANK_CODE_INDEX + BANK_CODE_LENGTH;
10 | const COUNTRY_CODE_LENGTH = 2;
11 | const LOCATION_CODE_INDEX = COUNTRY_CODE_INDEX + COUNTRY_CODE_LENGTH;
12 | const LOCATION_CODE_LENGTH = 2;
13 | const BRANCH_CODE_INDEX = LOCATION_CODE_INDEX + LOCATION_CODE_LENGTH;
14 | const BRANCH_CODE_LENGTH = 3;
15 |
16 | const ucRegex = /^[A-Z]+$/;
17 | const ucnumRegex = /^[A-Z0-9]+$/;
18 |
19 | export function getBankCode(bic: string): string {
20 | return bic.substring(BANK_CODE_INDEX, BANK_CODE_INDEX + BANK_CODE_LENGTH);
21 | }
22 |
23 | export function getCountryCode(bic: string): string {
24 | return bic.substring(COUNTRY_CODE_INDEX, COUNTRY_CODE_INDEX + COUNTRY_CODE_LENGTH);
25 | }
26 |
27 | export function getLocationCode(bic: string): string {
28 | return bic.substring(LOCATION_CODE_INDEX, LOCATION_CODE_INDEX + LOCATION_CODE_LENGTH);
29 | }
30 |
31 | export function getBranchCode(bic: string): string {
32 | return bic.substring(BRANCH_CODE_INDEX, BRANCH_CODE_INDEX + BRANCH_CODE_LENGTH);
33 | }
34 |
35 | export function hasBranchCode(bic: string): boolean {
36 | return bic.length === BIC11_LENGTH;
37 | }
38 |
39 | function validateEmpty(bic: string) {
40 | if (bic == null) {
41 | throw new FormatException(FormatViolation.NOT_NULL, "Null can't be a valid Bic.");
42 | }
43 |
44 | if (bic.length === 0) {
45 | throw new FormatException(FormatViolation.NOT_EMPTY, "Empty string can't be a valid Bic.");
46 | }
47 | }
48 |
49 | function validateLength(bic: string) {
50 | if (bic.length !== BIC8_LENGTH && bic.length !== BIC11_LENGTH) {
51 | throw new FormatException(
52 | FormatViolation.BIC_LENGTH_8_OR_11,
53 | `Bic length must be ${BIC8_LENGTH} or ${BIC11_LENGTH}`,
54 | );
55 | }
56 | }
57 |
58 | function validateCase(bic: string) {
59 | if (bic !== bic.toUpperCase()) {
60 | throw new FormatException(FormatViolation.BIC_ONLY_UPPER_CASE_LETTERS, "Bic must contain only upper case letters.");
61 | }
62 | }
63 |
64 | function validateBankCode(bic: string) {
65 | const bankCode = getBankCode(bic);
66 |
67 | if (!ucnumRegex.test(bankCode)) {
68 | throw new FormatException(FormatViolation.BANK_CODE_ONLY_LETTERS, "Bank code must contain only letters or digits.", bankCode);
69 | }
70 | }
71 |
72 | function validateCountryCode(bic: string) {
73 | const countryCode = getCountryCode(bic).trim();
74 |
75 | if (
76 | countryCode.length < COUNTRY_CODE_LENGTH ||
77 | countryCode !== countryCode.toUpperCase() ||
78 | !ucRegex.test(countryCode)
79 | ) {
80 | throw new FormatException(
81 | FormatViolation.COUNTRY_CODE_ONLY_UPPER_CASE_LETTERS,
82 | "Bic country code must contain upper case letters",
83 | countryCode,
84 | );
85 | }
86 |
87 | if (countryByCode(countryCode) == null) {
88 | throw new UnsupportedCountryException("Country code is not supported.", countryCode);
89 | }
90 | }
91 |
92 | function validateLocationCode(bic: string) {
93 | const locationCode = getLocationCode(bic);
94 |
95 | if (!ucnumRegex.test(locationCode)) {
96 | throw new FormatException(
97 | FormatViolation.LOCATION_CODE_ONLY_LETTERS_OR_DIGITS,
98 | "Location code must contain only letters or digits.",
99 | locationCode,
100 | );
101 | }
102 | }
103 |
104 | function validateBranchCode(bic: string) {
105 | const branchCode = getBranchCode(bic);
106 |
107 | if (!ucnumRegex.test(branchCode)) {
108 | throw new FormatException(
109 | FormatViolation.BRANCH_CODE_ONLY_LETTERS_OR_DIGITS,
110 | "Branch code must contain only letters or digits.",
111 | branchCode,
112 | );
113 | }
114 | }
115 |
116 | /**
117 | * Validates bic.
118 | *
119 | * @param bic to be validated.
120 | * @throws FormatException if bic is invalid.
121 | * UnsupportedCountryException if bic's country is not supported.
122 | */
123 | export function validate(bic: string): void {
124 | validateEmpty(bic);
125 | validateLength(bic);
126 | validateCase(bic);
127 | validateBankCode(bic);
128 | validateCountryCode(bic);
129 | validateLocationCode(bic);
130 |
131 | if (hasBranchCode(bic)) {
132 | validateBranchCode(bic);
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/country.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Country Code Enum
3 | *
4 | * ISO 3166-1 country code.
5 | */
6 |
7 | export enum CountryCode {
8 | AD = "AD",
9 | AE = "AE",
10 | AF = "AF",
11 | AG = "AG",
12 | AI = "AI",
13 | AL = "AL",
14 | AM = "AM",
15 | AO = "AO",
16 | AQ = "AQ",
17 | AR = "AR",
18 | AS = "AS",
19 | AT = "AT",
20 | AU = "AU",
21 | AW = "AW",
22 | AX = "AX",
23 | AZ = "AZ",
24 | BA = "BA",
25 | BB = "BB",
26 | BD = "BD",
27 | BE = "BE",
28 | BF = "BF",
29 | BG = "BG",
30 | BH = "BH",
31 | BI = "BI",
32 | BJ = "BJ",
33 | BL = "BL",
34 | BM = "BM",
35 | BN = "BN",
36 | BO = "BO",
37 | BQ = "BQ",
38 | BR = "BR",
39 | BS = "BS",
40 | BT = "BT",
41 | BV = "BV",
42 | BW = "BW",
43 | BY = "BY",
44 | BZ = "BZ",
45 | CA = "CA",
46 | CC = "CC",
47 | CD = "CD",
48 | CF = "CF",
49 | CG = "CG",
50 | CH = "CH",
51 | CI = "CI",
52 | CK = "CK",
53 | CL = "CL",
54 | CM = "CM",
55 | CN = "CN",
56 | CO = "CO",
57 | CR = "CR",
58 | CU = "CU",
59 | CV = "CV",
60 | CW = "CW",
61 | CX = "CX",
62 | CY = "CY",
63 | CZ = "CZ",
64 | DE = "DE",
65 | DJ = "DJ",
66 | DK = "DK",
67 | DM = "DM",
68 | DO = "DO",
69 | DZ = "DZ",
70 | EC = "EC",
71 | EE = "EE",
72 | EG = "EG",
73 | EH = "EH",
74 | ER = "ER",
75 | ES = "ES",
76 | ET = "ET",
77 | FI = "FI",
78 | FJ = "FJ",
79 | FK = "FK",
80 | FM = "FM",
81 | FO = "FO",
82 | FR = "FR",
83 | GA = "GA",
84 | GB = "GB",
85 | GD = "GD",
86 | GE = "GE",
87 | GF = "GF",
88 | GG = "GG",
89 | GH = "GH",
90 | GI = "GI",
91 | GL = "GL",
92 | GM = "GM",
93 | GN = "GN",
94 | GP = "GP",
95 | GQ = "GQ",
96 | GR = "GR",
97 | GS = "GS",
98 | GT = "GT",
99 | GU = "GU",
100 | GW = "GW",
101 | GY = "GY",
102 | HK = "HK",
103 | HM = "HM",
104 | HN = "HN",
105 | HR = "HR",
106 | HT = "HT",
107 | HU = "HU",
108 | ID = "ID",
109 | IE = "IE",
110 | IL = "IL",
111 | IM = "IM",
112 | IN = "IN",
113 | IO = "IO",
114 | IQ = "IQ",
115 | IR = "IR",
116 | IS = "IS",
117 | IT = "IT",
118 | JE = "JE",
119 | JM = "JM",
120 | JO = "JO",
121 | JP = "JP",
122 | KE = "KE",
123 | KG = "KG",
124 | KH = "KH",
125 | KI = "KI",
126 | KM = "KM",
127 | KN = "KN",
128 | KP = "KP",
129 | KR = "KR",
130 | KW = "KW",
131 | KY = "KY",
132 | KZ = "KZ",
133 | LA = "LA",
134 | LB = "LB",
135 | LC = "LC",
136 | LI = "LI",
137 | LK = "LK",
138 | LR = "LR",
139 | LS = "LS",
140 | LT = "LT",
141 | LU = "LU",
142 | LV = "LV",
143 | LY = "LY",
144 | MA = "MA",
145 | MC = "MC",
146 | MD = "MD",
147 | ME = "ME",
148 | MF = "MF",
149 | MG = "MG",
150 | MH = "MH",
151 | MK = "MK",
152 | ML = "ML",
153 | MM = "MM",
154 | MN = "MN",
155 | MO = "MO",
156 | MP = "MP",
157 | MQ = "MQ",
158 | MR = "MR",
159 | MS = "MS",
160 | MT = "MT",
161 | MU = "MU",
162 | MV = "MV",
163 | MW = "MW",
164 | MX = "MX",
165 | MY = "MY",
166 | MZ = "MZ",
167 | NA = "NA",
168 | NC = "NC",
169 | NE = "NE",
170 | NF = "NF",
171 | NG = "NG",
172 | NI = "NI",
173 | NL = "NL",
174 | NO = "NO",
175 | NP = "NP",
176 | NR = "NR",
177 | NU = "NU",
178 | NZ = "NZ",
179 | OM = "OM",
180 | PA = "PA",
181 | PE = "PE",
182 | PF = "PF",
183 | PG = "PG",
184 | PH = "PH",
185 | PK = "PK",
186 | PL = "PL",
187 | PM = "PM",
188 | PN = "PN",
189 | PR = "PR",
190 | PS = "PS",
191 | PT = "PT",
192 | PW = "PW",
193 | PY = "PY",
194 | QA = "QA",
195 | RE = "RE",
196 | RO = "RO",
197 | RS = "RS",
198 | RU = "RU",
199 | RW = "RW",
200 | SA = "SA",
201 | SB = "SB",
202 | SC = "SC",
203 | SD = "SD",
204 | SE = "SE",
205 | SG = "SG",
206 | SH = "SH",
207 | SI = "SI",
208 | SJ = "SJ",
209 | SK = "SK",
210 | SL = "SL",
211 | SM = "SM",
212 | SN = "SN",
213 | SO = "SO",
214 | SR = "SR",
215 | SS = "SS",
216 | ST = "ST",
217 | SV = "SV",
218 | SX = "SX",
219 | SY = "SY",
220 | SZ = "SZ",
221 | TC = "TC",
222 | TD = "TD",
223 | TF = "TF",
224 | TG = "TG",
225 | TH = "TH",
226 | TJ = "TJ",
227 | TK = "TK",
228 | TL = "TL",
229 | TM = "TM",
230 | TN = "TN",
231 | TO = "TO",
232 | TR = "TR",
233 | TT = "TT",
234 | TV = "TV",
235 | TW = "TW",
236 | TZ = "TZ",
237 | UA = "UA",
238 | UG = "UG",
239 | UM = "UM",
240 | US = "US",
241 | UY = "UY",
242 | UZ = "UZ",
243 | VA = "VA",
244 | VC = "VC",
245 | VE = "VE",
246 | VG = "VG",
247 | VI = "VI",
248 | VN = "VN",
249 | VU = "VU",
250 | WF = "WF",
251 | WS = "WS",
252 | XK = "XK",
253 | YE = "YE",
254 | YT = "YT",
255 | ZA = "ZA",
256 | ZM = "ZM",
257 | ZW = "ZW",
258 | }
259 |
260 | const countryData: Record = {
261 | [CountryCode.AD]: ["Andorra", "AND"],
262 | [CountryCode.AE]: ["United Arab Emirates", "ARE"],
263 | [CountryCode.AF]: ["Afghanistan", "AFG"],
264 | [CountryCode.AG]: ["Antigua and Barbuda", "ATG"],
265 | [CountryCode.AI]: ["Anguilla", "AIA"],
266 | [CountryCode.AL]: ["Albania", "ALB"],
267 | [CountryCode.AM]: ["Armenia", "ARM"],
268 | [CountryCode.AO]: ["Angola", "AGO"],
269 | [CountryCode.AQ]: ["Antarctica", "ATA"],
270 | [CountryCode.AR]: ["Argentina", "ARG"],
271 | [CountryCode.AS]: ["American Samoa", "ASM"],
272 | [CountryCode.AT]: ["Austria", "AUT"],
273 | [CountryCode.AU]: ["Australia", "AUS"],
274 | [CountryCode.AW]: ["Aruba", "ABW"],
275 | [CountryCode.AX]: ["\u212Bland Islands", "ALA"],
276 | [CountryCode.AZ]: ["Azerbaijan", "AZE"],
277 | [CountryCode.BA]: ["Bosnia and Herzegovina", "BIH"],
278 | [CountryCode.BB]: ["Barbados", "BRB"],
279 | [CountryCode.BD]: ["Bangladesh", "BGD"],
280 | [CountryCode.BE]: ["Belgium", "BEL"],
281 | [CountryCode.BF]: ["Burkina Faso", "BFA"],
282 | [CountryCode.BG]: ["Bulgaria", "BGR"],
283 | [CountryCode.BH]: ["Bahrain", "BHR"],
284 | [CountryCode.BI]: ["Burundi", "BDI"],
285 | [CountryCode.BJ]: ["Benin", "BEN"],
286 | [CountryCode.BL]: ["Saint Barth\u00E9lemy", "BLM"],
287 | [CountryCode.BM]: ["Bermuda", "BMU"],
288 | [CountryCode.BN]: ["Brunei Darussalam", "BRN"],
289 | [CountryCode.BO]: ["Plurinational State of Bolivia", "BOL"],
290 | [CountryCode.BQ]: ["Bonaire, Sint Eustatius and Saba", "BES"],
291 | [CountryCode.BR]: ["Brazil", "BRA"],
292 | [CountryCode.BS]: ["Bahamas", "BHS"],
293 | [CountryCode.BT]: ["Bhutan", "BTN"],
294 | [CountryCode.BV]: ["Bouvet Island", "BVT"],
295 | [CountryCode.BW]: ["Botswana", "BWA"],
296 | [CountryCode.BY]: ["Belarus", "BLR"],
297 | [CountryCode.BZ]: ["Belize", "BLZ"],
298 | [CountryCode.CA]: ["Canada", "CAN"],
299 | [CountryCode.CC]: ["Cocos Islands", "CCK"],
300 | [CountryCode.CD]: ["The Democratic Republic of the Congo", "COD"],
301 | [CountryCode.CF]: ["Central African Republic", "CAF"],
302 | [CountryCode.CG]: ["Congo", "COG"],
303 | [CountryCode.CH]: ["Switzerland", "CHE"],
304 | [CountryCode.CI]: ["C\u00F4te d'Ivoire", "CIV"],
305 | [CountryCode.CK]: ["Cook Islands", "COK"],
306 | [CountryCode.CL]: ["Chile", "CHL"],
307 | [CountryCode.CM]: ["Cameroon", "CMR"],
308 | [CountryCode.CN]: ["China", "CHN"],
309 | [CountryCode.CO]: ["Colombia", "COL"],
310 | [CountryCode.CR]: ["Costa Rica", "CRI"],
311 | [CountryCode.CU]: ["Cuba", "CUB"],
312 | [CountryCode.CV]: ["Cape Verde", "CPV"],
313 | [CountryCode.CW]: ["Cura\u00E7ao", "CUW"],
314 | [CountryCode.CX]: ["Christmas Island", "CXR"],
315 | [CountryCode.CY]: ["Cyprus", "CYP"],
316 | [CountryCode.CZ]: ["Czech Republic", "CZE"],
317 | [CountryCode.DE]: ["Germany", "DEU"],
318 | [CountryCode.DJ]: ["Djibouti", "DJI"],
319 | [CountryCode.DK]: ["Denmark", "DNK"],
320 | [CountryCode.DM]: ["Dominica", "DMA"],
321 | [CountryCode.DO]: ["Dominican Republic", "DOM"],
322 | [CountryCode.DZ]: ["Algeria", "DZA"],
323 | [CountryCode.EC]: ["Ecuador", "ECU"],
324 | [CountryCode.EE]: ["Estonia", "EST"],
325 | [CountryCode.EG]: ["Egypt", "EGY"],
326 | [CountryCode.EH]: ["Western Sahara", "ESH"],
327 | [CountryCode.ER]: ["Eritrea", "ERI"],
328 | [CountryCode.ES]: ["Spain", "ESP"],
329 | [CountryCode.ET]: ["Ethiopia", "ETH"],
330 | [CountryCode.FI]: ["Finland", "FIN"],
331 | [CountryCode.FJ]: ["Fiji", "FJI"],
332 | [CountryCode.FK]: ["Falkland Islands", "FLK"],
333 | [CountryCode.FM]: ["Federated States of Micronesia", "FSM"],
334 | [CountryCode.FO]: ["Faroe Islands", "FRO"],
335 | [CountryCode.FR]: ["France", "FRA"],
336 | [CountryCode.GA]: ["Gabon", "GAB"],
337 | [CountryCode.GB]: ["United Kingdom", "GBR"],
338 | [CountryCode.GD]: ["Grenada", "GRD"],
339 | [CountryCode.GE]: ["Georgia", "GEO"],
340 | [CountryCode.GF]: ["French Guiana", "GUF"],
341 | [CountryCode.GG]: ["Guemsey", "GGY"],
342 | [CountryCode.GH]: ["Ghana", "GHA"],
343 | [CountryCode.GI]: ["Gibraltar", "GIB"],
344 | [CountryCode.GL]: ["Greenland", "GRL"],
345 | [CountryCode.GM]: ["Gambia", "GMB"],
346 | [CountryCode.GN]: ["Guinea", "GIN"],
347 | [CountryCode.GP]: ["Guadeloupe", "GLP"],
348 | [CountryCode.GQ]: ["Equatorial Guinea", "GNQ"],
349 | [CountryCode.GR]: ["Greece", "GRC"],
350 | [CountryCode.GS]: ["South Georgia and the South Sandwich Islands", "SGS"],
351 | [CountryCode.GT]: ["Guatemala", "GTM"],
352 | [CountryCode.GU]: ["Guam", "GUM"],
353 | [CountryCode.GW]: ["Guinea-Bissau", "GNB"],
354 | [CountryCode.GY]: ["Guyana", "GUY"],
355 | [CountryCode.HK]: ["Hong Kong", "HKG"],
356 | [CountryCode.HM]: ["Heard Island and McDonald Islands", "HMD"],
357 | [CountryCode.HN]: ["Honduras", "HND"],
358 | [CountryCode.HR]: ["Croatia", "HRV"],
359 | [CountryCode.HT]: ["Haiti", "HTI"],
360 | [CountryCode.HU]: ["Hungary", "HUN"],
361 | [CountryCode.ID]: ["Indonesia", "IDN"],
362 | [CountryCode.IE]: ["Ireland", "IRL"],
363 | [CountryCode.IL]: ["Israel", "ISR"],
364 | [CountryCode.IM]: ["Isle of Man", "IMN"],
365 | [CountryCode.IN]: ["India", "IND"],
366 | [CountryCode.IO]: ["British Indian Ocean Territory", "IOT"],
367 | [CountryCode.IQ]: ["Iraq", "IRQ"],
368 | [CountryCode.IR]: ["Islamic Republic of Iran", "IRN"],
369 | [CountryCode.IS]: ["Iceland", "ISL"],
370 | [CountryCode.IT]: ["Italy", "ITA"],
371 | [CountryCode.JE]: ["Jersey", "JEY"],
372 | [CountryCode.JM]: ["Jamaica", "JAM"],
373 | [CountryCode.JO]: ["Jordan", "JOR"],
374 | [CountryCode.JP]: ["Japan", "JPN"],
375 | [CountryCode.KE]: ["Kenya", "KEN"],
376 | [CountryCode.KG]: ["Kyrgyzstan", "KGZ"],
377 | [CountryCode.KH]: ["Cambodia", "KHM"],
378 | [CountryCode.KI]: ["Kiribati", "KIR"],
379 | [CountryCode.KM]: ["Comoros", "COM"],
380 | [CountryCode.KN]: ["Saint Kitts and Nevis", "KNA"],
381 | [CountryCode.KP]: ["Democratic People's Republic of Korea", "PRK"],
382 | [CountryCode.KR]: ["Republic of Korea", "KOR"],
383 | [CountryCode.KW]: ["Kuwait", "KWT"],
384 | [CountryCode.KY]: ["Cayman Islands", "CYM"],
385 | [CountryCode.KZ]: ["Kazakhstan", "KAZ"],
386 | [CountryCode.LA]: ["Lao People's Democratic Republic", "LAO"],
387 | [CountryCode.LB]: ["Lebanon", "LBN"],
388 | [CountryCode.LC]: ["Saint Lucia", "LCA"],
389 | [CountryCode.LI]: ["Liechtenstein", "LIE"],
390 | [CountryCode.LK]: ["Sri Lanka", "LKA"],
391 | [CountryCode.LR]: ["Liberia", "LBR"],
392 | [CountryCode.LS]: ["Lesotho", "LSO"],
393 | [CountryCode.LT]: ["Lithuania", "LTU"],
394 | [CountryCode.LU]: ["Luxembourg", "LUX"],
395 | [CountryCode.LV]: ["Latvia", "LVA"],
396 | [CountryCode.LY]: ["Libya", "LBY"],
397 | [CountryCode.MA]: ["Morocco", "MAR"],
398 | [CountryCode.MC]: ["Monaco", "MCO"],
399 | [CountryCode.MD]: ["Republic of Moldova", "MDA"],
400 | [CountryCode.ME]: ["Montenegro", "MNE"],
401 | [CountryCode.MF]: ["Saint Martin", "MAF"],
402 | [CountryCode.MG]: ["Madagascar", "MDG"],
403 | [CountryCode.MH]: ["Marshall Islands", "MHL"],
404 | [CountryCode.MK]: ["The former Yugoslav Republic of Macedonia", "MKD"],
405 | [CountryCode.ML]: ["Mali", "MLI"],
406 | [CountryCode.MM]: ["Myanmar", "MMR"],
407 | [CountryCode.MN]: ["Mongolia", "MNG"],
408 | [CountryCode.MO]: ["Macao", "MAC"],
409 | [CountryCode.MP]: ["Northern Mariana Islands", "MNP"],
410 | [CountryCode.MQ]: ["Martinique", "MTQ"],
411 | [CountryCode.MR]: ["Mauritania", "MRT"],
412 | [CountryCode.MS]: ["Montserrat", "MSR"],
413 | [CountryCode.MT]: ["Malta", "MLT"],
414 | [CountryCode.MU]: ["Mauritius", "MUS"],
415 | [CountryCode.MV]: ["Maldives", "MDV"],
416 | [CountryCode.MW]: ["Malawi", "MWI"],
417 | [CountryCode.MX]: ["Mexico", "MEX"],
418 | [CountryCode.MY]: ["Malaysia", "MYS"],
419 | [CountryCode.MZ]: ["Mozambique", "MOZ"],
420 | [CountryCode.NA]: ["Namibia", "NAM"],
421 | [CountryCode.NC]: ["New Caledonia", "NCL"],
422 | [CountryCode.NE]: ["Niger", "NER"],
423 | [CountryCode.NF]: ["Norfolk Island", "NFK"],
424 | [CountryCode.NG]: ["Nigeria", "NGA"],
425 | [CountryCode.NI]: ["Nicaragua", "NIC"],
426 | [CountryCode.NL]: ["Netherlands", "NLD"],
427 | [CountryCode.NO]: ["Norway", "NOR"],
428 | [CountryCode.NP]: ["Nepal", "NPL"],
429 | [CountryCode.NR]: ["Nauru", "NRU"],
430 | [CountryCode.NU]: ["Niue", "NIU"],
431 | [CountryCode.NZ]: ["New Zealand", "NZL"],
432 | [CountryCode.OM]: ["Oman", "OMN"],
433 | [CountryCode.PA]: ["Panama", "PAN"],
434 | [CountryCode.PE]: ["Peru", "PER"],
435 | [CountryCode.PF]: ["French Polynesia", "PYF"],
436 | [CountryCode.PG]: ["Papua New Guinea", "PNG"],
437 | [CountryCode.PH]: ["Philippines", "PHL"],
438 | [CountryCode.PK]: ["Pakistan", "PAK"],
439 | [CountryCode.PL]: ["Poland", "POL"],
440 | [CountryCode.PM]: ["Saint Pierre and Miquelon", "SPM"],
441 | [CountryCode.PN]: ["Pitcairn", "PCN"],
442 | [CountryCode.PR]: ["Puerto Rico", "PRI"],
443 | [CountryCode.PS]: ["Occupied Palestinian Territory", "PSE"],
444 | [CountryCode.PT]: ["Portugal", "PRT"],
445 | [CountryCode.PW]: ["Palau", "PLW"],
446 | [CountryCode.PY]: ["Paraguay", "PRY"],
447 | [CountryCode.QA]: ["Qatar", "QAT"],
448 | [CountryCode.RE]: ["R\u00E9union", "REU"],
449 | [CountryCode.RO]: ["Romania", "ROU"],
450 | [CountryCode.RS]: ["Serbia", "SRB"],
451 | [CountryCode.RU]: ["Russian Federation", "RUS"],
452 | [CountryCode.RW]: ["Rwanda", "RWA"],
453 | [CountryCode.SA]: ["Saudi Arabia", "SAU"],
454 | [CountryCode.SB]: ["Solomon Islands", "SLB"],
455 | [CountryCode.SC]: ["Seychelles", "SYC"],
456 | [CountryCode.SD]: ["Sudan", "SDN"],
457 | [CountryCode.SE]: ["Sweden", "SWE"],
458 | [CountryCode.SG]: ["Singapore", "SGP"],
459 | [CountryCode.SH]: ["Saint Helena, Ascension and Tristan da Cunha", "SHN"],
460 | [CountryCode.SI]: ["Slovenia", "SVN"],
461 | [CountryCode.SJ]: ["Svalbard and Jan Mayen", "SJM"],
462 | [CountryCode.SK]: ["Slovakia", "SVK"],
463 | [CountryCode.SL]: ["Sierra Leone", "SLE"],
464 | [CountryCode.SM]: ["San Marino", "SMR"],
465 | [CountryCode.SN]: ["Senegal", "SEN"],
466 | [CountryCode.SO]: ["Somalia", "SOM"],
467 | [CountryCode.SR]: ["Suriname", "SUR"],
468 | [CountryCode.SS]: ["South Sudan", "SSD"],
469 | [CountryCode.ST]: ["Sao Tome and Principe", "STP"],
470 | [CountryCode.SV]: ["El Salvador", "SLV"],
471 | [CountryCode.SX]: ["Sint Maarten", "SXM"],
472 | [CountryCode.SY]: ["Syrian Arab Republic", "SYR"],
473 | [CountryCode.SZ]: ["Swaziland", "SWZ"],
474 | [CountryCode.TC]: ["Turks and Caicos Islands", "TCA"],
475 | [CountryCode.TD]: ["Chad", "TCD"],
476 | [CountryCode.TF]: ["French Southern Territories", "ATF"],
477 | [CountryCode.TG]: ["Togo", "TGO"],
478 | [CountryCode.TH]: ["Thailand", "THA"],
479 | [CountryCode.TJ]: ["Tajikistan", "TJK"],
480 | [CountryCode.TK]: ["Tokelau", "TKL"],
481 | [CountryCode.TL]: ["Timor-Leste", "TLS"],
482 | [CountryCode.TM]: ["Turkmenistan", "TKM"],
483 | [CountryCode.TN]: ["Tunisia", "TUN"],
484 | [CountryCode.TO]: ["Tonga", "TON"],
485 | [CountryCode.TR]: ["Turkey", "TUR"],
486 | [CountryCode.TT]: ["Trinidad and Tobago", "TTO"],
487 | [CountryCode.TV]: ["Tuvalu", "TUV"],
488 | [CountryCode.TW]: ["Taiwan, Province of China", "TWN"],
489 | [CountryCode.TZ]: ["United Republic of Tanzania", "TZA"],
490 | [CountryCode.UA]: ["Ukraine", "UKR"],
491 | [CountryCode.UG]: ["Uganda", "UGA"],
492 | [CountryCode.UM]: ["United States Minor Outlying Islands", "UMI"],
493 | [CountryCode.US]: ["United States", "USA"],
494 | [CountryCode.UY]: ["Uruguay", "URY"],
495 | [CountryCode.UZ]: ["Uzbekistan", "UZB"],
496 | [CountryCode.VA]: ["Holy See", "VAT"],
497 | [CountryCode.VC]: ["Saint Vincent and the Grenadines", "VCT"],
498 | [CountryCode.VE]: ["Bolivarian Republic of Venezuela", "VEN"],
499 | [CountryCode.VG]: ["British Virgin Islands", "VGB"],
500 | [CountryCode.VI]: ["Virgin Islands, U.S.", "VIR"],
501 | [CountryCode.VN]: ["Viet Nam", "VNM"],
502 | [CountryCode.VU]: ["Vanuatu", "VUT"],
503 | [CountryCode.WF]: ["Wallis and Futuna", "WLF"],
504 | [CountryCode.WS]: ["Samoa", "WSM"],
505 | [CountryCode.XK]: ["Kosovo", "UNK"],
506 | [CountryCode.YE]: ["Yemen", "YEM"],
507 | [CountryCode.YT]: ["Mayotte", "MYT"],
508 | [CountryCode.ZA]: ["South Africa", "ZAF"],
509 | [CountryCode.ZM]: ["Zambia", "ZMB"],
510 | [CountryCode.ZW]: ["Zimbabwe", "ZWE"],
511 | };
512 |
513 | const by2code = Object.entries(countryData).reduce(
514 | (acc, [k, v]) => {
515 | acc[k] = [k as CountryCode, v];
516 | return acc;
517 | },
518 | {} as Record,
519 | );
520 |
521 | const by3code = Object.entries(countryData).reduce(
522 | (acc, [k, v]) => {
523 | acc[v[1]] = [k as CountryCode, v];
524 | return acc;
525 | },
526 | {} as Record,
527 | );
528 |
529 | export function countryByCode(code: string): CountryCode | null {
530 | if (code === null) {
531 | return null;
532 | }
533 |
534 | let info;
535 | if (code.length === 3) {
536 | info = by3code[code];
537 | } else if (code.length === 2) {
538 | info = by2code[code];
539 | }
540 | if (info) {
541 | return info[0];
542 | }
543 |
544 | return null;
545 | }
546 |
--------------------------------------------------------------------------------
/src/exceptions.ts:
--------------------------------------------------------------------------------
1 | export enum FormatViolation {
2 | UNKNOWN,
3 |
4 | NOT_NULL,
5 | NOT_EMPTY,
6 | BIC_LENGTH_8_OR_11,
7 | BIC_ONLY_UPPER_CASE_LETTERS,
8 |
9 | // BIC Validation
10 | BRANCH_CODE_ONLY_LETTERS_OR_DIGITS,
11 | LOCATION_CODE_ONLY_LETTERS_OR_DIGITS,
12 | BANK_CODE_ONLY_LETTERS,
13 |
14 | COUNTRY_CODE_TWO_LETTERS,
15 | COUNTRY_CODE_ONLY_UPPER_CASE_LETTERS,
16 | COUNTRY_CODE_EXISTS,
17 |
18 | NATIONAL_CHECK_DIGIT,
19 |
20 | // IBAN Specific
21 | CHECK_DIGIT_TWO_DIGITS,
22 | CHECK_DIGIT_ONLY_DIGITS,
23 | BBAN_LENGTH,
24 | BBAN_ONLY_UPPER_CASE_LETTERS,
25 | BBAN_ONLY_DIGITS_OR_LETTERS,
26 | BBAN_ONLY_DIGITS,
27 | IBAN_VALID_CHARACTERS,
28 |
29 | // IbanBuilder
30 | COUNTRY_CODE_NOT_NULL,
31 | BANK_CODE_NOT_NULL,
32 | ACCOUNT_NUMBER_NOT_NULL,
33 | }
34 |
35 | export class FormatException extends Error {
36 | formatViolation: FormatViolation;
37 |
38 | actual?: string;
39 |
40 | expected?: string;
41 |
42 | constructor(formatViolation: FormatViolation, msg: string, expected?: string, actual?: string) {
43 | super(msg);
44 |
45 | this.formatViolation = formatViolation;
46 | this.expected = expected;
47 | this.actual = actual;
48 |
49 | // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
50 | Object.setPrototypeOf(this, FormatException.prototype);
51 | }
52 | }
53 |
54 | export class UnsupportedCountryException extends Error {
55 | actual?: string;
56 |
57 | constructor(msg: string, actual?: string) {
58 | super(msg);
59 | this.actual = actual;
60 |
61 | // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
62 | Object.setPrototypeOf(this, UnsupportedCountryException.prototype);
63 | }
64 | }
65 |
66 | export class InvalidCheckDigitException extends Error {
67 | actual?: string;
68 |
69 | expected?: string;
70 |
71 | constructor(msg: string, expected?: string, actual?: string) {
72 | super(msg);
73 |
74 | this.expected = expected;
75 | this.actual = actual;
76 |
77 | // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
78 | Object.setPrototypeOf(this, InvalidCheckDigitException.prototype);
79 | }
80 | }
81 |
82 | export class RequiredPartTypeMissing extends Error {
83 | constructor(msg: string) {
84 | super(msg);
85 |
86 | // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
87 | Object.setPrototypeOf(this, RequiredPartTypeMissing.prototype);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/iban.ts:
--------------------------------------------------------------------------------
1 | import * as ibanUtil from "./ibanUtil";
2 | import { countryByCode, CountryCode } from "./country";
3 | import { IBANBuilder } from "./ibanBuilder";
4 |
5 | // Some useful RegEx-s
6 | const NON_ALPHANUM = /[^a-z0-9]/ig;
7 |
8 | const samples: Record = {
9 | AD: "AD1200012030200359100100",
10 | AE: "AE070331234567890123456",
11 | AL: "AL47212110090000000235698741",
12 | AT: "AT611904300234573201",
13 | AZ: "AZ21NABZ00000000137010001944",
14 | BA: "BA391990440001200279",
15 | BE: "BE68539007547034",
16 | BG: "BG80BNBG96611020345678",
17 | BH: "BH67BMAG00001299123456",
18 | BR: "BR9700360305000010009795493P1",
19 | BY: "BY13NBRB3600900000002Z00AB00",
20 | CH: "CH9300762011623852957",
21 | CR: "CR05015202001026284066",
22 | CY: "CY17002001280000001200527600",
23 | CZ: "CZ6508000000192000145399",
24 | DE: "DE89370400440532013000",
25 | DK: "DK5000400440116243",
26 | DO: "DO28BAGR00000001212453611324",
27 | EE: "EE382200221020145685",
28 | ES: "ES9121000418450200051332",
29 | FI: "FI2112345600000785",
30 | FO: "FO6264600001631634",
31 | FR: "FR1420041010050500013M02606",
32 | GB: "GB29NWBK60161331926819",
33 | GE: "GE29NB0000000101904917",
34 | GI: "GI75NWBK000000007099453",
35 | GL: "GL8964710001000206",
36 | GR: "GR1601101250000000012300695",
37 | GT: "GT82TRAJ01020000001210029690",
38 | HR: "HR1210010051863000160",
39 | HU: "HU42117730161111101800000000",
40 | IE: "IE29AIBK93115212345678",
41 | IL: "IL620108000000099999999",
42 | IQ: "IQ98NBIQ850123456789012",
43 | IS: "IS140159260076545510730339",
44 | IT: "IT60X0542811101000000123456",
45 | JO: "JO94CBJO0010000000000131000302",
46 | KW: "KW81CBKU0000000000001234560101",
47 | KZ: "KZ86125KZT5004100100",
48 | LB: "LB62099900000001001901229114",
49 | LC: "LC07HEMM000100010012001200013015",
50 | LI: "LI21088100002324013AA",
51 | LT: "LT121000011101001000",
52 | LU: "LU280019400644750000",
53 | LV: "LV80BANK0000435195001",
54 | MC: "MC5811222000010123456789030",
55 | MD: "MD24AG000225100013104168",
56 | ME: "ME25505000012345678951",
57 | MK: "MK07250120000058984",
58 | MR: "MR1300020001010000123456753",
59 | MT: "MT84MALT011000012345MTLCAST001S",
60 | MU: "MU17BOMM0101101030300200000MUR",
61 | NL: "NL91ABNA0417164300",
62 | NO: "NO9386011117947",
63 | PK: "PK36SCBL0000001123456702",
64 | PL: "PL61109010140000071219812874",
65 | PS: "PS92PALS000000000400123456702",
66 | PT: "PT50000201231234567890154",
67 | QA: "QA58DOHB00001234567890ABCDEFG",
68 | RO: "RO49AAAA1B31007593840000",
69 | RS: "RS35260005601001611379",
70 | SA: "SA0380000000608010167519",
71 | SC: "SC18SSCB11010000000000001497USD",
72 | SE: "SE4550000000058398257466",
73 | SI: "SI56263300012039086",
74 | SK: "SK3112000000198742637541",
75 | SM: "SM86U0322509800000000270100",
76 | ST: "ST68000100010051845310112",
77 | SV: "SV62CENR00000000000000700025",
78 | TL: "TL380080012345678910157",
79 | TN: "TN5910006035183598478831",
80 | TR: "TR330006100519786457841326",
81 | UA: "UA213223130000026007233566001",
82 | VA: "VA59001123000012345678",
83 | VG: "VG96VPVG0000012345678901",
84 | XK: "XK051212012345678906",
85 | AO: "AO69123456789012345678901",
86 | BF: "BF2312345678901234567890123",
87 | BI: "BI41123456789012",
88 | BJ: "BJ11B00610100400271101192591",
89 | CF: "CF4220001000010120069700160",
90 | CI: "CI93CI0080111301134291200589",
91 | CM: "CM9012345678901234567890123",
92 | CV: "CV30123456789012345678901",
93 | DJ: "DJ2110002010010409943020008",
94 | DZ: "DZ8612345678901234567890",
95 | GQ: "GQ7050002001003715228190196",
96 | HN: "HN54PISA00000000000000123124",
97 | IR: "IR861234568790123456789012",
98 | MG: "MG1812345678901234567890123",
99 | ML: "ML15A12345678901234567890123",
100 | MZ: "MZ25123456789012345678901",
101 | SN: "SN52A12345678901234567890123",
102 | KM: "KM4600005000010010904400137",
103 | TD: "TD8960002000010271091600153",
104 | CG: "CG3930011000101013451300019",
105 | EG: "EG800002000156789012345180002",
106 | GA: "GA2140021010032001890020126",
107 | MA: "MA64011519000001205000534921",
108 | NI: "NI92BAMC000000000000000003123123",
109 | NE: "NE58NE0380100100130305000268",
110 | TG: "TG53TG0090604310346500400070",
111 | };
112 |
113 | /**
114 | * International Bank Account Number
115 | *
116 | * ISO_13616.
117 | */
118 | export class IBAN {
119 | private value: string;
120 |
121 | /**
122 | * Creates iban instance.
123 | *
124 | * @param iban the String to be parsed, any spaces are removed.
125 | * @throws FormatException if the String doesn't contain parsable Iban
126 | * InvalidCheckDigitException if Iban has invalid check digit
127 | * UnsupportedCountryException if Iban's Country is not supported.
128 | */
129 | constructor(iban: string) {
130 | const value = IBAN.electronicFormat(iban);
131 |
132 | ibanUtil.validate(value);
133 |
134 | this.value = value;
135 | }
136 |
137 | /**
138 | * Returns iban's country code.
139 | *
140 | * @return countryCode CountryCode
141 | */
142 | getCountryCode(): CountryCode {
143 | return countryByCode(ibanUtil.getCountryCode(this.value)) as CountryCode;
144 | }
145 |
146 | /**
147 | * Returns iban's check digit.
148 | *
149 | * @return checkDigit String
150 | */
151 | getCheckDigit(): string {
152 | return ibanUtil.getCheckDigit(this.value);
153 | }
154 |
155 | /**
156 | * Returns iban's account number.
157 | *
158 | * @return accountNumber String
159 | */
160 | public getAccountNumber(): string | null {
161 | return ibanUtil.getAccountNumber(this.value);
162 | }
163 |
164 | /**
165 | * Returns iban's bank code.
166 | *
167 | * @return bankCode String
168 | */
169 | public getBankCode(): string | null {
170 | return ibanUtil.getBankCode(this.value);
171 | }
172 |
173 | /**
174 | * Returns iban's branch code.
175 | *
176 | * @return branchCode String
177 | */
178 | public getBranchCode(): string | null {
179 | return ibanUtil.getBranchCode(this.value);
180 | }
181 |
182 | /**
183 | * Returns iban's national check digit.
184 | *
185 | * @return nationalCheckDigit String
186 | */
187 | public getNationalCheckDigit(): string | null {
188 | return ibanUtil.getNationalCheckDigit(this.value);
189 | }
190 |
191 | /**
192 | * Returns iban's national check digit.
193 | *
194 | * @return nationalCheckDigit String
195 | */
196 | public getBranchCheckDigit(): string | null {
197 | return ibanUtil.getBranchCheckDigit(this.value);
198 | }
199 |
200 | /**
201 | * Returns iban's currency type if encoded separate from account number
202 | *
203 | * @return nationalCheckDigit String
204 | */
205 | public getCurrencyType(): string | null {
206 | return ibanUtil.getCurrencyType(this.value);
207 | }
208 |
209 | /**
210 | * Returns iban's account type.
211 | *
212 | * @return accountType String
213 | */
214 | public getAccountType(): string | null {
215 | return ibanUtil.getAccountType(this.value);
216 | }
217 |
218 | /**
219 | * Returns iban's owner account type.
220 | *
221 | * @return ownerAccountType String
222 | */
223 | public getOwnerAccountType(): string | null {
224 | return ibanUtil.getOwnerAccountType(this.value);
225 | }
226 |
227 | /**
228 | * Returns iban's identification number.
229 | *
230 | * @return identificationNumber String
231 | */
232 | public getIdentificationNumber(): string | null {
233 | return ibanUtil.getIdentificationNumber(this.value);
234 | }
235 |
236 | /**
237 | * Returns iban's bban (Basic Bank Account Number).
238 | *
239 | * @return bban String
240 | */
241 | public getBban(): string {
242 | return ibanUtil.getBban(this.value);
243 | }
244 |
245 | /**
246 | * Returns an Iban object holding the value of the specified String.
247 | *
248 | * @param iban the String to be parsed.
249 | * @param format the format of the Iban.
250 | * @return an Iban object holding the value represented by the string argument.
251 | * @throws FormatException if the String doesn't contain parsable Iban
252 | * InvalidCheckDigitException if Iban has invalid check digit
253 | * UnsupportedCountryException if Iban's Country is not supported.
254 | *
255 | */
256 | toString(): string {
257 | return this.value;
258 | }
259 |
260 | /**
261 | * Returns formatted version of Iban.
262 | *
263 | * @return A string representing formatted Iban for printing.
264 | */
265 | toFormattedString(): string {
266 | return ibanUtil.toFormattedString(this.value);
267 | }
268 |
269 | /**
270 | * IBAN Validation testing [iban-js API compatibility]
271 | *
272 | * @param {string} iban
273 | * @returns {boolean} true if the value is a valid IBAN
274 | */
275 | static isValid(iban: string): boolean {
276 | try {
277 | ibanUtil.validate(IBAN.electronicFormat(iban)); // will throw if not valid
278 | } catch {
279 | return false;
280 | }
281 | return true;
282 | }
283 |
284 | /**
285 | * Convert an IBAN to a formatted BBAN - with validation[iban-js API compatibility]
286 | *
287 | * @param {string} iban
288 | * @param {String} [separator] the separator to use between the blocks of the BBAN, defaults to ' '
289 | * @returns {string|*}
290 | */
291 | static toBBAN(iban: string, separator: string = " "): string {
292 | const clean = IBAN.electronicFormat(iban);
293 | ibanUtil.validate(clean);
294 | return ibanUtil.toFormattedStringBBAN(clean, separator);
295 | }
296 |
297 | /**
298 | * Convert the passed BBAN to an IBAN for this country specification.
299 | * Please note that "generation of the IBAN shall be the exclusive responsibility
300 | * of the bank/branch servicing the account". * This method implements the
301 | * preferred algorithm described in
302 | * http://en.wikipedia.org/wiki/International_Bank_Account_Number#Generating_IBAN_check_digits
303 | *
304 | * @param countryCode the country of the BBAN
305 | * @param bban the BBAN to convert to IBAN
306 | * @returns {string} the IBAN
307 | */
308 | static fromBBAN(countryCode: string, bban: string): string {
309 | ibanUtil.validateBban(countryCode, IBAN.electronicFormat(bban));
310 |
311 | const iban = `${countryCode}00${bban}`;
312 | const checkDigit = ibanUtil.calculateCheckDigit(iban);
313 |
314 | return ibanUtil.replaceCheckDigit(iban, checkDigit);
315 | }
316 |
317 | /**
318 | * Check the validity of the passed BBAN. [iban-js API compatibility]
319 | *
320 | * @param countryCode the country of the BBAN
321 | * @param bban the BBAN to check the validity of
322 | */
323 | static isValidBBAN(countryCode: string, bban: string): boolean {
324 | try {
325 | ibanUtil.validateBban(countryCode, IBAN.electronicFormat(bban));
326 | } catch {
327 | return false;
328 | }
329 | return true;
330 | }
331 |
332 | /**
333 | * Standard print format of an IBAN, no validation is performed [iban-js API compatibility]
334 | *
335 | * @param iban
336 | * @param separator optional (default ' ')
337 | * @returns {string}
338 | */
339 | static printFormat(iban: string, separator: string = " "): string {
340 | return ibanUtil.toFormattedString(iban, separator);
341 | }
342 |
343 | /**
344 | * Electronic format of the IBAN, no validation is performed. [iban-js API compatibility]
345 | *
346 | * @param iban
347 | * @param separator
348 | * @returns {string}
349 | */
350 | static electronicFormat(iban: string): string {
351 | return iban.replace(NON_ALPHANUM, "").toUpperCase();
352 | }
353 |
354 | static random(cc?: CountryCode): IBAN {
355 | if (cc !== undefined) {
356 | return new IBANBuilder().countryCode(cc).build();
357 | }
358 | return new IBANBuilder().build();
359 | }
360 |
361 | /**
362 | * Return the well known version of the IBAN for this country. This is
363 | * the sample provided by the ISO documentation
364 | */
365 | static sample(cc: CountryCode | string): string {
366 | const s = samples[cc];
367 |
368 | return s !== undefined ? s : samples[CountryCode.DE];
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/src/ibanBuilder.ts:
--------------------------------------------------------------------------------
1 | import * as ibanUtil from "./ibanUtil";
2 | import { BbanStructure } from "./bbanStructure";
3 | import { PartType } from "./structurePart";
4 | import { CountryCode } from "./country";
5 | import { randInt } from "./randInt";
6 | import { UnsupportedCountryException, FormatViolation, FormatException } from "./exceptions";
7 | import { IBAN } from "./iban";
8 |
9 | /**
10 | * Iban Builder Class
11 | */
12 | export class IBANBuilder {
13 | private countryCodeValue?: CountryCode;
14 |
15 | private bankCodeValue?: string;
16 |
17 | private branchCodeValue?: string;
18 |
19 | private nationalCheckDigitValue?: string;
20 |
21 | private accountTypeValue?: string;
22 |
23 | private accountNumberValue?: string;
24 |
25 | private ownerAccountTypeValue?: string;
26 |
27 | private identificationNumberValue?: string;
28 |
29 | private branchCheckDigitValue?: string;
30 |
31 | /**
32 | * Creates an Iban Builder instance.
33 | */
34 | // public constructor() {}
35 |
36 | /**
37 | * Sets iban's country code.
38 | *
39 | * @param countryCode CountryCode
40 | * @return builder Builder
41 | */
42 | countryCode(countryCode: CountryCode): IBANBuilder {
43 | this.countryCodeValue = countryCode;
44 | return this;
45 | }
46 |
47 | /**
48 | * Sets iban's bank code.
49 | *
50 | * @param bankCode String
51 | * @return builder Builder
52 | */
53 | bankCode(bankCode: string): IBANBuilder {
54 | this.bankCodeValue = bankCode;
55 | return this;
56 | }
57 |
58 | /**
59 | * Sets iban's branch code.
60 | *
61 | * @param branchCode String
62 | * @return builder Builder
63 | */
64 | branchCode(branchCode: string): IBANBuilder {
65 | this.branchCodeValue = branchCode;
66 | return this;
67 | }
68 |
69 | /**
70 | * Sets iban's account number.
71 | *
72 | * @param accountNumber String
73 | * @return builder Builder
74 | */
75 | accountNumber(accountNumber: string): IBANBuilder {
76 | this.accountNumberValue = accountNumber;
77 | return this;
78 | }
79 |
80 | /**
81 | * Sets iban's national check digit.
82 | *
83 | * @param nationalCheckDigit String
84 | * @return builder Builder
85 | */
86 | nationalCheckDigit(nationalCheckDigit: string): IBANBuilder {
87 | this.nationalCheckDigitValue = nationalCheckDigit;
88 | return this;
89 | }
90 |
91 | /**
92 | * Sets iban's national check digit.
93 | *
94 | * @param nationalCheckDigit String
95 | * @return builder Builder
96 | */
97 | branchCheckDigit(branchCheckDigit: string): IBANBuilder {
98 | this.branchCheckDigitValue = branchCheckDigit;
99 | return this;
100 | }
101 |
102 | /**
103 | * Sets iban's account type.
104 | *
105 | * @param accountType String
106 | * @return builder Builder
107 | */
108 | accountType(accountType: string): IBANBuilder {
109 | this.accountTypeValue = accountType;
110 | return this;
111 | }
112 |
113 | /**
114 | * Sets iban's owner account type.
115 | *
116 | * @param ownerAccountType String
117 | * @return builder Builder
118 | */
119 | ownerAccountType(ownerAccountType: string): IBANBuilder {
120 | this.ownerAccountTypeValue = ownerAccountType;
121 | return this;
122 | }
123 |
124 | /**
125 | * Sets iban's identification number.
126 | *
127 | * @param identificationNumber String
128 | * @return builder Builder
129 | */
130 | identificationNumber(identificationNumber: string): IBANBuilder {
131 | this.identificationNumberValue = identificationNumber;
132 | return this;
133 | }
134 |
135 | /**
136 | * Builds new iban instance.
137 | *
138 | * @param validate boolean indicates if the generated IBAN needs to be
139 | * validated after generation
140 | * @return new iban instance.
141 | * @exception IbanFormatException if values are not parsable by Iban Specification
142 | * ISO_13616
143 | * @exception UnsupportedCountryException if country is not supported
144 | */
145 | build(fillRandom: boolean = true, validate: boolean = true): IBAN {
146 | if (fillRandom && this.countryCodeValue == null) {
147 | const countryCodes = BbanStructure.supportedCountries();
148 |
149 | this.countryCodeValue = countryCodes[randInt(countryCodes.length)];
150 | }
151 |
152 | const structure = BbanStructure.forCountry(this.countryCodeValue);
153 | if (structure === null) {
154 | throw new Error("shouldn't happen");
155 | }
156 |
157 | this.fillMissingFieldsRandomly(fillRandom);
158 |
159 | // iban is formatted with default check digit.
160 | const formattedIban = this.formatIban();
161 |
162 | const checkDigit = ibanUtil.calculateCheckDigit(formattedIban);
163 |
164 | // replace default check digit with calculated check digit
165 | const ibanValue = ibanUtil.replaceCheckDigit(formattedIban, checkDigit);
166 |
167 | if (validate) {
168 | ibanUtil.validate(ibanValue);
169 | }
170 | return new IBAN(ibanValue);
171 | }
172 |
173 | /**
174 | * Returns formatted bban string.
175 | */
176 | private formatBban(): string {
177 | const parts: string[] = [];
178 | const structure = BbanStructure.forCountry(this.countryCodeValue);
179 |
180 | if (structure === null) {
181 | throw new UnsupportedCountryException("Country code is not supported.", this.countryCodeValue);
182 | }
183 |
184 | for (const part of structure.getParts()) {
185 | switch (part.getPartType()) {
186 | case PartType.BANK_CODE:
187 | if (typeof this.bankCodeValue === "string") {
188 | parts.push(this.bankCodeValue);
189 | }
190 | break;
191 | case PartType.BRANCH_CODE:
192 | if (typeof this.branchCodeValue === "string") {
193 | parts.push(this.branchCodeValue);
194 | }
195 | break;
196 | case PartType.BRANCH_CHECK_DIGIT:
197 | if (typeof this.branchCheckDigitValue === "string") {
198 | parts.push(this.branchCheckDigitValue);
199 | }
200 | break;
201 | case PartType.ACCOUNT_NUMBER:
202 | if (typeof this.accountNumberValue === "string") {
203 | parts.push(this.accountNumberValue);
204 | }
205 | break;
206 | case PartType.NATIONAL_CHECK_DIGIT:
207 | if (typeof this.nationalCheckDigitValue === "string") {
208 | parts.push(this.nationalCheckDigitValue);
209 | }
210 | break;
211 | case PartType.ACCOUNT_TYPE:
212 | if (typeof this.accountTypeValue === "string") {
213 | parts.push(this.accountTypeValue);
214 | }
215 | break;
216 | case PartType.OWNER_ACCOUNT_NUMBER:
217 | if (typeof this.ownerAccountTypeValue === "string") {
218 | parts.push(this.ownerAccountTypeValue);
219 | }
220 | break;
221 | case PartType.IDENTIFICATION_NUMBER:
222 | if (typeof this.identificationNumberValue === "string") {
223 | parts.push(this.identificationNumberValue);
224 | }
225 | break;
226 | }
227 | }
228 |
229 | return parts.join("");
230 | }
231 |
232 | /**
233 | * Returns formatted iban string with default check digit.
234 | */
235 | private formatIban(): string {
236 | return `${this.countryCodeValue}${ibanUtil.DEFAULT_CHECK_DIGIT}${this.formatBban()}`;
237 | }
238 |
239 | private fillMissingFieldsRandomly(fillRandom: boolean) {
240 | const structure = BbanStructure.forCountry(this.countryCodeValue);
241 |
242 | if (structure == null) {
243 | throw new UnsupportedCountryException("Country code is not supported.", this.countryCodeValue);
244 | }
245 |
246 | let needCheckDigit = false;
247 |
248 | for (const entry of structure.getParts()) {
249 | switch (entry.getPartType()) {
250 | case PartType.BANK_CODE:
251 | if (!this.bankCodeValue) {
252 | this.bankCodeValue = entry.generate("", structure);
253 | } else if (!fillRandom) {
254 | throw new FormatException(FormatViolation.NOT_NULL, "bankCode is required; it cannot be null");
255 | }
256 | break;
257 | case PartType.BRANCH_CODE:
258 | if (!this.branchCodeValue) {
259 | this.branchCodeValue = entry.generate("", structure);
260 | } else if (!fillRandom) {
261 | throw new FormatException(FormatViolation.NOT_NULL, "branchCode is required; it cannot be null");
262 | }
263 | break;
264 | case PartType.BRANCH_CHECK_DIGIT:
265 | if (!this.branchCheckDigitValue) {
266 | this.branchCheckDigitValue = entry.generate("", structure);
267 | } else if (!fillRandom) {
268 | throw new FormatException(FormatViolation.NOT_NULL, "branchCheckDigit is required; it cannot be null");
269 | }
270 | break;
271 | case PartType.ACCOUNT_NUMBER:
272 | if (!this.accountNumberValue) {
273 | this.accountNumberValue = entry.generate("", structure);
274 | } else if (!fillRandom) {
275 | throw new FormatException(FormatViolation.NOT_NULL, "accountNumber is required; it cannot be null");
276 | }
277 | break;
278 | case PartType.NATIONAL_CHECK_DIGIT:
279 | if (!this.nationalCheckDigitValue) {
280 | needCheckDigit = true;
281 | this.nationalCheckDigitValue = "".padStart(entry.getLength(), "0");
282 | }
283 | break;
284 | case PartType.ACCOUNT_TYPE:
285 | if (!this.accountTypeValue) {
286 | this.accountTypeValue = entry.generate("", structure);
287 | } else if (!fillRandom) {
288 | throw new FormatException(FormatViolation.NOT_NULL, "accountType is required; it cannot be null");
289 | }
290 | break;
291 | case PartType.OWNER_ACCOUNT_NUMBER:
292 | if (!this.ownerAccountTypeValue) {
293 | this.ownerAccountTypeValue = entry.generate("", structure);
294 | } else if (!fillRandom) {
295 | throw new FormatException(FormatViolation.NOT_NULL, "ownerAccountType is required; it cannot be null");
296 | }
297 | break;
298 | case PartType.IDENTIFICATION_NUMBER:
299 | if (!this.identificationNumberValue) {
300 | this.identificationNumberValue = entry.generate("", structure);
301 | } else if (!fillRandom) {
302 | throw new FormatException(FormatViolation.NOT_NULL, "indentificationNumber is required; it cannot be null");
303 | }
304 | break;
305 | }
306 | }
307 |
308 | if (needCheckDigit) {
309 | for (const entry of structure.getParts()) {
310 | if (entry.getPartType() === PartType.NATIONAL_CHECK_DIGIT) {
311 | const bban = this.formatBban();
312 |
313 | this.nationalCheckDigitValue = entry.generate(bban, structure);
314 | }
315 | }
316 | }
317 | }
318 | }
319 |
--------------------------------------------------------------------------------
/src/ibanUtil.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-use-before-define */
2 | import { CountryCode, countryByCode } from "./country";
3 | import { BbanStructure } from "./bbanStructure";
4 | import { PartType } from "./structurePart";
5 | import {
6 | InvalidCheckDigitException,
7 | FormatViolation,
8 | FormatException,
9 | UnsupportedCountryException,
10 | } from "./exceptions";
11 |
12 | const ucRegex = /^[A-Z]+$/;
13 | const numRegex = /^[0-9]+$/;
14 |
15 | /**
16 | * Iban Utility Class
17 | */
18 | export const DEFAULT_CHECK_DIGIT = "00";
19 | const MOD = 97;
20 | const MAX = 999999999;
21 |
22 | const COUNTRY_CODE_INDEX = 0;
23 | const COUNTRY_CODE_LENGTH = 2;
24 | const CHECK_DIGIT_INDEX = COUNTRY_CODE_LENGTH;
25 | const CHECK_DIGIT_LENGTH = 2;
26 | const BBAN_INDEX = CHECK_DIGIT_INDEX + CHECK_DIGIT_LENGTH;
27 |
28 | /**
29 | * Calculates Iban
30 | * Check Digit.
31 | *
32 | * @param iban string value
33 | * @throws IbanFormatException if iban contains invalid character.
34 | *
35 | * @return check digit as String
36 | */
37 | export function calculateCheckDigit(iban: string): string {
38 | const reformattedIban = replaceCheckDigit(iban, DEFAULT_CHECK_DIGIT);
39 | const modResult = calculateMod(reformattedIban);
40 | const checkDigit = String(98 - modResult);
41 |
42 | return checkDigit.padStart(2, "0");
43 | }
44 |
45 | /**
46 | * Validates iban.
47 | *
48 | * @param iban to be validated.
49 | * @throws IbanFormatException if iban is invalid.
50 | * UnsupportedCountryException if iban's country is not supported.
51 | * InvalidCheckDigitException if iban has invalid check digit.
52 | */
53 | export function validate(iban: string): void {
54 | validateNotEmpty(iban);
55 | validateCountryCode(iban, true);
56 | validateCheckDigitPresence(iban);
57 | validateBban(getCountryCode(iban), getBban(iban));
58 | validateCheckDigitChecksum(iban);
59 | }
60 |
61 | /**
62 | * Validates iban checksum only, does not validate country or BBAN
63 | *
64 | * @param iban to be validated.
65 | * @throws IbanFormatException if iban is invalid.
66 | * InvalidCheckDigitException if iban has invalid check digit.
67 | */
68 | export function validateCheckDigit(iban: string): void {
69 | validateNotEmpty(iban);
70 | validateCheckDigitPresence(iban);
71 | validateCountryCode(iban, false);
72 | validateCheckDigitChecksum(iban);
73 | }
74 |
75 | /**
76 | * Validates bban.
77 | *
78 | * @param countryCode country for this bban
79 | * @param bban to be validated.
80 | * @throws IbanFormatException if iban is invalid.
81 | * UnsupportedCountryException if iban's country is not supported.
82 | * InvalidCheckDigitException if iban has invalid check digit.
83 | */
84 | export function validateBban(countryCode: string, bban: string): void {
85 | validateCountryCode(countryCode, true);
86 |
87 | const structure = getBbanStructure(countryCode);
88 |
89 | if (!structure) {
90 | throw new Error("Internal error, expected structure");
91 | }
92 |
93 | structure.validate(bban);
94 |
95 | // validateBbanLength(iban, structure);
96 | // validateBbanEntries(iban, structure);
97 | }
98 |
99 | /**
100 | * Checks whether country is supporting iban.
101 | * @param countryCode {@link org.iban4j.CountryCode}
102 | *
103 | * @return boolean true if country supports iban, false otherwise.
104 | */
105 | export function isSupportedCountry(countryCode: CountryCode): boolean {
106 | return BbanStructure.forCountry(countryCode) != null;
107 | }
108 |
109 | /**
110 | * Returns iban length for the specified country.
111 | *
112 | * @param countryCode {@link org.iban4j.CountryCode}
113 | * @return the length of the iban for the specified country.
114 | */
115 | export function getIbanLength(countryCode: CountryCode): number {
116 | const structure = getBbanStructure(countryCode);
117 |
118 | if (structure === null) {
119 | throw new UnsupportedCountryException("Unsuppored country", countryCode);
120 | }
121 |
122 | return COUNTRY_CODE_LENGTH + CHECK_DIGIT_LENGTH + structure.getBbanLength();
123 | }
124 |
125 | /**
126 | * Returns iban's check digit.
127 | *
128 | * @param iban String
129 | * @return checkDigit String
130 | */
131 | export function getCheckDigit(iban: string): string {
132 | return iban.substring(CHECK_DIGIT_INDEX, CHECK_DIGIT_INDEX + CHECK_DIGIT_LENGTH);
133 | }
134 |
135 | /**
136 | * Returns iban's country code.
137 | *
138 | * @param iban String
139 | * @return countryCode String
140 | */
141 | export function getCountryCode(iban: string): string {
142 | return iban.substring(COUNTRY_CODE_INDEX, COUNTRY_CODE_INDEX + COUNTRY_CODE_LENGTH);
143 | }
144 |
145 | /**
146 | * Returns iban's country code and check digit.
147 | *
148 | * @param iban String
149 | * @return countryCodeAndCheckDigit String
150 | */
151 | export function getCountryCodeAndCheckDigit(iban: string): string {
152 | return iban.substring(COUNTRY_CODE_INDEX, COUNTRY_CODE_INDEX + COUNTRY_CODE_LENGTH + CHECK_DIGIT_LENGTH);
153 | }
154 |
155 | /**
156 | * Returns iban's bban (Basic Bank Account Number).
157 | *
158 | * @param iban String
159 | * @return bban String
160 | */
161 | export function getBban(iban: string): string {
162 | return iban.substring(BBAN_INDEX);
163 | }
164 |
165 | /**
166 | * Returns iban's account number.
167 | *
168 | * @param iban String
169 | * @return accountNumber String
170 | */
171 | export function getAccountNumber(iban: string): string | null {
172 | return extractBbanEntry(iban, PartType.ACCOUNT_NUMBER);
173 | }
174 |
175 | /**
176 | * Returns iban's bank code.
177 | *
178 | * @param iban String
179 | * @return bankCode String
180 | */
181 | export function getBankCode(iban: string): string | null {
182 | return extractBbanEntry(iban, PartType.BANK_CODE);
183 | }
184 |
185 | /**
186 | * Returns iban's branch code.
187 | *
188 | * @param iban String
189 | * @return branchCode String
190 | */
191 | export function getBranchCode(iban: string): string | null {
192 | return extractBbanEntry(iban, PartType.BRANCH_CODE);
193 | }
194 |
195 | /**
196 | * Returns iban's national check digit.
197 | *
198 | * @param iban String
199 | * @return nationalCheckDigit String
200 | */
201 | export function getNationalCheckDigit(iban: string): string | null {
202 | return extractBbanEntry(iban, PartType.NATIONAL_CHECK_DIGIT);
203 | }
204 |
205 | /**
206 | * Returns iban's branch check digit.
207 | *
208 | * @param iban String
209 | * @return nationalCheckDigit String
210 | */
211 | export function getBranchCheckDigit(iban: string): string | null {
212 | return extractBbanEntry(iban, PartType.BRANCH_CHECK_DIGIT);
213 | }
214 |
215 | /**
216 | * Returns iban's currency type
217 | *
218 | * @param iban String
219 | * @return nationalCheckDigit String
220 | */
221 | export function getCurrencyType(iban: string): string | null {
222 | return extractBbanEntry(iban, PartType.CURRENCY_TYPE);
223 | }
224 |
225 | /**
226 | * Returns iban's account type.
227 | *
228 | * @param iban String
229 | * @return accountType String
230 | */
231 | export function getAccountType(iban: string): string | null {
232 | return extractBbanEntry(iban, PartType.ACCOUNT_TYPE);
233 | }
234 |
235 | /**
236 | * Returns iban's owner account type.
237 | *
238 | * @param iban String
239 | * @return ownerAccountType String
240 | */
241 | export function getOwnerAccountType(iban: string): string | null {
242 | return extractBbanEntry(iban, PartType.OWNER_ACCOUNT_NUMBER);
243 | }
244 |
245 | /**
246 | * Returns iban's identification number.
247 | *
248 | * @param iban String
249 | * @return identificationNumber String
250 | */
251 | export function getIdentificationNumber(iban: string): string | null {
252 | return extractBbanEntry(iban, PartType.IDENTIFICATION_NUMBER);
253 | }
254 |
255 | /*
256 | function calculateCheckDigitIban(iban: Iban): string {
257 | return calculateCheckDigit(iban.toString());
258 | }
259 | */
260 |
261 | /**
262 | * Returns an iban with replaced check digit.
263 | *
264 | * @param iban The iban
265 | * @return The iban without the check digit
266 | */
267 | export function replaceCheckDigit(iban: string, checkDigit: string): string {
268 | return getCountryCode(iban) + checkDigit + getBban(iban);
269 | }
270 |
271 | /**
272 | * Returns formatted version of Iban.
273 | *
274 | * @return A string representing formatted Iban for printing.
275 | */
276 | export function toFormattedString(iban: string, separator: string = " "): string {
277 | return iban.replace(/(.{4})/g, `$1${separator}`).trim();
278 | }
279 |
280 | /* Returns formatted version of BBAN from IBAN.
281 | *
282 | * @return A string representing formatted BBAN in "national" format
283 | */
284 | export function toFormattedStringBBAN(iban: string, separator: string = " "): string {
285 | const structure = getBbanStructure(iban);
286 |
287 | if (structure === null) {
288 | throw new Error("should't happen - already validated IBAN");
289 | }
290 |
291 | const bban = getBban(iban);
292 | const parts = structure.getParts().reduce((acc, part) => {
293 | const value = structure.extractValue(bban, part.getPartType());
294 |
295 | return acc.concat(value || "", part.trailingSeparator ? separator : "");
296 | }, [] as string[]);
297 | parts.pop(); // Don't care about last separator
298 |
299 | return parts.join("");
300 | }
301 |
302 | export function validateCheckDigitChecksum(iban: string): void {
303 | if (calculateMod(iban) != 1) {
304 | const checkDigit = getCheckDigit(iban);
305 | const expectedCheckDigit = calculateCheckDigit(iban);
306 |
307 | throw new InvalidCheckDigitException(
308 | `[${iban}] has invalid check digit: ${checkDigit}, expected check digit is: ${expectedCheckDigit}`,
309 | checkDigit,
310 | expectedCheckDigit,
311 | );
312 | }
313 | }
314 |
315 | function validateNotEmpty(iban: string) {
316 | if (iban == null) {
317 | throw new FormatException(FormatViolation.NOT_NULL, "Null can't be a valid Iban.");
318 | }
319 |
320 | if (iban.length === 0) {
321 | throw new FormatException(FormatViolation.NOT_EMPTY, "Empty string can't be a valid Iban.");
322 | }
323 | }
324 |
325 | function validateCountryCode(iban: string, hasStructure = true) {
326 | // check if iban contains 2 char country code
327 | if (iban.length < COUNTRY_CODE_LENGTH) {
328 | throw new FormatException(FormatViolation.COUNTRY_CODE_TWO_LETTERS, "Iban must contain 2 char country code.", iban);
329 | }
330 |
331 | const countryCode = getCountryCode(iban);
332 |
333 | // check case sensitivity
334 | if (countryCode !== countryCode.toUpperCase() || !ucRegex.test(countryCode)) {
335 | throw new FormatException(
336 | FormatViolation.COUNTRY_CODE_ONLY_UPPER_CASE_LETTERS,
337 | "Iban country code must contain upper case letters.",
338 | countryCode,
339 | );
340 | }
341 |
342 | const country = countryByCode(countryCode);
343 | if (country == null) {
344 | throw new FormatException(
345 | FormatViolation.COUNTRY_CODE_EXISTS,
346 | "Iban contains non existing country code.",
347 | countryCode,
348 | );
349 | }
350 |
351 | if (hasStructure) {
352 | // check if country is supported
353 | const structure = BbanStructure.forCountry(country);
354 | if (structure == null) {
355 | throw new UnsupportedCountryException("Country code is not supported.", countryCode);
356 | }
357 | }
358 | }
359 |
360 | function validateCheckDigitPresence(iban: string) {
361 | // check if iban contains 2 digit check digit
362 | if (iban.length < COUNTRY_CODE_LENGTH + CHECK_DIGIT_LENGTH) {
363 | throw new FormatException(
364 | FormatViolation.CHECK_DIGIT_TWO_DIGITS,
365 | "Iban must contain 2 digit check digit.",
366 | iban.substring(COUNTRY_CODE_LENGTH),
367 | );
368 | }
369 |
370 | const checkDigit = getCheckDigit(iban);
371 |
372 | // check digits
373 | if (!numRegex.test(checkDigit)) {
374 | throw new FormatException(
375 | FormatViolation.CHECK_DIGIT_ONLY_DIGITS,
376 | "Iban's check digit should contain only digits.",
377 | checkDigit,
378 | );
379 | }
380 | }
381 |
382 | /**
383 | * Calculates
384 | * Iban Modulo.
385 | *
386 | * @param iban String value
387 | * @return modulo 97
388 | */
389 | function calculateMod(iban: string): number {
390 | const reformattedIban = getBban(iban) + getCountryCodeAndCheckDigit(iban);
391 |
392 | const VA = "A".charCodeAt(0);
393 | const VZ = "Z".charCodeAt(0);
394 | const V0 = "0".charCodeAt(0);
395 | const V9 = "9".charCodeAt(0);
396 |
397 | function addSum(total: number, value: number) {
398 | const newTotal = (value > 9 ? total * 100 : total * 10) + value;
399 |
400 | return newTotal > MAX ? newTotal % MOD : newTotal;
401 | }
402 |
403 | const total = reformattedIban
404 | .toUpperCase()
405 | .split("")
406 | .reduce((totalValue, ch) => {
407 | const code = ch.charCodeAt(0);
408 |
409 | if (VA <= code && code <= VZ) {
410 | return addSum(totalValue, code - VA + 10);
411 | } else if (V0 <= code && code <= V9) {
412 | return addSum(totalValue, code - V0);
413 | } else {
414 | throw new FormatException(FormatViolation.IBAN_VALID_CHARACTERS, `Invalid Character[${ch}] = '${code}'`, ch);
415 | }
416 | }, 0);
417 |
418 | return total % MOD;
419 | }
420 |
421 | function getBbanStructure(iban: string): BbanStructure | null {
422 | const countryCode = countryByCode(getCountryCode(iban));
423 |
424 | if (!countryCode) {
425 | return null;
426 | }
427 |
428 | return getBbanStructureByCountry(countryCode);
429 | }
430 |
431 | function getBbanStructureByCountry(countryCode: CountryCode): BbanStructure | null {
432 | return BbanStructure.forCountry(countryCode);
433 | }
434 |
435 | function extractBbanEntry(iban: string, partType: PartType): string | null {
436 | const bban = getBban(iban);
437 | const structure = getBbanStructure(iban);
438 |
439 | if (structure === null) {
440 | return null;
441 | }
442 |
443 | return structure.extractValue(bban, partType);
444 | }
445 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { CountryCode } from "./country";
2 | export { BbanStructure } from "./bbanStructure";
3 | export { IBAN } from "./iban";
4 | export { IBANBuilder } from "./ibanBuilder";
5 | export { BIC } from "./bic";
6 |
--------------------------------------------------------------------------------
/src/randInt.ts:
--------------------------------------------------------------------------------
1 | export function randInt(maxVal: number, minVal: number = 0): number {
2 | return Math.floor(Math.random() * maxVal) + minVal;
3 | }
4 |
--------------------------------------------------------------------------------
/src/structurePart.ts:
--------------------------------------------------------------------------------
1 | import { randInt } from "./randInt";
2 | import { BbanStructure } from "./bbanStructure";
3 |
4 | export enum PartType {
5 | BANK_CODE,
6 | BRANCH_CODE,
7 | ACCOUNT_NUMBER,
8 | BRANCH_CHECK_DIGIT,
9 | NATIONAL_CHECK_DIGIT,
10 | CURRENCY_TYPE,
11 | ACCOUNT_TYPE,
12 | OWNER_ACCOUNT_NUMBER,
13 | IDENTIFICATION_NUMBER,
14 | }
15 |
16 | /**
17 | * Bban Structure Entry representation.
18 | */
19 | export enum CharacterType {
20 | /**
21 | * Digits (numeric characters 0 to 9 only)
22 | */
23 | n,
24 | /**
25 | * Upper case letters (alphabetic characters A-Z only)
26 | */
27 | a,
28 | /**
29 | * Upper case alphanumeric characters (A-Z, a-z and 0-9)
30 | */
31 | c,
32 | /**
33 | * Blank space
34 | */
35 | e,
36 | }
37 |
38 | // Use by random string generation
39 | const charByCharacterType: Record = {
40 | [CharacterType.n]: "0123456789",
41 | [CharacterType.a]: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
42 | [CharacterType.c]: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
43 | [CharacterType.e]: " ",
44 | };
45 |
46 | // Used by validation
47 | const charByCharacterRE: Record = {
48 | [CharacterType.n]: /^[0-9]+$/,
49 | [CharacterType.a]: /^[A-Z]+$/,
50 | [CharacterType.c]: /^[0-9A-Za-z]+$/,
51 | [CharacterType.e]: /^ +$/,
52 | };
53 |
54 | type GenerateValue = (bban: string, structure: BbanStructure) => string;
55 |
56 | export class BbanStructurePart {
57 | private entryType: PartType;
58 |
59 | private characterType: CharacterType;
60 |
61 | private length: number;
62 |
63 | trailingSeparator: boolean;
64 |
65 | generate: GenerateValue;
66 |
67 | hasGenerator: boolean;
68 |
69 | private constructor(
70 | entryType: PartType,
71 | characterType: CharacterType,
72 | length: number,
73 | trailingSeparator: boolean,
74 | generate?: GenerateValue,
75 | ) {
76 | this.entryType = entryType;
77 | this.characterType = characterType;
78 | this.length = length;
79 | this.generate = generate || this.defaultGenerator;
80 | this.hasGenerator = !!generate;
81 | this.trailingSeparator = trailingSeparator;
82 | }
83 |
84 | static bankCode(length: number, characterType: CharacterType, trailingSeparator: boolean = true): BbanStructurePart {
85 | return new BbanStructurePart(PartType.BANK_CODE, characterType, length, trailingSeparator);
86 | }
87 |
88 | static branchCode(
89 | length: number,
90 | characterType: CharacterType,
91 | trailingSeparator: boolean = true,
92 | ): BbanStructurePart {
93 | return new BbanStructurePart(PartType.BRANCH_CODE, characterType, length, trailingSeparator);
94 | }
95 |
96 | static accountNumber(
97 | length: number,
98 | characterType: CharacterType,
99 | trailingSeparator: boolean = true,
100 | ): BbanStructurePart {
101 | return new BbanStructurePart(PartType.ACCOUNT_NUMBER, characterType, length, trailingSeparator);
102 | }
103 |
104 | static nationalCheckDigit(
105 | length: number,
106 | characterType: CharacterType,
107 | generate?: GenerateValue,
108 | trailingSeparator: boolean = false,
109 | ): BbanStructurePart {
110 | return new BbanStructurePart(PartType.NATIONAL_CHECK_DIGIT, characterType, length, trailingSeparator, generate);
111 | }
112 |
113 | static branchCheckDigit(
114 | length: number,
115 | characterType: CharacterType,
116 | generate?: GenerateValue,
117 | trailingSeparator: boolean = false,
118 | ): BbanStructurePart {
119 | return new BbanStructurePart(PartType.BRANCH_CHECK_DIGIT, characterType, length, trailingSeparator, generate);
120 | }
121 |
122 | static accountType(
123 | length: number,
124 | characterType: CharacterType,
125 | trailingSeparator: boolean = false,
126 | ): BbanStructurePart {
127 | return new BbanStructurePart(PartType.ACCOUNT_TYPE, characterType, length, trailingSeparator);
128 | }
129 |
130 | static currencyType(
131 | length: number,
132 | characterType: CharacterType,
133 | trailingSeparator: boolean = false,
134 | ): BbanStructurePart {
135 | return new BbanStructurePart(PartType.CURRENCY_TYPE, characterType, length, trailingSeparator);
136 | }
137 |
138 | static ownerAccountNumber(
139 | length: number,
140 | characterType: CharacterType,
141 | trailingSeparator: boolean = true,
142 | ): BbanStructurePart {
143 | return new BbanStructurePart(PartType.OWNER_ACCOUNT_NUMBER, characterType, length, trailingSeparator);
144 | }
145 |
146 | static identificationNumber(
147 | length: number,
148 | characterType: CharacterType,
149 | trailingSeparator: boolean = true,
150 | ): BbanStructurePart {
151 | return new BbanStructurePart(PartType.IDENTIFICATION_NUMBER, characterType, length, trailingSeparator);
152 | }
153 |
154 | getPartType(): PartType {
155 | return this.entryType;
156 | }
157 |
158 | getCharacterType(): CharacterType {
159 | return this.characterType;
160 | }
161 |
162 | getLength(): number {
163 | return this.length;
164 | }
165 |
166 | /**
167 | * Check to see if the string value is valid for the entry
168 | */
169 | validate(value: string): boolean {
170 | return charByCharacterRE[this.characterType].test(value);
171 | }
172 |
173 | /**
174 | * Default generator to use -- just generate random sequence
175 | */
176 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
177 | private defaultGenerator(bban: string, structure: BbanStructure): string {
178 | const charChoices = charByCharacterType[this.characterType];
179 |
180 | const s: string[] = [];
181 | for (let i = 0; i < this.getLength(); i += 1) {
182 | s.push(charChoices[randInt(charChoices.length)]);
183 | }
184 |
185 | return s.join("");
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/test/bic.spec.ts:
--------------------------------------------------------------------------------
1 | import { BIC } from "../src";
2 |
3 | describe("BIC", () => {
4 | describe("Creation", () => {
5 | it("invalid country code", () => {
6 | expect(() => new BIC("DEUTAAFF500")).toThrow();
7 | });
8 |
9 | it("equal", () => {
10 | const bic1 = new BIC("DEUTDEFF500");
11 | const bic2 = new BIC("DEUTDEFF500");
12 |
13 | expect(bic1.toString()).toBe(bic2.toString());
14 | });
15 |
16 | it("not equal", () => {
17 | const bic1 = new BIC("DEUTDEFF500");
18 | const bic2 = new BIC("DEUTDEFF501");
19 |
20 | expect(bic1.toString()).not.toBe(bic2.toString());
21 | });
22 |
23 | it("bank code", () => {
24 | const bic = new BIC("DEUTDEFF500");
25 |
26 | expect(bic.getBankCode()).toBe("DEUT");
27 | });
28 |
29 | it("bank code alphanum", () => {
30 | const bic = new BIC("E097AEXXXXX");
31 |
32 | expect(bic.getBankCode()).toBe("E097");
33 | });
34 |
35 | it("country code", () => {
36 | const bic = new BIC("DEUTDEFF500");
37 |
38 | expect(bic.getCountryCode()).toBe("DE");
39 | });
40 |
41 | it("branch code", () => {
42 | const bic = new BIC("DEUTDEFF500");
43 |
44 | expect(bic.getBranchCode()).toBe("500");
45 | });
46 |
47 | it("branch code", () => {
48 | const bic = new BIC("DEUTDEFF");
49 |
50 | expect(bic.getBranchCode()).toBe(null);
51 | });
52 |
53 | it("location code", () => {
54 | const bic = new BIC("DEUTDEFF");
55 |
56 | expect(bic.getLocationCode()).toBe("FF");
57 | });
58 |
59 | it("toString 1", () => {
60 | const bic = new BIC("DEUTDEFF");
61 |
62 | expect(bic.toString()).toBe("DEUTDEFF");
63 | });
64 |
65 | it("toString 2", () => {
66 | const bic = new BIC("DEUTDEFF500");
67 |
68 | expect(bic.toString()).toBe("DEUTDEFF500");
69 | });
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/test/exceptions.spec.ts:
--------------------------------------------------------------------------------
1 | import * as exceptions from "../src/exceptions";
2 |
3 | describe("exceptions", () => {
4 | it("smoke", () => {
5 | const e = new exceptions.UnsupportedCountryException("test");
6 |
7 | expect(e).toBeInstanceOf(exceptions.UnsupportedCountryException);
8 | expect(e instanceof exceptions.UnsupportedCountryException).toEqual(true);
9 | });
10 | });
11 |
--------------------------------------------------------------------------------
/test/iban.spec.ts:
--------------------------------------------------------------------------------
1 | import { IBAN } from "../src";
2 |
3 | describe("IBAN", () => {
4 | describe("test IBAN.isValid", () => {
5 | it("valid iban", () => {
6 | expect(IBAN.isValid("BA391990440001200279")).toBe(true);
7 | });
8 | it("valid with spaces", () => {
9 | expect(IBAN.isValid("DE89 3704 0044 0532 0130 00")).toBe(true);
10 | });
11 | it("bad iban", () => {
12 | expect(IBAN.isValid("BA391990440001200278")).toBe(false);
13 | });
14 | });
15 |
16 | describe("Test check digit", () => {
17 | it("valid iban", () => {
18 | const iban = new IBAN("BA391990440001200279");
19 | expect(iban.getCountryCode()).toBe("BA");
20 | });
21 |
22 | it("bad iban", () => {
23 | expect(() => {
24 | new IBAN("BA391990440001200278");
25 | }).toThrow();
26 | });
27 | });
28 |
29 | describe("Test toFormat", () => {
30 | it("AD - 24 characters", () => {
31 | const iban = new IBAN("AD12 0001 2030 200359100100");
32 | expect(iban.toFormattedString()).toBe("AD12 0001 2030 2003 5910 0100");
33 | });
34 |
35 | it("AE - 23 characters", () => {
36 | const iban = new IBAN("AE07 0331 234567890123456");
37 | expect(iban.toFormattedString()).toBe("AE07 0331 2345 6789 0123 456");
38 | });
39 | });
40 |
41 | describe("Test IBAN Version 80", () => {
42 | it("AD", () => {
43 | const iban = new IBAN("AD12 0001 2030 200359100100");
44 | expect(iban.getCountryCode()).toBe("AD");
45 | expect(iban.getBankCode()).toBe("0001");
46 | expect(iban.getBranchCode()).toBe("2030");
47 | expect(iban.getAccountNumber()).toBe("200359100100");
48 | });
49 |
50 | it("AE", () => {
51 | const iban = new IBAN("AE07 0331 234567890123456");
52 | expect(iban.getCountryCode()).toBe("AE");
53 | expect(iban.getBankCode()).toBe("033");
54 | expect(iban.getAccountNumber()).toBe("1234567890123456");
55 | });
56 |
57 | it("AL", () => {
58 | const iban = new IBAN("AL47212110090000000235698741");
59 | expect(iban.getCountryCode()).toBe("AL");
60 | expect(iban.getBankCode()).toBe("212");
61 | expect(iban.getBranchCode()).toBe("1100");
62 | expect(iban.getNationalCheckDigit()).toBe("9");
63 | expect(iban.getAccountNumber()).toBe("0000000235698741");
64 | });
65 |
66 | it("AT", () => {
67 | const iban = new IBAN("AT611904300234573201");
68 | expect(iban.getCountryCode()).toBe("AT");
69 | expect(iban.getBankCode()).toBe("19043");
70 | expect(iban.getAccountNumber()).toBe("00234573201");
71 | });
72 |
73 | it("AZ", () => {
74 | const iban = new IBAN("AZ21NABZ00000000137010001944");
75 | expect(iban.getCountryCode()).toBe("AZ");
76 | expect(iban.getBankCode()).toBe("NABZ");
77 | expect(iban.getAccountNumber()).toBe("00000000137010001944");
78 | });
79 |
80 | it("BA", () => {
81 | const iban = new IBAN("BA391990440001200279");
82 | expect(iban.getCountryCode()).toBe("BA");
83 | expect(iban.getBankCode()).toBe("199");
84 | expect(iban.getBranchCode()).toBe("044");
85 | expect(iban.getAccountNumber()).toBe("00012002");
86 | expect(iban.getNationalCheckDigit()).toBe("79");
87 | });
88 |
89 | it("BE", () => {
90 | const iban = new IBAN("BE68539007547034");
91 | expect(iban.getCountryCode()).toBe("BE");
92 | expect(iban.getBankCode()).toBe("539");
93 | expect(iban.getAccountNumber()).toBe("0075470");
94 | expect(iban.getNationalCheckDigit()).toBe("34");
95 | });
96 |
97 | it("BG", () => {
98 | const iban = new IBAN("BG80BNBG96611020345678");
99 | expect(iban.getCountryCode()).toBe("BG");
100 | expect(iban.getBankCode()).toBe("BNBG");
101 | expect(iban.getBranchCode()).toBe("9661");
102 | expect(iban.getAccountType()).toBe("10");
103 | expect(iban.getAccountNumber()).toBe("20345678");
104 | expect(iban.getNationalCheckDigit()).toBe(null);
105 | });
106 |
107 | it("BH", () => {
108 | const iban = new IBAN("BH67BMAG00001299123456");
109 | expect(iban.getCountryCode()).toBe("BH");
110 | expect(iban.getBankCode()).toBe("BMAG");
111 | expect(iban.getAccountNumber()).toBe("00001299123456");
112 | });
113 |
114 | it("BI", () => {
115 | const iban = new IBAN("BI4210000100010000332045181");
116 | expect(iban.getCountryCode()).toBe("BI");
117 | expect(iban.getBankCode()).toBe("10000");
118 | expect(iban.getBranchCode()).toBe("10001");
119 | expect(iban.getAccountNumber()).toBe("00003320451");
120 | });
121 |
122 | it("BR", () => {
123 | const iban = new IBAN("BR9700360305000010009795493P1");
124 | expect(iban.getCountryCode()).toBe("BR");
125 | expect(iban.getBankCode()).toBe("00360305");
126 | expect(iban.getBranchCode()).toBe("00001");
127 | expect(iban.getAccountNumber()).toBe("0009795493");
128 | expect(iban.getAccountType()).toBe("P");
129 | expect(iban.getOwnerAccountType()).toBe("1");
130 | });
131 |
132 | it("BY", () => {
133 | const iban = new IBAN("BY13NBRB3600900000002Z00AB00");
134 | expect(iban.getCountryCode()).toBe("BY");
135 | expect(iban.getBankCode()).toBe("NBRB");
136 | expect(iban.getAccountType()).toBe("3600");
137 | expect(iban.getAccountNumber()).toBe("900000002Z00AB00");
138 | });
139 |
140 | it("CH", () => {
141 | const iban = new IBAN("CH9300762011623852957");
142 | expect(iban.getCountryCode()).toBe("CH");
143 | expect(iban.getBankCode()).toBe("00762");
144 | expect(iban.getAccountNumber()).toBe("011623852957");
145 | });
146 |
147 | it("CR", () => {
148 | const iban = new IBAN("CR05015202001026284066");
149 | expect(iban.getCountryCode()).toBe("CR");
150 | expect(iban.getBankCode()).toBe("0152");
151 | expect(iban.getAccountNumber()).toBe("02001026284066");
152 | });
153 |
154 | it("CY", () => {
155 | const iban = new IBAN("CY17002001280000001200527600");
156 | expect(iban.getCountryCode()).toBe("CY");
157 | expect(iban.getBankCode()).toBe("002");
158 | expect(iban.getBranchCode()).toBe("00128");
159 | expect(iban.getAccountNumber()).toBe("0000001200527600");
160 | });
161 |
162 | it("CZ", () => {
163 | const iban = new IBAN("CZ6508000000192000145399");
164 | expect(iban.getCountryCode()).toBe("CZ");
165 | expect(iban.getBankCode()).toBe("0800");
166 | expect(iban.getBranchCode()).toBe("000019");
167 | expect(iban.getAccountNumber()).toBe("2000145399");
168 | });
169 |
170 | it("DE", () => {
171 | const iban = new IBAN("DE89370400440532013000");
172 | expect(iban.getCountryCode()).toBe("DE");
173 | expect(iban.getBankCode()).toBe("37040044");
174 | expect(iban.getAccountNumber()).toBe("0532013000");
175 | });
176 |
177 | it("DJ", () => {
178 | const iban = new IBAN("DJ2100010000000154000100186");
179 | expect(iban.getCountryCode()).toBe("DJ");
180 | expect(iban.getBankCode()).toBe("00010");
181 | expect(iban.getBranchCode()).toBe("00000");
182 | expect(iban.getAccountNumber()).toBe("01540001001");
183 | });
184 |
185 | it("DK", () => {
186 | const iban = new IBAN("DK5000400440116243");
187 | expect(iban.getCountryCode()).toBe("DK");
188 | expect(iban.getBankCode()).toBe("0040");
189 | expect(iban.getAccountNumber()).toBe("0440116243");
190 | });
191 |
192 | it("DO", () => {
193 | const iban = new IBAN("DO28BAGR00000001212453611324");
194 | expect(iban.getCountryCode()).toBe("DO");
195 | expect(iban.getBankCode()).toBe("BAGR");
196 | expect(iban.getAccountNumber()).toBe("00000001212453611324");
197 | });
198 |
199 | it("EE", () => {
200 | const iban = new IBAN("EE382200221020145685");
201 | expect(iban.getCountryCode()).toBe("EE");
202 | expect(iban.getBankCode()).toBe("22");
203 | expect(iban.getBranchCode()).toBe("00");
204 | expect(iban.getAccountNumber()).toBe("22102014568");
205 | expect(iban.getNationalCheckDigit()).toBe("5");
206 | });
207 |
208 | it("EG", () => {
209 | const iban = new IBAN("EG380019000500000000263180002");
210 | expect(iban.getCountryCode()).toBe("EG");
211 | expect(iban.getBankCode()).toBe("0019");
212 | expect(iban.getBranchCode()).toBe("0005");
213 | expect(iban.getAccountNumber()).toBe("00000000263180002");
214 | });
215 |
216 | it("ES", () => {
217 | const iban = new IBAN("ES9121000418450200051332");
218 | expect(iban.getCountryCode()).toBe("ES");
219 | expect(iban.getBankCode()).toBe("2100");
220 | expect(iban.getBranchCode()).toBe("0418");
221 | expect(iban.getAccountNumber()).toBe("0200051332");
222 | expect(iban.getNationalCheckDigit()).toBe("45");
223 | });
224 |
225 | it("FI", () => {
226 | const iban = new IBAN("FI2112345600000785");
227 | expect(iban.getCountryCode()).toBe("FI");
228 | expect(iban.getBankCode()).toBe("123");
229 | expect(iban.getAccountNumber()).toBe("45600000785");
230 | });
231 |
232 | it("FK", () => {
233 | const iban = new IBAN("FK88SC123456789012");
234 | expect(iban.getCountryCode()).toBe("FK");
235 | expect(iban.getBankCode()).toBe("SC");
236 | expect(iban.getAccountNumber()).toBe("123456789012");
237 | });
238 |
239 | it("FO", () => {
240 | const iban = new IBAN("FO6264600001631634");
241 | expect(iban.getCountryCode()).toBe("FO");
242 | expect(iban.getBankCode()).toBe("6460");
243 | expect(iban.getAccountNumber()).toBe("000163163");
244 | expect(iban.getNationalCheckDigit()).toBe("4");
245 | });
246 |
247 | it("FR", () => {
248 | const iban = new IBAN("FR1420041010050500013M02606");
249 | expect(iban.getCountryCode()).toBe("FR");
250 | expect(iban.getBankCode()).toBe("20041");
251 | expect(iban.getBranchCode()).toBe("01005");
252 | expect(iban.getAccountNumber()).toBe("0500013M026");
253 | expect(iban.getNationalCheckDigit()).toBe("06");
254 | });
255 |
256 | it("GB", () => {
257 | const iban = new IBAN("GB29NWBK60161331926819");
258 | expect(iban.getCountryCode()).toBe("GB");
259 | expect(iban.getBankCode()).toBe("NWBK");
260 | expect(iban.getBranchCode()).toBe("601613");
261 | expect(iban.getAccountNumber()).toBe("31926819");
262 | });
263 |
264 | it("GE", () => {
265 | const iban = new IBAN("GE29NB0000000101904917");
266 | expect(iban.getCountryCode()).toBe("GE");
267 | expect(iban.getBankCode()).toBe("NB");
268 | expect(iban.getAccountNumber()).toBe("0000000101904917");
269 | });
270 |
271 | it("GI", () => {
272 | const iban = new IBAN("GI75NWBK000000007099453");
273 | expect(iban.getCountryCode()).toBe("GI");
274 | expect(iban.getBankCode()).toBe("NWBK");
275 | expect(iban.getAccountNumber()).toBe("000000007099453");
276 | });
277 |
278 | it("GL", () => {
279 | const iban = new IBAN("GL8964710001000206");
280 | expect(iban.getCountryCode()).toBe("GL");
281 | expect(iban.getBankCode()).toBe("6471");
282 | expect(iban.getAccountNumber()).toBe("0001000206");
283 | });
284 |
285 | it("GR", () => {
286 | const iban = new IBAN("GR1601101250000000012300695");
287 | expect(iban.getCountryCode()).toBe("GR");
288 | expect(iban.getBankCode()).toBe("011");
289 | expect(iban.getBranchCode()).toBe("0125");
290 | expect(iban.getAccountNumber()).toBe("0000000012300695");
291 | });
292 |
293 | it("GT", () => {
294 | const iban = new IBAN("GT82TRAJ01020000001210029690");
295 | expect(iban.getCountryCode()).toBe("GT");
296 | expect(iban.getBankCode()).toBe("TRAJ");
297 | expect(iban.getCurrencyType()).toBe("01");
298 | expect(iban.getAccountType()).toBe("02");
299 | expect(iban.getAccountNumber()).toBe("0000001210029690");
300 | });
301 |
302 | it("HR", () => {
303 | const iban = new IBAN("HR1210010051863000160");
304 | expect(iban.getCountryCode()).toBe("HR");
305 | expect(iban.getBankCode()).toBe("1001005");
306 | expect(iban.getAccountNumber()).toBe("1863000160");
307 | });
308 |
309 | it("HU", () => {
310 | const iban = new IBAN("HU42117730161111101800000000");
311 | expect(iban.getCountryCode()).toBe("HU");
312 | expect(iban.getBankCode()).toBe("117");
313 | expect(iban.getBranchCode()).toBe("7301");
314 | expect(iban.getBranchCheckDigit()).toBe("6");
315 | expect(iban.getAccountNumber()).toBe("111110180000000");
316 | expect(iban.getNationalCheckDigit()).toBe("0");
317 | });
318 |
319 | it("IE", () => {
320 | const iban = new IBAN("IE29AIBK93115212345678");
321 | expect(iban.getCountryCode()).toBe("IE");
322 | expect(iban.getBankCode()).toBe("AIBK");
323 | expect(iban.getBranchCode()).toBe("931152");
324 | expect(iban.getAccountNumber()).toBe("12345678");
325 | });
326 |
327 | it("IL", () => {
328 | const iban = new IBAN("IL620108000000099999999");
329 | expect(iban.getCountryCode()).toBe("IL");
330 | expect(iban.getBankCode()).toBe("010");
331 | expect(iban.getBranchCode()).toBe("800");
332 | expect(iban.getAccountNumber()).toBe("0000099999999");
333 | });
334 |
335 | it("IQ", () => {
336 | const iban = new IBAN("IQ98NBIQ850123456789012");
337 | expect(iban.getCountryCode()).toBe("IQ");
338 | expect(iban.getBankCode()).toBe("NBIQ");
339 | expect(iban.getBranchCode()).toBe("850");
340 | expect(iban.getAccountNumber()).toBe("123456789012");
341 | });
342 |
343 | it("IS", () => {
344 | const iban = new IBAN("IS140159260076545510730339");
345 | expect(iban.getCountryCode()).toBe("IS");
346 | expect(iban.getBankCode()).toBe("0159");
347 | expect(iban.getBranchCode()).toBe("26");
348 | expect(iban.getAccountNumber()).toBe("007654");
349 | expect(iban.getIdentificationNumber()).toBe("5510730339");
350 | });
351 |
352 | it("IT", () => {
353 | const iban = new IBAN("IT60X0542811101000000123456");
354 | expect(iban.getCountryCode()).toBe("IT");
355 | expect(iban.getBankCode()).toBe("05428");
356 | expect(iban.getBranchCode()).toBe("11101");
357 | expect(iban.getAccountNumber()).toBe("000000123456");
358 | });
359 |
360 | it("JO", () => {
361 | const iban = new IBAN("JO94CBJO0010000000000131000302");
362 | expect(iban.getCountryCode()).toBe("JO");
363 | expect(iban.getBankCode()).toBe("CBJO");
364 | expect(iban.getBranchCode()).toBe("0010");
365 | expect(iban.getAccountNumber()).toBe("000000000131000302");
366 | });
367 |
368 | it("KW", () => {
369 | const iban = new IBAN("KW81CBKU0000000000001234560101");
370 | expect(iban.getCountryCode()).toBe("KW");
371 | expect(iban.getBankCode()).toBe("CBKU");
372 | expect(iban.getAccountNumber()).toBe("0000000000001234560101");
373 | });
374 |
375 | it("KZ", () => {
376 | const iban = new IBAN("KZ86125KZT5004100100");
377 | expect(iban.getCountryCode()).toBe("KZ");
378 | expect(iban.getBankCode()).toBe("125");
379 | expect(iban.getAccountNumber()).toBe("KZT5004100100");
380 | });
381 |
382 | it("LB", () => {
383 | const iban = new IBAN("LB62099900000001001901229114");
384 | expect(iban.getCountryCode()).toBe("LB");
385 | expect(iban.getBankCode()).toBe("0999");
386 | expect(iban.getAccountNumber()).toBe("00000001001901229114");
387 | });
388 |
389 | it("LC", () => {
390 | const iban = new IBAN("LC07HEMM000100010012001200013015");
391 | expect(iban.getCountryCode()).toBe("LC");
392 | expect(iban.getBankCode()).toBe("HEMM");
393 | expect(iban.getAccountNumber()).toBe("000100010012001200013015");
394 | });
395 |
396 | it("LI", () => {
397 | const iban = new IBAN("LI21088100002324013AA");
398 | expect(iban.getCountryCode()).toBe("LI");
399 | expect(iban.getBankCode()).toBe("08810");
400 | expect(iban.getAccountNumber()).toBe("0002324013AA");
401 | });
402 |
403 | it("LT", () => {
404 | const iban = new IBAN("LT121000011101001000");
405 | expect(iban.getCountryCode()).toBe("LT");
406 | expect(iban.getBankCode()).toBe("10000");
407 | expect(iban.getAccountNumber()).toBe("11101001000");
408 | });
409 |
410 | it("LU", () => {
411 | const iban = new IBAN("LU280019400644750000");
412 | expect(iban.getCountryCode()).toBe("LU");
413 | expect(iban.getBankCode()).toBe("001");
414 | expect(iban.getAccountNumber()).toBe("9400644750000");
415 | });
416 |
417 | it("LV", () => {
418 | const iban = new IBAN("LV80BANK0000435195001");
419 | expect(iban.getCountryCode()).toBe("LV");
420 | expect(iban.getBankCode()).toBe("BANK");
421 | expect(iban.getAccountNumber()).toBe("0000435195001");
422 | });
423 |
424 | it("LY", () => {
425 | const iban = new IBAN("LY83002048000020100120361");
426 | expect(iban.getCountryCode()).toBe("LY");
427 | expect(iban.getBankCode()).toBe("002");
428 | expect(iban.getBranchCode()).toBe("048");
429 | expect(iban.getAccountNumber()).toBe("000020100120361");
430 | });
431 |
432 | it("MC", () => {
433 | const iban = new IBAN("MC5811222000010123456789030");
434 | expect(iban.getCountryCode()).toBe("MC");
435 | expect(iban.getBankCode()).toBe("11222");
436 | expect(iban.getBranchCode()).toBe("00001");
437 | expect(iban.getAccountNumber()).toBe("01234567890");
438 | expect(iban.getNationalCheckDigit()).toBe("30");
439 | });
440 |
441 | it("MD", () => {
442 | const iban = new IBAN("MD24AG000225100013104168");
443 | expect(iban.getCountryCode()).toBe("MD");
444 | expect(iban.getBankCode()).toBe("AG");
445 | expect(iban.getAccountNumber()).toBe("000225100013104168");
446 | });
447 |
448 | it("ME", () => {
449 | const iban = new IBAN("ME25505000012345678951");
450 | expect(iban.getCountryCode()).toBe("ME");
451 | expect(iban.getBankCode()).toBe("505");
452 | expect(iban.getAccountNumber()).toBe("0000123456789");
453 | expect(iban.getNationalCheckDigit()).toBe("51");
454 | });
455 |
456 | it("MK", () => {
457 | const iban = new IBAN("MK07250120000058984");
458 | expect(iban.getCountryCode()).toBe("MK");
459 | expect(iban.getBankCode()).toBe("250");
460 | expect(iban.getAccountNumber()).toBe("1200000589");
461 | expect(iban.getNationalCheckDigit()).toBe("84");
462 | });
463 |
464 | it("MN", () => {
465 | const iban = new IBAN("MN121234123456789123");
466 | expect(iban.getCountryCode()).toBe("MN");
467 | expect(iban.getBankCode()).toBe("1234");
468 | expect(iban.getAccountNumber()).toBe("123456789123");
469 | });
470 |
471 | it("MR", () => {
472 | const iban = new IBAN("MR1300020001010000123456753");
473 | expect(iban.getCountryCode()).toBe("MR");
474 | expect(iban.getBankCode()).toBe("00020");
475 | expect(iban.getBranchCode()).toBe("00101");
476 | expect(iban.getAccountNumber()).toBe("00001234567");
477 | expect(iban.getNationalCheckDigit()).toBe("53");
478 | });
479 |
480 | it("MT", () => {
481 | const iban = new IBAN("MT84MALT011000012345MTLCAST001S");
482 | expect(iban.getCountryCode()).toBe("MT");
483 | expect(iban.getBankCode()).toBe("MALT");
484 | expect(iban.getBranchCode()).toBe("01100");
485 | expect(iban.getAccountNumber()).toBe("0012345MTLCAST001S");
486 | });
487 |
488 | it("MU", () => {
489 | const iban = new IBAN("MU17BOMM0101101030300200000MUR");
490 | expect(iban.getCountryCode()).toBe("MU");
491 | expect(iban.getBankCode()).toBe("BOMM01");
492 | expect(iban.getBranchCode()).toBe("01");
493 | expect(iban.getAccountNumber()).toBe("101030300200");
494 | expect(iban.getAccountType()).toBe("000");
495 | expect(iban.getCurrencyType()).toBe("MUR");
496 | });
497 |
498 | it("NI", () => {
499 | const iban = new IBAN("NI79BAMC00000000000003123123");
500 | expect(iban.getCountryCode()).toBe("NI");
501 | expect(iban.getBankCode()).toBe("BAMC");
502 | expect(iban.getAccountNumber()).toBe("00000000000003123123");
503 | });
504 |
505 | it("NL", () => {
506 | const iban = new IBAN("NL91ABNA0417164300");
507 | expect(iban.getCountryCode()).toBe("NL");
508 | expect(iban.getBankCode()).toBe("ABNA");
509 | expect(iban.getAccountNumber()).toBe("0417164300");
510 | });
511 |
512 | it("NO", () => {
513 | const iban = new IBAN("NO9386011117947");
514 | expect(iban.getCountryCode()).toBe("NO");
515 | expect(iban.getBankCode()).toBe("8601");
516 | expect(iban.getAccountNumber()).toBe("111794");
517 | expect(iban.getNationalCheckDigit()).toBe("7");
518 | });
519 |
520 | it("OM", () => {
521 | const iban = new IBAN("OM770180010080577149017");
522 | expect(iban.getCountryCode()).toBe("OM");
523 | expect(iban.getBankCode()).toBe("018");
524 | expect(iban.getAccountNumber()).toBe("0010080577149017");
525 |
526 | });
527 |
528 | it("PK", () => {
529 | const iban = new IBAN("PK36SCBL0000001123456702");
530 | expect(iban.getCountryCode()).toBe("PK");
531 | expect(iban.getBankCode()).toBe("SCBL");
532 | expect(iban.getAccountNumber()).toBe("0000001123456702");
533 | });
534 |
535 | it("PL", () => {
536 | const iban = new IBAN("PL61109010140000071219812874");
537 | expect(iban.getCountryCode()).toBe("PL");
538 | expect(iban.getBankCode()).toBe("109");
539 | expect(iban.getBranchCode()).toBe("0101");
540 | expect(iban.getAccountNumber()).toBe("0000071219812874");
541 | expect(iban.getNationalCheckDigit()).toBe("4");
542 | });
543 |
544 | it("PS", () => {
545 | const iban = new IBAN("PS92PALS000000000400123456702");
546 | expect(iban.getCountryCode()).toBe("PS");
547 | expect(iban.getBankCode()).toBe("PALS");
548 | expect(iban.getAccountNumber()).toBe("000000000400123456702");
549 | });
550 |
551 | it("PT", () => {
552 | const iban = new IBAN("PT50000201231234567890154");
553 |
554 | expect(iban.getCountryCode()).toBe("PT");
555 | expect(iban.getBankCode()).toBe("0002");
556 | expect(iban.getBranchCode()).toBe("0123");
557 | expect(iban.getAccountNumber()).toBe("12345678901");
558 | expect(iban.getNationalCheckDigit()).toBe("54");
559 | });
560 |
561 | it("QA", () => {
562 | const iban = new IBAN("QA58DOHB00001234567890ABCDEFG");
563 | expect(iban.getCountryCode()).toBe("QA");
564 | expect(iban.getBankCode()).toBe("DOHB");
565 | expect(iban.getAccountNumber()).toBe("00001234567890ABCDEFG");
566 | });
567 |
568 | it("RO", () => {
569 | const iban = new IBAN("RO49AAAA1B31007593840000");
570 | expect(iban.getCountryCode()).toBe("RO");
571 | expect(iban.getBankCode()).toBe("AAAA");
572 | expect(iban.getAccountNumber()).toBe("1B31007593840000");
573 | });
574 |
575 | it("RS", () => {
576 | const iban = new IBAN("RS35260005601001611379");
577 | expect(iban.getCountryCode()).toBe("RS");
578 | expect(iban.getBankCode()).toBe("260");
579 | expect(iban.getAccountNumber()).toBe("0056010016113");
580 | expect(iban.getNationalCheckDigit()).toBe("79");
581 | });
582 |
583 | it("RU", () => {
584 | const iban = new IBAN("RU0204452560040702810412345678901");
585 | expect(iban.getCountryCode()).toBe("RU");
586 | expect(iban.getBankCode()).toBe("044525600");
587 | expect(iban.getBranchCode()).toBe("40702");
588 | expect(iban.getAccountNumber()).toBe("810412345678901");
589 | });
590 |
591 | it("SA", () => {
592 | const iban = new IBAN("SA0380000000608010167519");
593 | expect(iban.getCountryCode()).toBe("SA");
594 | expect(iban.getBankCode()).toBe("80");
595 | expect(iban.getAccountNumber()).toBe("000000608010167519");
596 | });
597 |
598 | it("SC", () => {
599 | const iban = new IBAN("SC18SSCB11010000000000001497USD");
600 | expect(iban.getCountryCode()).toBe("SC");
601 | expect(iban.getBankCode()).toBe("SSCB");
602 | expect(iban.getBranchCode()).toBe("11");
603 | expect(iban.getBranchCheckDigit()).toBe("01");
604 | expect(iban.getAccountNumber()).toBe("0000000000001497");
605 | });
606 |
607 | it("SD", () => {
608 | const iban = new IBAN("SD8811123456789012");
609 | expect(iban.getCountryCode()).toBe("SD");
610 | expect(iban.getBankCode()).toBe("11");
611 | expect(iban.getAccountNumber()).toBe("123456789012");
612 | });
613 |
614 | it("SE", () => {
615 | const iban = new IBAN("SE4550000000058398257466");
616 | expect(iban.getCountryCode()).toBe("SE");
617 | expect(iban.getBankCode()).toBe("500");
618 | expect(iban.getAccountNumber()).toBe("0000005839825746");
619 | expect(iban.getNationalCheckDigit()).toBe("6");
620 | });
621 |
622 | it("SI", () => {
623 | const iban = new IBAN("SI56263300012039086");
624 | expect(iban.getCountryCode()).toBe("SI");
625 | expect(iban.getBankCode()).toBe("26");
626 | expect(iban.getBranchCode()).toBe("330");
627 | expect(iban.getAccountNumber()).toBe("00120390");
628 | expect(iban.getNationalCheckDigit()).toBe("86");
629 | });
630 |
631 | it("SK", () => {
632 | const iban = new IBAN("SK3112000000198742637541");
633 | expect(iban.getCountryCode()).toBe("SK");
634 | expect(iban.getBankCode()).toBe("1200");
635 | expect(iban.getAccountNumber()).toBe("0000198742637541");
636 | });
637 |
638 | it("SM", () => {
639 | const iban = new IBAN("SM86U0322509800000000270100");
640 | expect(iban.getCountryCode()).toBe("SM");
641 | expect(iban.getBankCode()).toBe("03225");
642 | expect(iban.getBranchCode()).toBe("09800");
643 | expect(iban.getAccountNumber()).toBe("000000270100");
644 | });
645 |
646 | it("SO", () => {
647 | const iban = new IBAN("SO061000001123123456789");
648 | expect(iban.getCountryCode()).toBe("SO");
649 | expect(iban.getBankCode()).toBe("1000");
650 | expect(iban.getBranchCode()).toBe("001");
651 | expect(iban.getAccountNumber()).toBe("123123456789");
652 | });
653 |
654 | it("ST", () => {
655 | const iban = new IBAN("ST68000100010051845310112");
656 | expect(iban.getCountryCode()).toBe("ST");
657 | expect(iban.getBankCode()).toBe("0001");
658 | expect(iban.getBranchCode()).toBe("0001");
659 | expect(iban.getAccountNumber()).toBe("0051845310112");
660 | });
661 |
662 | it("SV", () => {
663 | const iban = new IBAN("SV62CENR00000000000000700025");
664 | expect(iban.getCountryCode()).toBe("SV");
665 | expect(iban.getBankCode()).toBe("CENR");
666 | expect(iban.getBranchCode()).toBe("0000");
667 | expect(iban.getAccountNumber()).toBe("0000000000700025");
668 | });
669 |
670 | it("TL", () => {
671 | const iban = new IBAN("TL380080012345678910157");
672 | expect(iban.getCountryCode()).toBe("TL");
673 | expect(iban.getBankCode()).toBe("008");
674 | expect(iban.getAccountNumber()).toBe("00123456789101");
675 | expect(iban.getNationalCheckDigit()).toBe("57");
676 | });
677 |
678 | it("TN", () => {
679 | const iban = new IBAN("TN5910006035183598478831");
680 | expect(iban.getCountryCode()).toBe("TN");
681 | expect(iban.getBankCode()).toBe("10");
682 | expect(iban.getBranchCode()).toBe("006");
683 | expect(iban.getAccountNumber()).toBe("0351835984788");
684 | expect(iban.getNationalCheckDigit()).toBe("31");
685 | });
686 |
687 | it("TR", () => {
688 | const iban = new IBAN("TR330006100519786457841326");
689 | expect(iban.getCountryCode()).toBe("TR");
690 | expect(iban.getBankCode()).toBe("00061");
691 | expect(iban.getAccountNumber()).toBe("0519786457841326");
692 | expect(iban.getNationalCheckDigit()).toBe("0");
693 | });
694 |
695 | it("UA", () => {
696 | const iban = new IBAN("UA213223130000026007233566001");
697 | expect(iban.getCountryCode()).toBe("UA");
698 | expect(iban.getBankCode()).toBe("322313");
699 | expect(iban.getAccountNumber()).toBe("0000026007233566001");
700 | });
701 |
702 | it("VA", () => {
703 | const iban = new IBAN("VA59001123000012345678");
704 | expect(iban.getCountryCode()).toBe("VA");
705 | expect(iban.getBankCode()).toBe("001");
706 | expect(iban.getAccountNumber()).toBe("123000012345678");
707 | });
708 |
709 | it("VG", () => {
710 | const iban = new IBAN("VG96VPVG0000012345678901");
711 | expect(iban.getCountryCode()).toBe("VG");
712 | expect(iban.getBankCode()).toBe("VPVG");
713 | expect(iban.getAccountNumber()).toBe("0000012345678901");
714 | });
715 |
716 | it("XK", () => {
717 | const iban = new IBAN("XK051212012345678906");
718 | expect(iban.getCountryCode()).toBe("XK");
719 | expect(iban.getBankCode()).toBe("12");
720 | expect(iban.getBranchCode()).toBe("12");
721 | expect(iban.getAccountNumber()).toBe("0123456789");
722 | expect(iban.getNationalCheckDigit()).toBe("06");
723 | });
724 | });
725 |
726 | describe("provisional countries", () => {
727 | it("AO", () => {
728 | const iban = new IBAN("AO69123456789012345678901");
729 | expect(iban.getCountryCode()).toBe("AO");
730 | });
731 |
732 | it("BF", () => {
733 | const iban = new IBAN("BF2312345678901234567890123");
734 | expect(iban.getCountryCode()).toBe("BF");
735 | });
736 |
737 | it("BJ", () => {
738 | const iban = new IBAN("BJ11B00610100400271101192591");
739 | expect(iban.getCountryCode()).toBe("BJ");
740 |
741 | const iban2 = new IBAN("BJ66BJ0610100100144390000769");
742 | expect(iban2.getCountryCode()).toBe("BJ");
743 | });
744 |
745 | it("CF", () => {
746 | const iban = new IBAN("CF4220001000010120069700160");
747 | expect(iban.getCountryCode()).toBe("CF");
748 | });
749 |
750 | it("CI", () => {
751 | const iban = new IBAN("CI93CI0080111301134291200589");
752 | expect(iban.getCountryCode()).toBe("CI");
753 | });
754 |
755 | it("CM", () => {
756 | const iban = new IBAN("CM9012345678901234567890123");
757 | expect(iban.getCountryCode()).toBe("CM");
758 | });
759 |
760 | it("CV", () => {
761 | const iban = new IBAN("CV30123456789012345678901");
762 | expect(iban.getCountryCode()).toBe("CV");
763 | });
764 |
765 | it("DJ", () => {
766 | const iban = new IBAN("DJ2110002010010409943020008");
767 | expect(iban.getCountryCode()).toBe("DJ");
768 | });
769 |
770 | it("DZ", () => {
771 | const iban = new IBAN("DZ8612345678901234567890");
772 | expect(iban.getCountryCode()).toBe("DZ");
773 | });
774 |
775 | it("GQ", () => {
776 | const iban = new IBAN("GQ7050002001003715228190196");
777 | expect(iban.getCountryCode()).toBe("GQ");
778 | });
779 |
780 | it("HN", () => {
781 | const iban = new IBAN("HN54PISA00000000000000123124");
782 | expect(iban.getCountryCode()).toBe("HN");
783 | });
784 |
785 | it("IR", () => {
786 | const iban = new IBAN("IR861234568790123456789012");
787 | expect(iban.getCountryCode()).toBe("IR");
788 | });
789 |
790 | it("MG", () => {
791 | const iban = new IBAN("MG1812345678901234567890123");
792 | expect(iban.getCountryCode()).toBe("MG");
793 | });
794 |
795 | // Conflicting information
796 | // it("ML", () => {
797 | // const iban = new IBAN("ML1300016012010260010066849700");
798 | // expect(iban.getCountryCode()).toBe("ML");
799 | // });
800 |
801 | it("MZ", () => {
802 | const iban = new IBAN("MZ25123456789012345678901");
803 | expect(iban.getCountryCode()).toBe("MZ");
804 | });
805 |
806 | it("SN", () => {
807 | const iban = new IBAN("SN52A12345678901234567890123");
808 | expect(iban.getCountryCode()).toBe("SN");
809 | });
810 |
811 | // ----
812 | it("KM", () => {
813 | const iban = new IBAN("KM4600005000010010904400137");
814 | expect(iban.getCountryCode()).toBe("KM");
815 | });
816 |
817 | it("TD", () => {
818 | const iban = new IBAN("TD8960002000010271091600153");
819 | expect(iban.getCountryCode()).toBe("TD");
820 | });
821 |
822 | it("CG", () => {
823 | const iban = new IBAN("CG3930011000101013451300019");
824 | expect(iban.getCountryCode()).toBe("CG");
825 | });
826 |
827 | it("GA", () => {
828 | const iban = new IBAN("GA2140021010032001890020126");
829 | expect(iban.getCountryCode()).toBe("GA");
830 | });
831 |
832 | it("MA", () => {
833 | const iban = new IBAN("MA64011519000001205000534921");
834 | expect(iban.getCountryCode()).toBe("MA");
835 | });
836 |
837 | it("NE", () => {
838 | const iban = new IBAN("NE58NE0380100100130305000268");
839 | expect(iban.getCountryCode()).toBe("NE");
840 | });
841 |
842 | it("TG", () => {
843 | const iban = new IBAN("TG53TG0090604310346500400070");
844 | expect(iban.getCountryCode()).toBe("TG");
845 | });
846 | });
847 |
848 | describe("national check digits - failures", () => {
849 | it("NO", () => {
850 | expect(() => new IBAN("NO9386011117948")).toThrow();
851 | });
852 |
853 | it("BE", () => {
854 | expect(() => new IBAN("BE68539007547035")).toThrow();
855 | });
856 |
857 | it("FR", () => {
858 | expect(() => new IBAN("FR1420041010050500013M02607")).toThrow();
859 | });
860 |
861 | it("BJ", () => {
862 | expect(() => new IBAN("BJ66BJ0610100100144390000760")).toThrow();
863 | });
864 | });
865 |
866 | describe("sample value", () => {
867 | it("FR", () => {
868 | expect(IBAN.sample("FR")).toBe("FR1420041010050500013M02606");
869 | });
870 |
871 | it("germany as default", () => {
872 | expect(IBAN.sample("XX")).toBe("DE89370400440532013000");
873 | });
874 | });
875 |
876 | describe("iban-js compatibility", () => {
877 | it("printFormat", () => {
878 | expect(IBAN.printFormat("FR1420041010050500013M02606")).toBe("FR14 2004 1010 0505 0001 3M02 606");
879 | });
880 |
881 | it("electronicFormat", () => {
882 | expect(IBAN.electronicFormat(" FR14*2&004 1010050500013M02606*")).toBe("FR1420041010050500013M02606");
883 | });
884 |
885 | it("toBBAN", () => {
886 | expect(IBAN.toBBAN(" FR142004 1010050500013M02606*")).toBe("20041 01005 0500013M026 06");
887 | });
888 |
889 | it("fromBBAN", () => {
890 | expect(IBAN.fromBBAN("FR", "20041010050500013M02606")).toBe("FR1420041010050500013M02606");
891 | });
892 |
893 | it("isValidBBAN", () => {
894 | expect(IBAN.isValidBBAN("FR", "20041010050500013M02606")).toBe(true);
895 | });
896 | });
897 | });
898 |
--------------------------------------------------------------------------------
/test/ibanBuilder.spec.ts:
--------------------------------------------------------------------------------
1 | import { IBANBuilder, IBAN, CountryCode } from "../src";
2 |
3 | describe("IBANBuilder", () => {
4 | it("NO random test", () => {
5 | const iban = new IBANBuilder()
6 | .countryCode(CountryCode.NO)
7 | .bankCode("8601")
8 | .build()
9 | .toString();
10 |
11 | expect(IBAN.isValid(iban)).toBe(true);
12 | });
13 |
14 | it("NO random test", () => {
15 | const iban = new IBANBuilder()
16 | .countryCode(CountryCode.NO)
17 | .bankCode("8601")
18 | .accountNumber("111794")
19 | .build()
20 | .toString();
21 |
22 | expect(IBAN.isValid(iban)).toBe(true);
23 | });
24 |
25 | it("BJ test", () => {
26 | const iban = new IBANBuilder()
27 | .countryCode(CountryCode.BJ)
28 | .bankCode("BJ104")
29 | .branchCode("01003")
30 | .accountNumber("035033423001")
31 | .build()
32 | .toString();
33 |
34 | expect(IBAN.isValid(iban)).toBe(true);
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/tsconfig-cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "CommonJS",
5 | "esModuleInterop": true,
6 | "outDir": "./lib/cjs"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig-lint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "CommonJS",
5 | "outDir": "./lib/cjs"
6 | },
7 | "exclude": ["lib", "node_modules"]
8 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "moduleResolution": "node",
4 | "skipLibCheck": true,
5 | "importHelpers": false,
6 | "target": "es6",
7 | "removeComments": true,
8 | "module": "ES6",
9 | "outDir": "lib/esm",
10 | "strict": true,
11 | "resolveJsonModule": true,
12 | "allowSyntheticDefaultImports": true,
13 | "emitDecoratorMetadata": true,
14 | "declaration": true,
15 | "sourceMap": false,
16 | "inlineSourceMap": true,
17 | "preserveConstEnums": true,
18 | "noImplicitAny": true,
19 | "noEmitOnError": true,
20 | "noEmitHelpers": false,
21 | "noFallthroughCasesInSwitch": true,
22 | "noImplicitReturns": true,
23 | "noImplicitThis": true,
24 | "experimentalDecorators": true,
25 | "strictNullChecks": true,
26 | "pretty": true,
27 | "forceConsistentCasingInFileNames": true,
28 | "noUnusedLocals": true,
29 | "jsx": "react",
30 | "lib": ["es2017", "es2016", "dom"],
31 | "baseUrl": ".",
32 | "paths": {
33 | "*": ["src/*", "node_modules/*"]
34 | }
35 | },
36 | "exclude": ["lib", "node_modules", "**/*.spec.*"],
37 | "files": ["src/index.ts"]
38 | }
39 |
--------------------------------------------------------------------------------