Skip to content

Commit d843c51

Browse files
committed
feat: added VPA validation
1 parent 2b42b0f commit d843c51

13 files changed

+449
-3
lines changed

README.md

+26-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# Format Utilities
1+
# Format Validation Utilities
22

33
## Introduction
44

5-
Utilities for validating various formats of Indian system codes like Mobile, PAN, AADHAR, UID and more!
5+
Utilities for validating various formats of Indian system codes like Mobile, PAN, AADHAR, GST and more!
66

77
## Installation
88

@@ -221,3 +221,27 @@ let isValid = Validator.vehicleRegistration('DL4CAF4943');
221221
isValid = Validator.vehicleRegistration('DL4CAF494G');
222222
// isValid = false
223223
```
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. <br/>When it is `true` the handle part is checked against default handles listed in [vpa-handles.json](./src/vpa-handles.json). <br/>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+
```

__tests__/validator.test.ts

+13
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,16 @@ describe('Vehicle Registration Number', () => {
202202
expect(Validator.vehicleRegistration(value)).toBe(expected);
203203
});
204204
});
205+
206+
describe('VPA', () => {
207+
const tests: [string, boolean][] = [
208+
['amazing-uid@upi', true],
209+
['random9999@upi', true],
210+
211+
['axisbank@bankaxis', false],
212+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
213+
];
214+
test.each(tests)(`Check %s => %s`, (value, expected) => {
215+
expect(Validator.vpa(value)).toBe(expected);
216+
});
217+
});

__tests__/vpa.test.ts

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { VPA } from '../src/vpa';
2+
3+
describe('VPA', () => {
4+
const tests: [string, boolean][] = [
5+
['amazing-uid@upi', true],
6+
['random9999@upi', true],
7+
['vpa_with_underscore@upi', true],
8+
['vpa-with-hyphen@upi', true],
9+
['vpa.with.dot@upi', true],
10+
['9876543210@upi', true],
11+
12+
['it@upi', false],
13+
['with#hash@upi', false],
14+
['with space@upi', false],
15+
['with@at@upi', false],
16+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
17+
];
18+
test.each(tests)(`Check %s => %s`, (value, expected) => {
19+
expect(VPA.validate(value)).toBe(expected);
20+
});
21+
});
22+
23+
describe('VPA with options: { maxLength: 20 }', () => {
24+
const tests: [string, boolean][] = [
25+
['amazing-uid@upi', true],
26+
['random9999@upi', true],
27+
28+
['vpa_with_underscore@upi', false],
29+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
30+
];
31+
test.each(tests)(`Check %s => %s`, (value, expected) => {
32+
expect(VPA.validate(value, { maxLength: 20 })).toBe(expected);
33+
});
34+
});
35+
36+
describe('VPA with options: { handles: true }', () => {
37+
const tests: [string, boolean][] = [
38+
['amazing-uid@upi', true],
39+
['random9999@upi', true],
40+
['yesbank@ybl', true],
41+
['axisbank@okaxis', true],
42+
43+
['axisbank@bankaxis', false],
44+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
45+
];
46+
test.each(tests)(`Check %s => %s`, (value, expected) => {
47+
expect(VPA.validate(value, { handles: true })).toBe(expected);
48+
});
49+
});
50+
51+
describe('VPA with options: { handles: ["bankaxis", "superbank"] }', () => {
52+
const tests: [string, boolean][] = [
53+
['amazing-uid@upi', true],
54+
['random9999@upi', true],
55+
['yesbank@ybl', true],
56+
['axisbank@okaxis', true],
57+
['axisbank@bankaxis', true],
58+
['super-vpa@superbank', true],
59+
60+
['super-vpa@banksuper', false],
61+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
62+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@superbank', false],
63+
];
64+
const handles = ['bankaxis', 'superbank'];
65+
test.each(tests)(`Check %s => %s`, (value, expected) => {
66+
expect(VPA.validate(value, { handles })).toBe(expected);
67+
});
68+
});
69+
70+
describe('VPA with options: { maxLength: 20, handles: ["bankaxis", "superbank"] }', () => {
71+
const tests: [string, boolean][] = [
72+
['amazing-uid@upi', true],
73+
['random9999@upi', true],
74+
['yesbank@ybl', true],
75+
['axisbank@okaxis', true],
76+
['axisbank@bankaxis', true],
77+
['super-vpa@superbank', true],
78+
79+
['super-vpa@banksuper', false],
80+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@upi', false],
81+
['very-long-upi-id-or-vpa-should-not-pass-the-validation@superbank', false],
82+
];
83+
const handles = ['bankaxis', 'superbank'];
84+
test.each(tests)(`Check %s => %s`, (value, expected) => {
85+
expect(VPA.validate(value, { maxLength: 20, handles })).toBe(expected);
86+
});
87+
});

dist/validator.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { VpaValidationOptions } from './vpa';
12
export declare class Validator {
23
static mobile(value: string): boolean;
34
static pincode(value: string | number): boolean;
@@ -12,4 +13,5 @@ export declare class Validator {
1213
static gst(value: string): boolean;
1314
static gstChecksum(value: string): boolean;
1415
static vehicleRegistration(value: string): boolean;
16+
static vpa(value: string, options?: VpaValidationOptions): boolean;
1517
}

dist/validator.js

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Object.defineProperty(exports, "__esModule", { value: true });
33
const luhn_1 = require("./luhn");
44
const verhoeff_1 = require("./verhoeff");
5+
const vpa_1 = require("./vpa");
56
class Validator {
67
static mobile(value) {
78
return /^[6789]\d{9}$/.test(value);
@@ -62,5 +63,8 @@ class Validator {
6263
const regex = /^[A-Z]{2}[\s-.]?[0-9]{1,2}[\s-.]?[0-9A-Z]{1,3}[\s-.]?[0-9]{1,4}$/i;
6364
return regex.test(value);
6465
}
66+
static vpa(value, options) {
67+
return vpa_1.VPA.validate(value, options);
68+
}
6569
}
6670
exports.Validator = Validator;

dist/vpa-handles.json

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
]

dist/vpa.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export declare 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+
static getDefaultVpaHandles(): string[];
9+
}

dist/vpa.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use strict";
2+
Object.defineProperty(exports, "__esModule", { value: true });
3+
const ValidationOptionsDefaults = {
4+
maxLength: 50,
5+
handles: true,
6+
};
7+
class VPA {
8+
static validate(value, options) {
9+
options = Object.assign({}, ValidationOptionsDefaults, options);
10+
const regex = /^[a-z0-9_.-]{3,}@[a-z]{3,}$/i;
11+
let isValidFormat = regex.test(value) && value.length <= options.maxLength;
12+
if (!isValidFormat) {
13+
return false;
14+
}
15+
if (options.handles) {
16+
const defaultHandles = VPA.getDefaultVpaHandles();
17+
options.handles = (options.handles === true
18+
? defaultHandles
19+
: [...options.handles, ...defaultHandles]);
20+
const handle = value.split('@')[1];
21+
isValidFormat = options.handles.indexOf(handle) >= 0;
22+
}
23+
return isValidFormat;
24+
}
25+
static getDefaultVpaHandles() {
26+
if (VPA.defaultVpaHandles === undefined) {
27+
VPA.defaultVpaHandles = require('./vpa-handles.json');
28+
}
29+
return VPA.defaultVpaHandles;
30+
}
31+
}
32+
exports.VPA = VPA;
33+
VPA.defaultVpaHandles = undefined;

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"GST",
1818
"GSTIN",
1919
"vehicle registration",
20+
"vpa",
21+
"upi",
22+
"upi id",
2023
"india",
2124
"validators",
2225
"validation"

src/validator.ts

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Luhn } from './luhn';
22
import { Verhoeff } from './verhoeff';
3+
import { VPA, VpaValidationOptions } from './vpa';
34

45
export class Validator {
56
static mobile(value: string): boolean {
@@ -73,4 +74,8 @@ export class Validator {
7374
const regex = /^[A-Z]{2}[\s-.]?[0-9]{1,2}[\s-.]?[0-9A-Z]{1,3}[\s-.]?[0-9]{1,4}$/i;
7475
return regex.test(value);
7576
}
77+
78+
static vpa(value: string, options?: VpaValidationOptions): boolean {
79+
return VPA.validate(value, options);
80+
}
7681
}

0 commit comments

Comments
 (0)