├── .commitlintrc.cjs ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── .gitignore ├── commit-msg └── pre-commit ├── .prettierrc.js ├── CHANGELOG.md ├── LICENSE ├── README.md ├── __tests__ ├── luhn.test.ts ├── validator.test.ts ├── verhoeff.test.ts └── vpa.test.ts ├── commitlint.config.cjs ├── dist ├── luhn.d.ts ├── luhn.js ├── validator.d.ts ├── validator.js ├── verhoeff.d.ts ├── verhoeff.js ├── vpa-handles.json ├── vpa.d.ts └── vpa.js ├── eslint.config.mjs ├── jest.config.js ├── lint-staged.config.js ├── package-lock.json ├── package.json ├── src ├── luhn.ts ├── validator.ts ├── verhoeff.ts ├── vpa-handles.json └── vpa.ts ├── tsconfig.json └── vitest.config.ts /.commitlintrc.cjs: -------------------------------------------------------------------------------- 1 | const configConventional = require('@commitlint/config-conventional'); 2 | 3 | const typeEnum = configConventional.rules['type-enum']; 4 | typeEnum[2].push('init'); 5 | 6 | module.exports = { 7 | extends: [ 8 | '@commitlint/config-conventional' 9 | ], 10 | rules: { 11 | 'type-enum': typeEnum, 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "06:30" 8 | timezone: Asia/Kolkata 9 | open-pull-requests-limit: 20 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: CI 5 | 6 | on: [push] 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: ['22.x'] 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm ci 24 | - run: npm run lint --if-present 25 | - run: npm run build --if-present 26 | - run: npm test 27 | env: 28 | CI: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | .parcel-cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Stores VSCode versions used for testing VSCode extensions 107 | .vscode-test 108 | 109 | # yarn v2 110 | 111 | .yarn/cache 112 | .yarn/unplugged 113 | .yarn/build-state.yml 114 | .pnp.* -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --config commitlint.config.cjs --edit "$1" 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | trailingComma: 'all', 3 | singleQuote: true, 4 | printWidth: 120 5 | }; -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [0.0.11](https://github.com/mastermunj/format-utils/compare/v0.0.10...v0.0.11) (2025-03-18) 6 | 7 | ### [0.0.10](https://github.com/mastermunj/format-utils/compare/v0.0.9...v0.0.10) (2024-12-17) 8 | 9 | ### [0.0.9](https://github.com/mastermunj/format-utils/compare/v0.0.8...v0.0.9) (2024-03-13) 10 | 11 | ### [0.0.8](https://github.com/mastermunj/format-utils/compare/v0.0.7...v0.0.8) (2022-12-15) 12 | 13 | ### [0.0.7](https://github.com/mastermunj/format-utils/compare/v0.0.6...v0.0.7) (2021-09-04) 14 | 15 | ### [0.0.6](https://github.com/mastermunj/format-utils/compare/v0.0.5...v0.0.6) (2021-03-22) 16 | 17 | ### [0.0.5](https://github.com/mastermunj/format-utils/compare/v0.0.4...v0.0.5) (2020-12-11) 18 | 19 | 20 | ### Bug Fixes 21 | 22 | * copy vpa-handles.json file to dist ([ce5cff0](https://github.com/mastermunj/format-utils/commit/ce5cff006278e3cfb277f4b7c48b125bc48de6c4)) 23 | 24 | ### [0.0.4](https://github.com/mastermunj/format-utils/compare/v0.0.3...v0.0.4) (2020-09-22) 25 | 26 | ### [0.0.3](https://github.com/mastermunj/format-utils/compare/v0.0.2...v0.0.3) (2020-05-07) 27 | 28 | ### [0.0.2](https://github.com/mastermunj/format-utils/compare/v0.0.1...v0.0.2) (2020-04-28) 29 | 30 | 31 | ### Features 32 | 33 | * added VPA validation ([d843c51](https://github.com/mastermunj/format-utils/commit/d843c51214088efd990c003469786d47ac00821c)) 34 | 35 | ### 0.0.1 (2020-04-27) 36 | 37 | 38 | ### Features 39 | 40 | * added AADHAAR & AADHAAR VID validations ([47a3d6d](https://github.com/mastermunj/format-utils/commit/47a3d6da1ec1ccaf19cf7388078826d55283f6bb)) 41 | * added GST validation ([3f67b97](https://github.com/mastermunj/format-utils/commit/3f67b97eccb1176ed3b08f72f0fb6b43e19577fd)) 42 | * added imei validator ([76f6c16](https://github.com/mastermunj/format-utils/commit/76f6c1691437557fcca955f0f634266351eeb762)) 43 | * added validation for vehicle registration number ([c643d5b](https://github.com/mastermunj/format-utils/commit/c643d5b2abd7f51554ef250475dd87e96d6b2d86)) 44 | * added validators for mobile & pincode ([da31838](https://github.com/mastermunj/format-utils/commit/da318380b13af975df8da269f29d85ee50899738)) 45 | * added validators for PAN, TAN, UAN, IFSC and ESIC ([ac7626e](https://github.com/mastermunj/format-utils/commit/ac7626e599236822fcebc1da3a6e2ca06a9195d4)) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * remove dist from gitignore and added in repo ([bc53c3b](https://github.com/mastermunj/format-utils/commit/bc53c3bffbf40f95411aefee5380e08783ff7626)) 51 | * typo in package.json ([47fcf8a](https://github.com/mastermunj/format-utils/commit/47fcf8a89f025eb7a747752b4c7e33441f2223f9)) 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Munjal Dhamecha 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Format Validation Utilities 2 | 3 | ## Introduction 4 | 5 | Utilities for validating various formats of Indian system codes like Mobile, PAN, AADHAAR, GST and more! 6 | 7 | ## Installation 8 | 9 | ```js 10 | npm install format-utils --save 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | const { Validator } = require('format-utils'); 17 | ``` 18 | OR 19 | ```js 20 | import { Validator } from 'format-utils'; 21 | ``` 22 | 23 | ## Available Validators 24 | 25 | ### Mobile 26 | 27 | A mobile is a 10 digit numerical code starting with either 6, 7, 8, 9. 28 | 29 | ```js 30 | let isValid = Validator.mobile('9876543210'); 31 | // isValid = true 32 | 33 | isValid = Validator.mobile('5678943210'); 34 | // isValid = false 35 | ``` 36 | 37 | ### PIN (Postal Index Number) 38 | 39 | A pincode is a 6 digit numeric code used by Indian Post. 40 | 41 | #### Format 42 | * The first character is a number from `1` to `9`. 43 | * The second to sixth characters are numberical sequence from `00000` to `99999`. 44 | 45 | ```js 46 | let isValid = Validator.pincode('400001'); 47 | // isValid = true 48 | 49 | isValid = Validator.pincode('0123456'); 50 | // isValid = false 51 | ``` 52 | 53 | ### PAN (Permanent Account Number) 54 | 55 | A PAN is a 10 digit alphanumeric code issued by Income Tax Department of India. 56 | 57 | #### Format 58 | * The first three characters are alphabetic series running from `AAA` to `ZZZ`. 59 | * The fourth character represents the status of the PAN holder. 60 | * `P` stands for Individual 61 | * `C` stands for Company 62 | * `H` stands for Hindu Undivided Family (HUF) 63 | * `A` stands for Association of Persons (AOP) 64 | * `B` stands for Body of Individuals (BOI) 65 | * `G` stands for Government Agency 66 | * `J` stands for Artificial Juridical Person 67 | * `L` stands for Local Authority 68 | * `F` stands for Firm/ Limited Liability Partnership 69 | * `T` stands for Trust 70 | * The fifth character represents the first character of the PAN holder's last name/surname in case of an individual. In case of non-individual PAN holders fifth character represents the first character of PAN holder's name. 71 | * The sixth to ninth characters are sequential numbers running from `0001` to `9999`. 72 | * The tenth character is an alphabetic check digit. 73 | 74 | Visit [this](https://www.incometaxindia.gov.in/Forms/tps/1.Permanent%20Account%20Number%20(PAN).pdf) to know more about PAN. 75 | 76 | ```js 77 | let isValid = Validator.pan('ALWPG5809L'); 78 | // isValid = true 79 | 80 | isValid = Validator.pan('ABAB12345Y'); 81 | // isValid = false 82 | ``` 83 | 84 | ### TAN (Tax Deduction and Collection Account Number) 85 | 86 | A TAN is a 10 digit alphanumeric code. 87 | 88 | #### Format 89 | * The first four characters are alphabetic series running from `AAAA` to `ZZZZ`. 90 | * The fifth to ninth characters are sequential numbers running from `00001` to `99999`. 91 | * The tenth character is an alphabetic character. 92 | 93 | ```js 94 | let isValid = Validator.tan('RAJA99999B'); 95 | // isValid = true 96 | 97 | isValid = Validator.tan('RAJA999991'); 98 | // isValid = false 99 | ``` 100 | 101 | ### UAN (Universal Account Number) 102 | 103 | A UAN is a 12 digit numberic code that is issued to member of the Employees’ Provident Fund Organisation (EPFO). 104 | 105 | ```js 106 | let isValid = Validator.uan('987654321098'); 107 | // isValid = true 108 | 109 | isValid = Validator.uan('A98765432109'); 110 | // isValid = false 111 | ``` 112 | 113 | ### IFSC (Indian Financial System Code) 114 | 115 | A IFSC is a 11 digit alphanumberic code that is issued to member of the Employees’ Provident Fund Organisation (EPFO). 116 | 117 | #### Format 118 | * The first four characters are alphabets that denote the bank name. 119 | * The fifth character is numerical zero (`0`). 120 | * The sixth to eleventh characters are numerical code that denote the branch name. 121 | 122 | 123 | ```js 124 | let isValid = Validator.ifsc('SBIN0011569'); 125 | // isValid = true 126 | 127 | isValid = Validator.ifsc('BK1D0006046'); 128 | // isValid = false 129 | ``` 130 | 131 | ### ESIC (Employee State Insurance Corporation) Code 132 | 133 | A ESIC code is a 17 digit numerical code that is issued by Employee State Insurance Corporation. 134 | 135 | ```js 136 | let isValid = Validator.esic('12345678901234567'); 137 | // isValid = true 138 | 139 | isValid = Validator.esic('1234567890123456'); 140 | // isValid = false 141 | ``` 142 | 143 | ### IMEI (International Mobile Equipment Identity) 144 | 145 | A IMEI is a 15 digit numeric code to identify mobile phones, as well as some satellite phones. 146 | The last digit of IMEI is a Luhn check digit. 147 | 148 | ```js 149 | let isValid = Validator.imei('490154203237518'); 150 | // isValid = true 151 | 152 | isValid = Validator.imei('490154203237519'); 153 | // isValid = false 154 | ``` 155 | 156 | ### AADHAAR 157 | 158 | Aadhaar is a 12 digit numberic code that can be obtained by residents or passport holders of India, based on their biometric and demographic data. 159 | 160 | #### Format 161 | * The first character is a number between `2` and `9`. 162 | * The second to eleventh characters are random numbers. 163 | * The twelfth character is a Verhoeff check digit. 164 | 165 | ```js 166 | let isValid = Validator.aadhaar('234567890124'); 167 | // isValid = true 168 | 169 | isValid = Validator.aadhaar('187654321096'); 170 | // isValid = false 171 | ``` 172 | 173 | ### AADHAAR VID (Aadhaar Virtual ID) 174 | 175 | Aadhaar VID is a 16 digit numberic code that can be used instead of Aadhaar number at the time of authentication to avoid sharing of Aadhaar number. 176 | The last digit is a Verhoeff check digit. 177 | 178 | ```js 179 | let isValid = Validator.aadhaarVID('9876543210987659'); 180 | // isValid = true 181 | 182 | isValid = Validator.aadhaarVID('6234897234982734'); 183 | // isValid = false 184 | ``` 185 | 186 | ### GSTIN (Goods & Services Tax Identification Number) 187 | 188 | A GISTIN is a 15 digit alphanumeric code assigned to a business or person registered under the GST Act. 189 | 190 | #### Format 191 | * The first two characters are numerical series from `01` to `37` denoting state code. 192 | * The third to twelfth characters are [PAN](#pan-permanent-account-number) number of the GST registered entity. 193 | * The thirteenth character is a alphabet assigned based on the number of registration within a state. 194 | * The fourteenth character is `Z` by default. 195 | * The fifteenth character is a check codeand can be an alphabet or a number. 196 | 197 | ```js 198 | let isValid = Validator.gst('22ALJPT5243L1ZS'); 199 | // isValid = true 200 | 201 | isValid = Validator.gst('22ALJPT5243L1ZB'); 202 | // isValid = false 203 | ``` 204 | 205 | ### Vehicle Registration (Number Plate) 206 | 207 | A vehicle number plate is an alphanumeric code assigned to a vehicle registered in India. 208 | 209 | The current format of the registration index consists of 4 parts. 210 | 211 | #### Format 212 | * The first two characters are alphanumeric code of the State / Union Territory where the vehicle is registered. 213 | * The third & fourth characters the sequential number of a district. 214 | * The third part consists of one, two or three letters or no letters at all. 215 | * The fourth part is a number from 1 to 9999, unique to each plate. 216 | 217 | ```js 218 | let isValid = Validator.vehicleRegistration('DL4CAF4943'); 219 | // isValid = true 220 | 221 | isValid = Validator.vehicleRegistration('DL4CAF494G'); 222 | // isValid = false 223 | ``` 224 | 225 | ### VPA (Virtual Payment Address) 226 | 227 | A VPA / UPI (Unified Payment Interface) ID is a unique id generated for use of UPI in India. 228 | 229 | #### Format 230 | * VPA consists of alphabets, numbers, hyphen (`-`), underscore (`_`) and dot (`.`) as part of identification. 231 | * The identification part is followed by `@` sign. 232 | * The last part is the handle of the issuer bank or PSP (Payment Service Provider). 233 | * The maximum length of VPA is 50 characters. 234 | 235 | #### Options 236 | | Option | Type | Default | Description | 237 | | ------------- | ------------- | ------------- | ------------- | 238 | | maxLength | number | 50 | Maximum length of the VPA address including `@` sign & the handle. | 239 | | handles | boolean \| string[] | false | Whether to do additional check of verifying the handle.
When it is `true` the handle part is checked against default handles listed in [vpa-handles.json](./src/vpa-handles.json).
When it is `string[]` the handle part is checked against merged list of default handles listed in [vpa-handles.json](./src/vpa-handles.json) and the ones given as input. | 240 | 241 | ```js 242 | let isValid = Validator.vpa('amazing-uid@upi'); 243 | // isValid = true 244 | 245 | isValid = Validator.vpa('with@at@upi'); 246 | // isValid = false 247 | ``` 248 | -------------------------------------------------------------------------------- /__tests__/luhn.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { Luhn } from '../src/luhn'; 3 | 4 | describe('Luhn', () => { 5 | const tests: [string, boolean][] = [ 6 | ['378282246310005', true], 7 | ['371449635398431', true], 8 | ['378734493671000', true], 9 | ['5610591081018250', true], 10 | ['30569309025904', true], 11 | ['38520000023237', true], 12 | ['6011111111111117', true], 13 | ['6011000990139424', true], 14 | ['3530111333300000', true], 15 | ['3566002020360505', true], 16 | ['5555555555554444', true], 17 | ['5105105105105100', true], 18 | ['4111111111111111', true], 19 | ['4012888888881881', true], 20 | ['4222222222222', true], 21 | ['5019717010103742', true], 22 | ['6331101999990016', true], 23 | 24 | ['378282246310006', false], 25 | ['371449635398432', false], 26 | ['378734493671001', false], 27 | ['5610591081018251', false], 28 | ['30569309025905', false], 29 | ['38520000023238', false], 30 | ['6011111111111118', false], 31 | ['6011000990139425', false], 32 | ['3530111333300001', false], 33 | ['3566002020360506', false], 34 | ['5555555555554445', false], 35 | ['5105105105105101', false], 36 | ['4111111111111112', false], 37 | ['4012888888881882', false], 38 | ['4222222222223', false], 39 | ['5019717010103743', false], 40 | ['6331101999990017', false], 41 | ]; 42 | test.each(tests)(`Check %s => %s`, (value, expected) => { 43 | expect(Luhn.validate(value)).toBe(expected); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /__tests__/validator.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { Validator } from '../src/validator'; 3 | 4 | describe('Mobile', () => { 5 | const tests: [string, boolean][] = [ 6 | ['9876543210', true], 7 | ['8765432109', true], 8 | ['7654321098', true], 9 | ['6543210987', true], 10 | ['5432109876', false], 11 | ['4321098765', false], 12 | ['3210987654', false], 13 | ['2109876543', false], 14 | ['1098765432', false], 15 | ['0987654321', false], 16 | ['987654321', false], 17 | ['a987654321', false], 18 | ['987654321a', false], 19 | ['9876 54321', false], 20 | ]; 21 | test.each(tests)(`Check %s => %s`, (value, expected) => { 22 | expect(Validator.mobile(value)).toBe(expected); 23 | }); 24 | }); 25 | 26 | describe('Pincode', () => { 27 | const tests: [string, boolean][] = [ 28 | ['403294', true], 29 | ['110014', true], 30 | ['600036', true], 31 | ['a12345', false], 32 | ['6345678', false], 33 | ['012345', false], 34 | ]; 35 | test.each(tests)(`Check %s => %s`, (value, expected) => { 36 | expect(Validator.pincode(value)).toBe(expected); 37 | }); 38 | }); 39 | 40 | describe('PAN', () => { 41 | const tests: [string, boolean][] = [ 42 | ['ALWPG5809L', true], 43 | ['alwpg5809l', true], 44 | ['ABAB12345Y', false], 45 | ['abab12345y', false], 46 | ]; 47 | test.each(tests)(`Check %s => %s`, (value, expected) => { 48 | expect(Validator.pan(value)).toBe(expected); 49 | }); 50 | }); 51 | 52 | describe('TAN', () => { 53 | const tests: [string, boolean][] = [ 54 | ['RAJA99999B', true], 55 | ['AAAA99999A', true], 56 | ['BLRW39567H', true], 57 | ['blrw39567h', true], 58 | 59 | ['RAJA999991', false], 60 | ['RAJA9999WB', false], 61 | ['R2JA9999WB', false], 62 | ['R2JA9999B', false], 63 | ['R2JA9999WB1', false], 64 | ]; 65 | test.each(tests)(`Check %s => %s`, (value, expected) => { 66 | expect(Validator.tan(value)).toBe(expected); 67 | }); 68 | }); 69 | 70 | describe('UAN', () => { 71 | const tests: [string, boolean][] = [ 72 | ['987654321098', true], 73 | ['123456789012', true], 74 | 75 | ['98765432101', false], 76 | ['9876543210987', false], 77 | ['A98765432109', false], 78 | ['98765432109A', false], 79 | ]; 80 | test.each(tests)(`Check %s => %s`, (value, expected) => { 81 | expect(Validator.uan(value)).toBe(expected); 82 | }); 83 | }); 84 | 85 | describe('IFSC', () => { 86 | const tests: [string, boolean][] = [ 87 | ['SBIN0011569', true], 88 | ['BKID0006046', true], 89 | ['BKID000604D', true], 90 | 91 | ['BKID000604', false], 92 | ['BKID1006046', false], 93 | ['BK1D0006046', false], 94 | ['BKID00060461', false], 95 | ['BKID0006046D', false], 96 | ]; 97 | test.each(tests)(`Check %s => %s`, (value, expected) => { 98 | expect(Validator.ifsc(value)).toBe(expected); 99 | }); 100 | }); 101 | 102 | describe('ESIC', () => { 103 | const tests: [string, boolean][] = [ 104 | ['12345678901234567', true], 105 | 106 | ['123456789012345678', false], 107 | ['1234567890123456', false], 108 | ['A2345678901234567', false], 109 | ]; 110 | test.each(tests)(`Check %s => %s`, (value, expected) => { 111 | expect(Validator.esic(value)).toBe(expected); 112 | }); 113 | }); 114 | 115 | describe('IMEI', () => { 116 | const tests: [string, boolean][] = [ 117 | ['49-015420-323751-8', true], 118 | ['490154203237518', true], 119 | ['356938035643809', true], 120 | 121 | ['49-015420-323751-9', false], 122 | ['490154203237519', false], 123 | ['4901542032375191', false], 124 | ['49015420323751', false], 125 | ['a49015420323751', false], 126 | ['49015420323751a', false], 127 | ]; 128 | test.each(tests)(`Check %s => %s`, (value, expected) => { 129 | expect(Validator.imei(value)).toBe(expected); 130 | }); 131 | }); 132 | 133 | describe('Aadhaar', () => { 134 | const tests: [string, boolean][] = [ 135 | ['234567890124', true], 136 | ['987654321096', true], 137 | 138 | ['a87654321096', false], 139 | ['98765432109a', false], 140 | ['087654321096', false], 141 | ['187654321096', false], 142 | ['9876543210967', false], 143 | ['98765432109', false], 144 | ]; 145 | test.each(tests)(`Check %s => %s`, (value, expected) => { 146 | expect(Validator.aadhaar(value)).toBe(expected); 147 | }); 148 | }); 149 | 150 | describe('Aadhaar VID', () => { 151 | const tests: [string, boolean][] = [ 152 | ['9876543210987659', true], 153 | ['6234897234982733', true], 154 | 155 | ['9876543210987656', false], 156 | ['6234897234982734', false], 157 | ['a876543210987659', false], 158 | ['987654321098765a', false], 159 | ['987654321098765', false], 160 | ['98765432109876591', false], 161 | ]; 162 | test.each(tests)(`Check %s => %s`, (value, expected) => { 163 | expect(Validator.aadhaarVID(value)).toBe(expected); 164 | }); 165 | }); 166 | 167 | describe('GST', () => { 168 | const tests: [string, boolean][] = [ 169 | ['22ALJPT5243L1ZS', true], 170 | ['18AABCT3518Q1ZV', true], 171 | ['37AADCB2230M2ZR', true], 172 | 173 | ['22ALJPT5243L1ZB', false], 174 | ['38AABCT3518Q1ZV', false], 175 | ['47AADCB2230M2ZR', false], 176 | ['47AADCB2230M2ZRT', false], 177 | ['47AADCB2230M2Z', false], 178 | ['47AAD CB2230M2Z', false], 179 | ['47AAD-CB2230M2Z', false], 180 | ]; 181 | test.each(tests)(`Check %s => %s`, (value, expected) => { 182 | expect(Validator.gst(value)).toBe(expected); 183 | }); 184 | 185 | test.each(tests)(`Check GST Checksum %s => %s`, (value, expected) => { 186 | expect(Validator.gstChecksum(value)).toBe(expected); 187 | }); 188 | }); 189 | 190 | describe('Vehicle Registration Number', () => { 191 | const tests: [string, boolean][] = [ 192 | ['DL4CAF4943', true], 193 | ['GJ5CL2213', true], 194 | ['GJ 5 CL 2213', true], 195 | ['KL 01 CK 1', true], 196 | ['TN 58 N 4006', true], 197 | 198 | ['DL4CAF494G', false], 199 | ['DL4CANF4943', false], 200 | ['1DL4CAF4943', false], 201 | ]; 202 | test.each(tests)(`Check %s => %s`, (value, expected) => { 203 | expect(Validator.vehicleRegistration(value)).toBe(expected); 204 | }); 205 | }); 206 | 207 | describe('VPA', () => { 208 | const tests: [string, boolean][] = [ 209 | ['amazing-uid@upi', true], 210 | ['random9999@upi', true], 211 | 212 | ['axisbank@bankaxis', false], 213 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false], 214 | ]; 215 | test.each(tests)(`Check %s => %s`, (value, expected) => { 216 | expect(Validator.vpa(value)).toBe(expected); 217 | }); 218 | }); 219 | -------------------------------------------------------------------------------- /__tests__/verhoeff.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { Verhoeff } from '../src/verhoeff'; 3 | 4 | describe('Verhoeff', () => { 5 | const tests: [string, boolean][] = [ 6 | ['98765432101234567897', true], 7 | ['45908745543341', true], 8 | 9 | ['98765432101234567896', false], 10 | ['a98765432101234567897', false], 11 | ['45908745543343', false], 12 | ]; 13 | test.each(tests)(`Check %s => %s`, (value, expected) => { 14 | expect(Verhoeff.validate(value)).toBe(expected); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /__tests__/vpa.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { VPA } from '../src/vpa'; 3 | 4 | describe('VPA', () => { 5 | const tests: [string, boolean][] = [ 6 | ['amazing-uid@upi', true], 7 | ['random9999@upi', true], 8 | ['vpa_with_underscore@upi', true], 9 | ['vpa-with-hyphen@upi', true], 10 | ['vpa.with.dot@upi', true], 11 | ['9876543210@upi', true], 12 | 13 | ['it@upi', false], 14 | ['with#hash@upi', false], 15 | ['with space@upi', false], 16 | ['with@at@upi', false], 17 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false], 18 | ]; 19 | test.each(tests)(`Check %s => %s`, (value, expected) => { 20 | expect(VPA.validate(value)).toBe(expected); 21 | }); 22 | }); 23 | 24 | describe('VPA with options: { maxLength: 20 }', () => { 25 | const tests: [string, boolean][] = [ 26 | ['amazing-uid@upi', true], 27 | ['random9999@upi', true], 28 | 29 | ['vpa_with_underscore@upi', false], 30 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false], 31 | ]; 32 | test.each(tests)(`Check %s => %s`, (value, expected) => { 33 | expect(VPA.validate(value, { maxLength: 20 })).toBe(expected); 34 | }); 35 | }); 36 | 37 | describe('VPA with options: { handles: true }', () => { 38 | const tests: [string, boolean][] = [ 39 | ['amazing-uid@upi', true], 40 | ['random9999@upi', true], 41 | ['yesbank@ybl', true], 42 | ['axisbank@okaxis', true], 43 | 44 | ['axisbank@bankaxis', false], 45 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false], 46 | ]; 47 | test.each(tests)(`Check %s => %s`, (value, expected) => { 48 | expect(VPA.validate(value, { handles: true })).toBe(expected); 49 | }); 50 | }); 51 | 52 | describe('VPA with options: { handles: ["bankaxis", "superbank"] }', () => { 53 | const tests: [string, boolean][] = [ 54 | ['amazing-uid@upi', true], 55 | ['random9999@upi', true], 56 | ['yesbank@ybl', true], 57 | ['axisbank@okaxis', true], 58 | ['axisbank@bankaxis', true], 59 | ['super-vpa@superbank', true], 60 | 61 | ['super-vpa@banksuper', false], 62 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false], 63 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@superbank', false], 64 | ]; 65 | const handles = ['bankaxis', 'superbank']; 66 | test.each(tests)(`Check %s => %s`, (value, expected) => { 67 | expect(VPA.validate(value, { handles })).toBe(expected); 68 | }); 69 | }); 70 | 71 | describe('VPA with options: { maxLength: 20, handles: ["bankaxis", "superbank"] }', () => { 72 | const tests: [string, boolean][] = [ 73 | ['amazing-uid@upi', true], 74 | ['random9999@upi', true], 75 | ['yesbank@ybl', true], 76 | ['axisbank@okaxis', true], 77 | ['axisbank@bankaxis', true], 78 | ['super-vpa@superbank', true], 79 | 80 | ['super-vpa@banksuper', false], 81 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false], 82 | ['very-long-upi-id-or-vpa-should-not-pass-the-validation@superbank', false], 83 | ]; 84 | const handles = ['bankaxis', 'superbank']; 85 | test.each(tests)(`Check %s => %s`, (value, expected) => { 86 | expect(VPA.validate(value, { maxLength: 20, handles })).toBe(expected); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /dist/luhn.d.ts: -------------------------------------------------------------------------------- 1 | export declare class Luhn { 2 | static validate(value: string): boolean; 3 | } 4 | -------------------------------------------------------------------------------- /dist/luhn.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Luhn = void 0; 4 | class Luhn { 5 | static validate(value) { 6 | const digits = [...value.replace(/\s/g, '')].reverse(); 7 | const sum = digits 8 | .map((digit, index) => { 9 | digit = parseInt(digit, 10) * (index % 2 !== 0 ? 2 : 1); 10 | return digit > 9 ? digit - 9 : digit; 11 | }) 12 | .reduce((prev, current) => { 13 | return prev + current; 14 | }); 15 | return sum > 0 && sum % 10 === 0; 16 | } 17 | } 18 | exports.Luhn = Luhn; 19 | -------------------------------------------------------------------------------- /dist/validator.d.ts: -------------------------------------------------------------------------------- 1 | import { VpaValidationOptions } from './vpa'; 2 | export declare class Validator { 3 | static mobile(value: string): boolean; 4 | static pincode(value: string | number): boolean; 5 | static pan(value: string): boolean; 6 | static tan(value: string): boolean; 7 | static uan(value: string): boolean; 8 | static ifsc(value: string): boolean; 9 | static esic(value: string): boolean; 10 | static imei(value: string): boolean; 11 | static aadhaar(value: string): boolean; 12 | static aadhaarVID(value: string): boolean; 13 | static gst(value: string): boolean; 14 | static gstChecksum(value: string): boolean; 15 | static vehicleRegistration(value: string): boolean; 16 | static vpa(value: string, options?: VpaValidationOptions): boolean; 17 | } 18 | -------------------------------------------------------------------------------- /dist/validator.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Validator = void 0; 4 | const luhn_1 = require("./luhn"); 5 | const verhoeff_1 = require("./verhoeff"); 6 | const vpa_1 = require("./vpa"); 7 | class Validator { 8 | static mobile(value) { 9 | return /^[6789]\d{9}$/.test(value); 10 | } 11 | static pincode(value) { 12 | return /^[1-9]\d{5}$/.test(value); 13 | } 14 | static pan(value) { 15 | return /^[A-Z]{3}[PCHABGJLFTE][A-Z]\d{4}[A-Z]$/i.test(value); 16 | } 17 | static tan(value) { 18 | return /^[A-Z]{4}\d{5}[A-Z]$/i.test(value); 19 | } 20 | static uan(value) { 21 | return /^\d{12}$/.test(value); 22 | } 23 | static ifsc(value) { 24 | return /^[A-Z]{4}0[A-Z0-9]{6}$/i.test(value); 25 | } 26 | static esic(value) { 27 | return /^\d{17}$/.test(value); 28 | } 29 | static imei(value) { 30 | value = value.replace(/[\s-]+/g, ''); 31 | return /^\d{15}$/.test(value) && luhn_1.Luhn.validate(value); 32 | } 33 | static aadhaar(value) { 34 | value = value.replace(/[\s-]+/g, ''); 35 | return /^[2-9]\d{11}$/.test(value) && verhoeff_1.Verhoeff.validate(value); 36 | } 37 | static aadhaarVID(value) { 38 | value = value.replace(/[\s-]+/g, ''); 39 | return /^\d{16}$/.test(value) && verhoeff_1.Verhoeff.validate(value); 40 | } 41 | static gst(value) { 42 | const regex = /^([0-2][0-9]|[3][0-7])[A-Z]{3}[ABCFGHLJPTK][A-Z]\d{4}[A-Z][A-Z0-9][Z][A-Z0-9]$/i; 43 | return regex.test(value) && Validator.gstChecksum(value); 44 | } 45 | static gstChecksum(value) { 46 | if (!/^[0-9A-Z]{15}$/i.test(value)) { 47 | return false; 48 | } 49 | const chars = [...'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ']; 50 | const values = [...value.toUpperCase()]; 51 | const lastChar = values.pop(); 52 | const sum = values 53 | .map((char, index) => { 54 | const product = chars.indexOf(char) * (index % 2 !== 0 ? 2 : 1); 55 | return Math.floor(product / chars.length) + (product % chars.length); 56 | }) 57 | .reduce((prev, current) => { 58 | return prev + current; 59 | }); 60 | const checksum = (chars.length - (sum % chars.length)) % chars.length; 61 | return chars[checksum] === lastChar; 62 | } 63 | static vehicleRegistration(value) { 64 | const regex = /^[A-Z]{2}[\s-.]?[0-9]{1,2}[\s-.]?[0-9A-Z]{1,3}[\s-.]?[0-9]{1,4}$/i; 65 | return regex.test(value); 66 | } 67 | static vpa(value, options) { 68 | return vpa_1.VPA.validate(value, options); 69 | } 70 | } 71 | exports.Validator = Validator; 72 | -------------------------------------------------------------------------------- /dist/verhoeff.d.ts: -------------------------------------------------------------------------------- 1 | export declare class Verhoeff { 2 | static multiplication: number[][]; 3 | static permutation: number[][]; 4 | static inverse: number[]; 5 | static validate(value: string): boolean; 6 | } 7 | -------------------------------------------------------------------------------- /dist/verhoeff.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.Verhoeff = void 0; 4 | class Verhoeff { 5 | static validate(value) { 6 | const digits = [...value.replace(/\s/g, '')].reverse().map((digit) => parseInt(digit, 10)); 7 | const checksum = digits.reduce((prev, current, index) => { 8 | return Verhoeff.multiplication[prev][Verhoeff.permutation[index % 8][current]]; 9 | }, 0); 10 | return checksum === 0; 11 | } 12 | } 13 | exports.Verhoeff = Verhoeff; 14 | Verhoeff.multiplication = [ 15 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 16 | [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], 17 | [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], 18 | [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], 19 | [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], 20 | [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], 21 | [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], 22 | [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], 23 | [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], 24 | [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 25 | ]; 26 | Verhoeff.permutation = [ 27 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 28 | [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], 29 | [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], 30 | [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], 31 | [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], 32 | [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], 33 | [2, 7, 9, 3, 8, 0, 6, 4, 1, 5], 34 | [7, 0, 4, 6, 9, 1, 3, 2, 5, 8], 35 | ]; 36 | Verhoeff.inverse = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]; 37 | -------------------------------------------------------------------------------- /dist/vpa-handles.json: -------------------------------------------------------------------------------- 1 | [ 2 | "abfspay", 3 | "airtel", 4 | "airtelpaymentsbank", 5 | "albk", 6 | "allahabadbank", 7 | "allbank", 8 | "andb", 9 | "apb", 10 | "apl", 11 | "axis", 12 | "axisb", 13 | "axisbank", 14 | "axisgo", 15 | "bandhan", 16 | "barodampay", 17 | "barodapay", 18 | "boi", 19 | "cbin", 20 | "cboi", 21 | "centralbank", 22 | "cmsidfc", 23 | "cnrb", 24 | "csbcash", 25 | "csbpay", 26 | "cub", 27 | "dbs", 28 | "dcb", 29 | "dcbbank", 30 | "denabank", 31 | "dlb", 32 | "eazypay", 33 | "equitas", 34 | "ezeepay", 35 | "fbl", 36 | "federal", 37 | "finobank", 38 | "hdfcbank", 39 | "hdfcbankjd", 40 | "hsbc", 41 | "icici", 42 | "idbi", 43 | "idbibank", 44 | "idfc", 45 | "idfcbank", 46 | "idfcnetc", 47 | "ikwik", 48 | "imobile", 49 | "indbank", 50 | "indianbank", 51 | "indianbk", 52 | "indus", 53 | "indusind", 54 | "iob", 55 | "jkb", 56 | "jsbp", 57 | "karb", 58 | "karurvysyabank", 59 | "kaypay", 60 | "kbl", 61 | "kbl052", 62 | "kmb", 63 | "kmbl", 64 | "kotak", 65 | "kvb", 66 | "kvbank", 67 | "lime", 68 | "lvb", 69 | "lvbank", 70 | "mahb", 71 | "myicici", 72 | "obc", 73 | "okaxis", 74 | "okhdfcbank", 75 | "okicici", 76 | "oksbi", 77 | "paytm", 78 | "payzapp", 79 | "pingpay", 80 | "pnb", 81 | "pockets", 82 | "psb", 83 | "purz", 84 | "rajgovhdfcbank", 85 | "rbl", 86 | "sbi", 87 | "sc", 88 | "scb", 89 | "scbl", 90 | "scmobile", 91 | "sib", 92 | "srcb", 93 | "synd", 94 | "syndbank", 95 | "syndicate", 96 | "tjsb", 97 | "ubi", 98 | "uboi", 99 | "uco", 100 | "unionbank", 101 | "unionbankofindia", 102 | "united", 103 | "utbi", 104 | "vijayabank", 105 | "vijb", 106 | "vjb", 107 | "ybl", 108 | "yesbank", 109 | "yesbankltd", 110 | "freecharge", 111 | "upi" 112 | ] 113 | -------------------------------------------------------------------------------- /dist/vpa.d.ts: -------------------------------------------------------------------------------- 1 | export type VpaValidationOptions = { 2 | maxLength?: number; 3 | handles?: boolean | string[]; 4 | }; 5 | export declare class VPA { 6 | static defaultVpaHandles: string[] | undefined; 7 | static validate(value: string, options?: VpaValidationOptions): boolean; 8 | } 9 | -------------------------------------------------------------------------------- /dist/vpa.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.VPA = void 0; 7 | const ValidationOptionsDefaults = { 8 | maxLength: 50, 9 | handles: true, 10 | }; 11 | const vpa_handles_json_1 = __importDefault(require("./vpa-handles.json")); 12 | class VPA { 13 | static validate(value, options) { 14 | options = Object.assign({}, ValidationOptionsDefaults, options); 15 | const regex = /^[a-z0-9_.-]{3,}@[a-z]{3,}$/i; 16 | let isValidFormat = regex.test(value) && value.length <= options.maxLength; 17 | if (!isValidFormat) { 18 | return false; 19 | } 20 | if (options.handles) { 21 | options.handles = (options.handles === true ? vpa_handles_json_1.default : [...options.handles, ...vpa_handles_json_1.default]); 22 | const handle = value.split('@')[1]; 23 | isValidFormat = options.handles.indexOf(handle) >= 0; 24 | } 25 | return isValidFormat; 26 | } 27 | } 28 | exports.VPA = VPA; 29 | VPA.defaultVpaHandles = undefined; 30 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from '@typescript-eslint/eslint-plugin'; 2 | import globals from 'globals'; 3 | import tsParser from '@typescript-eslint/parser'; 4 | import path from 'node:path'; 5 | import { fileURLToPath } from 'node:url'; 6 | import js from '@eslint/js'; 7 | import { FlatCompat } from '@eslint/eslintrc'; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all, 15 | }); 16 | 17 | export default [ 18 | ...compat.extends( 19 | 'eslint:recommended', 20 | 'plugin:@typescript-eslint/eslint-recommended', 21 | 'plugin:@typescript-eslint/recommended', 22 | 'prettier', 23 | 'plugin:prettier/recommended', 24 | ), 25 | { 26 | plugins: { 27 | '@typescript-eslint': typescriptEslint, 28 | }, 29 | 30 | languageOptions: { 31 | globals: { 32 | ...globals.node, 33 | ...globals.commonjs, 34 | ...Object.fromEntries(Object.entries(globals.browser).map(([key]) => [key, 'off'])), 35 | }, 36 | 37 | parser: tsParser, 38 | ecmaVersion: 2018, 39 | sourceType: 'module', 40 | }, 41 | files: ['**/*.ts'], 42 | }, 43 | { 44 | ignores: ['node_modules', 'dist', 'coverage', '**/*.d.ts', '**/*.js', '**/*.cjs'], 45 | }, 46 | ]; 47 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | collectCoverage: true, 5 | coverageDirectory: 'coverage', 6 | coverageProvider: 'v8', 7 | verbose: true, 8 | roots: [''], 9 | testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], 10 | transform: { 11 | '^.+\\.(ts|tsx)$': 'ts-jest', 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /lint-staged.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'package.json': 'sort-package-json', 3 | '*.{ts,tsx}': 'eslint --no-warn-ignored --max-warnings=0 . --fix', 4 | '**/*.ts?(x)': () => 'tsc -p tsconfig.json', 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "format-utils", 3 | "version": "0.0.11", 4 | "description": "Utilities for validating various formats of Indian system codes like Mobile, PAN, AADHAAR, GST and more!", 5 | "keywords": [ 6 | "format validators", 7 | "mobile", 8 | "pincode", 9 | "PAN", 10 | "TAN", 11 | "UAN", 12 | "IFSC", 13 | "ESIC", 14 | "IMEI", 15 | "AADHAAR", 16 | "AADHAR", 17 | "AADHAAR VID", 18 | "GST", 19 | "GSTIN", 20 | "vehicle registration", 21 | "vpa", 22 | "upi", 23 | "upi id", 24 | "india", 25 | "validators", 26 | "validation" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/mastermunj/format-utils.git" 31 | }, 32 | "license": "MIT", 33 | "author": "Munjal Dhamecha", 34 | "main": "dist/validator.js", 35 | "files": [ 36 | "dist/*" 37 | ], 38 | "scripts": { 39 | "build": "npm run clean && tsc --noEmitOnError", 40 | "build:watch": "npm run build -- --watch", 41 | "clean": "rimraf dist coverage", 42 | "commit": "git-cz", 43 | "lint": "eslint --max-warnings=0 .", 44 | "lint:fix": "npm run lint -- --fix", 45 | "prepare": "husky", 46 | "release": "standard-version", 47 | "release:mock": "npm run release -- --dry-run", 48 | "test": "vitest", 49 | "test:watch": "npm run test -- --watch" 50 | }, 51 | "config": { 52 | "commitizen": { 53 | "path": "./node_modules/cz-conventional-changelog" 54 | } 55 | }, 56 | "devDependencies": { 57 | "@commitlint/cli": "^19.8.0", 58 | "@commitlint/config-conventional": "^19.8.0", 59 | "@types/node": "^22.13.10", 60 | "@typescript-eslint/eslint-plugin": "^8.26.1", 61 | "@typescript-eslint/parser": "^8.26.1", 62 | "@vitest/coverage-v8": "^3.0.9", 63 | "cpy-cli": "^5.0.0", 64 | "cz-conventional-changelog": "^3.3.0", 65 | "eslint": "^9.22.0", 66 | "eslint-config-prettier": "^10.1.1", 67 | "eslint-plugin-prettier": "^5.2.3", 68 | "husky": "^9.1.7", 69 | "lint-staged": "^16.0.0", 70 | "prettier": "^3.5.3", 71 | "rimraf": "^6.0.1", 72 | "sort-package-json": "^3.0.0", 73 | "standard-version": "^9.5.0", 74 | "typescript": "^5.8.2", 75 | "vitest": "^3.0.9" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/luhn.ts: -------------------------------------------------------------------------------- 1 | export class Luhn { 2 | static validate(value: string): boolean { 3 | const digits = [...value.replace(/\s/g, '')].reverse(); 4 | 5 | const sum = digits 6 | .map((digit: number | string, index) => { 7 | digit = parseInt(digit as string, 10) * (index % 2 !== 0 ? 2 : 1); 8 | return digit > 9 ? digit - 9 : digit; 9 | }) 10 | .reduce((prev, current) => { 11 | return prev + current; 12 | }); 13 | 14 | return sum > 0 && sum % 10 === 0; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/validator.ts: -------------------------------------------------------------------------------- 1 | import { Luhn } from './luhn'; 2 | import { Verhoeff } from './verhoeff'; 3 | import { VPA, VpaValidationOptions } from './vpa'; 4 | 5 | export class Validator { 6 | static mobile(value: string): boolean { 7 | return /^[6789]\d{9}$/.test(value); 8 | } 9 | 10 | static pincode(value: string | number): boolean { 11 | return /^[1-9]\d{5}$/.test(value as string); 12 | } 13 | 14 | static pan(value: string): boolean { 15 | return /^[A-Z]{3}[PCHABGJLFTE][A-Z]\d{4}[A-Z]$/i.test(value); 16 | } 17 | 18 | static tan(value: string): boolean { 19 | return /^[A-Z]{4}\d{5}[A-Z]$/i.test(value); 20 | } 21 | 22 | static uan(value: string): boolean { 23 | return /^\d{12}$/.test(value); 24 | } 25 | 26 | static ifsc(value: string): boolean { 27 | return /^[A-Z]{4}0[A-Z0-9]{6}$/i.test(value); 28 | } 29 | 30 | static esic(value: string): boolean { 31 | return /^\d{17}$/.test(value); 32 | } 33 | 34 | static imei(value: string): boolean { 35 | value = value.replace(/[\s-]+/g, ''); 36 | return /^\d{15}$/.test(value) && Luhn.validate(value); 37 | } 38 | 39 | static aadhaar(value: string): boolean { 40 | value = value.replace(/[\s-]+/g, ''); 41 | return /^[2-9]\d{11}$/.test(value) && Verhoeff.validate(value); 42 | } 43 | 44 | static aadhaarVID(value: string): boolean { 45 | value = value.replace(/[\s-]+/g, ''); 46 | return /^\d{16}$/.test(value) && Verhoeff.validate(value); 47 | } 48 | 49 | static gst(value: string): boolean { 50 | const regex = /^([0-2][0-9]|[3][0-7])[A-Z]{3}[ABCFGHLJPTK][A-Z]\d{4}[A-Z][A-Z0-9][Z][A-Z0-9]$/i; 51 | return regex.test(value) && Validator.gstChecksum(value); 52 | } 53 | 54 | static gstChecksum(value: string): boolean { 55 | if (!/^[0-9A-Z]{15}$/i.test(value)) { 56 | return false; 57 | } 58 | const chars = [...'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ']; 59 | const values = [...value.toUpperCase()]; 60 | const lastChar = values.pop(); 61 | const sum = values 62 | .map((char, index) => { 63 | const product = chars.indexOf(char) * (index % 2 !== 0 ? 2 : 1); 64 | return Math.floor(product / chars.length) + (product % chars.length); 65 | }) 66 | .reduce((prev, current) => { 67 | return prev + current; 68 | }); 69 | const checksum = (chars.length - (sum % chars.length)) % chars.length; 70 | return chars[checksum] === lastChar; 71 | } 72 | 73 | static vehicleRegistration(value: string): boolean { 74 | const regex = /^[A-Z]{2}[\s-.]?[0-9]{1,2}[\s-.]?[0-9A-Z]{1,3}[\s-.]?[0-9]{1,4}$/i; 75 | return regex.test(value); 76 | } 77 | 78 | static vpa(value: string, options?: VpaValidationOptions): boolean { 79 | return VPA.validate(value, options); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/verhoeff.ts: -------------------------------------------------------------------------------- 1 | export class Verhoeff { 2 | static multiplication = [ 3 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 4 | [1, 2, 3, 4, 0, 6, 7, 8, 9, 5], 5 | [2, 3, 4, 0, 1, 7, 8, 9, 5, 6], 6 | [3, 4, 0, 1, 2, 8, 9, 5, 6, 7], 7 | [4, 0, 1, 2, 3, 9, 5, 6, 7, 8], 8 | [5, 9, 8, 7, 6, 0, 4, 3, 2, 1], 9 | [6, 5, 9, 8, 7, 1, 0, 4, 3, 2], 10 | [7, 6, 5, 9, 8, 2, 1, 0, 4, 3], 11 | [8, 7, 6, 5, 9, 3, 2, 1, 0, 4], 12 | [9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 13 | ]; 14 | 15 | static permutation = [ 16 | [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 17 | [1, 5, 7, 6, 2, 8, 3, 0, 9, 4], 18 | [5, 8, 0, 3, 7, 9, 6, 1, 4, 2], 19 | [8, 9, 1, 6, 0, 4, 3, 5, 2, 7], 20 | [9, 4, 5, 3, 1, 2, 6, 8, 7, 0], 21 | [4, 2, 8, 6, 5, 7, 3, 9, 0, 1], 22 | [2, 7, 9, 3, 8, 0, 6, 4, 1, 5], 23 | [7, 0, 4, 6, 9, 1, 3, 2, 5, 8], 24 | ]; 25 | 26 | static inverse = [0, 4, 3, 2, 1, 5, 6, 7, 8, 9]; 27 | 28 | static validate(value: string): boolean { 29 | const digits = [...value.replace(/\s/g, '')].reverse().map((digit) => parseInt(digit, 10)); 30 | 31 | const checksum = digits.reduce((prev, current, index) => { 32 | return Verhoeff.multiplication[prev][Verhoeff.permutation[index % 8][current]]; 33 | }, 0); 34 | 35 | return checksum === 0; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/vpa-handles.json: -------------------------------------------------------------------------------- 1 | [ 2 | "abfspay", 3 | "airtel", 4 | "airtelpaymentsbank", 5 | "albk", 6 | "allahabadbank", 7 | "allbank", 8 | "andb", 9 | "apb", 10 | "apl", 11 | "axis", 12 | "axisb", 13 | "axisbank", 14 | "axisgo", 15 | "bandhan", 16 | "barodampay", 17 | "barodapay", 18 | "boi", 19 | "cbin", 20 | "cboi", 21 | "centralbank", 22 | "cmsidfc", 23 | "cnrb", 24 | "csbcash", 25 | "csbpay", 26 | "cub", 27 | "dbs", 28 | "dcb", 29 | "dcbbank", 30 | "denabank", 31 | "dlb", 32 | "eazypay", 33 | "equitas", 34 | "ezeepay", 35 | "fbl", 36 | "federal", 37 | "finobank", 38 | "hdfcbank", 39 | "hdfcbankjd", 40 | "hsbc", 41 | "icici", 42 | "idbi", 43 | "idbibank", 44 | "idfc", 45 | "idfcbank", 46 | "idfcnetc", 47 | "ikwik", 48 | "imobile", 49 | "indbank", 50 | "indianbank", 51 | "indianbk", 52 | "indus", 53 | "indusind", 54 | "iob", 55 | "jkb", 56 | "jsbp", 57 | "karb", 58 | "karurvysyabank", 59 | "kaypay", 60 | "kbl", 61 | "kbl052", 62 | "kmb", 63 | "kmbl", 64 | "kotak", 65 | "kvb", 66 | "kvbank", 67 | "lime", 68 | "lvb", 69 | "lvbank", 70 | "mahb", 71 | "myicici", 72 | "obc", 73 | "okaxis", 74 | "okhdfcbank", 75 | "okicici", 76 | "oksbi", 77 | "paytm", 78 | "payzapp", 79 | "pingpay", 80 | "pnb", 81 | "pockets", 82 | "psb", 83 | "purz", 84 | "rajgovhdfcbank", 85 | "rbl", 86 | "sbi", 87 | "sc", 88 | "scb", 89 | "scbl", 90 | "scmobile", 91 | "sib", 92 | "srcb", 93 | "synd", 94 | "syndbank", 95 | "syndicate", 96 | "tjsb", 97 | "ubi", 98 | "uboi", 99 | "uco", 100 | "unionbank", 101 | "unionbankofindia", 102 | "united", 103 | "utbi", 104 | "vijayabank", 105 | "vijb", 106 | "vjb", 107 | "ybl", 108 | "yesbank", 109 | "yesbankltd", 110 | "freecharge", 111 | "upi" 112 | ] -------------------------------------------------------------------------------- /src/vpa.ts: -------------------------------------------------------------------------------- 1 | export type VpaValidationOptions = { 2 | maxLength?: number; 3 | handles?: boolean | string[]; 4 | }; 5 | 6 | const ValidationOptionsDefaults: VpaValidationOptions = { 7 | maxLength: 50, 8 | handles: true, 9 | }; 10 | 11 | import defaultVpaHandles from './vpa-handles.json'; 12 | 13 | export class VPA { 14 | static defaultVpaHandles: string[] | undefined = undefined; 15 | 16 | static validate(value: string, options?: VpaValidationOptions): boolean { 17 | options = Object.assign({}, ValidationOptionsDefaults, options); 18 | 19 | const regex = /^[a-z0-9_.-]{3,}@[a-z]{3,}$/i; 20 | let isValidFormat = regex.test(value) && value.length <= (options.maxLength as number); 21 | if (!isValidFormat) { 22 | return false; 23 | } 24 | 25 | if (options.handles) { 26 | options.handles = ( 27 | options.handles === true ? defaultVpaHandles : [...options.handles, ...defaultVpaHandles] 28 | ) as string[]; 29 | const handle = value.split('@')[1]; 30 | isValidFormat = options.handles.indexOf(handle) >= 0; 31 | } 32 | return isValidFormat; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "declaration": true, 8 | 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "strictFunctionTypes": true, 13 | "strictBindCallApply": true, 14 | "strictPropertyInitialization": true, 15 | "noImplicitThis": true, 16 | "alwaysStrict": true, 17 | 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | 23 | "esModuleInterop": true, 24 | 25 | "forceConsistentCasingInFileNames": true, 26 | "resolveJsonModule": true 27 | }, 28 | "exclude": [ 29 | "node_modules", 30 | "__tests__" 31 | ], 32 | "include": [ 33 | "./src/**/*" 34 | ] 35 | } -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ['**/*.test.ts'], 6 | watch: false, 7 | cache: false, 8 | coverage: { 9 | enabled: true, 10 | include: ['src/**'], 11 | }, 12 | }, 13 | }); 14 | --------------------------------------------------------------------------------