├── .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 | [![npm version](https://badge.fury.io/js/ibankit.svg)](https://badge.fury.io/js/ibankit) 4 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](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 | --------------------------------------------------------------------------------