diff --git a/README.md b/README.md
index d75f15b27..a079026fb 100644
--- a/README.md
+++ b/README.md
@@ -166,7 +166,7 @@ Validator | Description
**isSlug(str)** | check if the string is of type slug.
**isStrongPassword(str [, options])** | check if the string can be considered a strong password or not. Allows for custom requirements or scoring rules. If `returnScore` is true, then the function returns an integer score for the password rather than a boolean.
Default options:
`{ minLength: 8, minLowercase: 1, minUppercase: 1, minNumbers: 1, minSymbols: 1, returnScore: false, pointsPerUnique: 1, pointsPerRepeat: 0.5, pointsForContainingLower: 10, pointsForContainingUpper: 10, pointsForContainingNumber: 10, pointsForContainingSymbol: 10 }`
**isTime(str [, options])** | check if the string is a valid time e.g. [`23:01:59`, new Date().toLocaleTimeString()].
`options` is an object which can contain the keys `hourFormat` or `mode`.
`hourFormat` is a key and defaults to `'hour24'`.
`mode` is a key and defaults to `'default'`.
`hourFomat` can contain the values `'hour12'` or `'hour24'`, `'hour24'` will validate hours in 24 format and `'hour12'` will validate hours in 12 format.
`mode` can contain the values `'default'` or `'withSeconds'`, `'default'` will validate `HH:MM` format, `'withSeconds'` will validate the `HH:MM:SS` format.
-**isTaxID(str, locale)** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.
More info about exact TIN support can be found in `src/lib/isTaxID.js`.
Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`.
+**isTaxID(str, locale [, options])** | check if the string is a valid Tax Identification Number. Default locale is `en-US`.
More info about exact TIN support can be found in `src/lib/isTaxID.js`.
`options` is an object that has no default.
`options` contains `localeOption` as key and can be used if the locale has multiple Tax Identification Number `(e.g.{localeOption:'GSTIN'})`.
`localeOption` is of type `string`.
Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-CA', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', 'fr-CA', 'fr-FR', 'fr-LU', 'hr-HR', 'hu-HU', 'it-IT', 'lb-LU', 'lt-LT', 'lv-LV', 'mt-MT', 'nl-BE', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', 'sk-SK', 'sl-SI', 'sv-SE' ]`.
Supported localeOption: `['GSTIN','PAN']` for `en-IN` locale.
**isURL(str [, options])** | check if the string is a URL.
`options` is an object which defaults to `{ protocols: ['http','https','ftp'], require_tld: true, require_protocol: false, require_host: true, require_port: false, require_valid_protocol: true, allow_underscores: false, host_whitelist: false, host_blacklist: false, allow_trailing_dot: false, allow_protocol_relative_urls: false, allow_fragments: true, allow_query_components: true, disallow_auth: false, validate_length: true }`.
`require_protocol` - if set to true isURL will return false if protocol is not present in the URL.
`require_valid_protocol` - isURL will check if the URL's protocol is present in the protocols option.
`protocols` - valid protocols can be modified with this option.
`require_host` - if set to false isURL will not check if host is present in the URL.
`require_port` - if set to true isURL will check if port is present in the URL.
`allow_protocol_relative_urls` - if set to true protocol relative URLs will be allowed.
`allow_fragments` - if set to false isURL will return false if fragments are present.
`allow_query_components` - if set to false isURL will return false if query components are present.
`validate_length` - if set to false isURL will skip string length validation (2083 characters is IE max URL length).
**isUUID(str [, version])** | check if the string is a UUID (version 1, 2, 3, 4 or 5).
**isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars.
diff --git a/src/lib/isTaxID.js b/src/lib/isTaxID.js
index 933783f44..8c4c6e617 100644
--- a/src/lib/isTaxID.js
+++ b/src/lib/isTaxID.js
@@ -1136,6 +1136,10 @@ const taxIdFormat = {
'en-CA': /^\d{9}$/,
'en-GB': /^\d{10}$|^(?!GB|NK|TN|ZZ)(?![DFIQUV])[A-Z](?![DFIQUVO])[A-Z]\d{6}[ABCD ]$/i,
'en-IE': /^\d{7}[A-W][A-IW]{0,1}$/i,
+ 'en-IN': {
+ GSTIN: /^((?!39)(?!00)[0-3][0-9]|97)([A-Z]{3}[ABCFGHLJPT][A-Z](?!0000)[0-9]{4}[A-Z])[1-9A-Z]Z[0-9 A-Z]$/,
+ PAN: /^[A-Z]{3}[ABCFGHLJPT][A-Z](?!0000)[0-9]{4}[A-Z]$/,
+ },
'en-US': /^\d{2}[- ]{0,1}\d{7}$/,
'es-ES': /^(\d{0,8}|[XYZKLM]\d{7})[A-HJ-NP-TV-Z]$/i,
'et-EE': /^[1-6]\d{6}(00[1-9]|0[1-9][0-9]|[1-6][0-9]{2}|70[0-9]|710)\d$/,
@@ -1211,30 +1215,62 @@ const sanitizeRegexes = {
// sanitizeRegexes locale aliases
sanitizeRegexes['nl-BE'] = sanitizeRegexes['fr-BE'];
+const multipleTaxIdLocale = {
+ 'en-IN': {
+ GSTIN: true,
+ PAN: true,
+ },
+};
+
+const availableOptions = {
+ localeOption: true,
+};
+
+// Validates args of isTaxID function
+function validateArgs(str, locale, options) {
+ const localeOption = options?.localeOption;
+
+ try { assertString(str); } catch (err) { throw new Error(`${err.message} for str`); }
+ try { assertString(locale); } catch (err) { throw new Error(`${err.message} for locale`); }
+
+ for (const option in options) {
+ if (!(option in availableOptions)) throw new Error(`'${option}' is not available`);
+ }
+
+ if (!(locale in taxIdFormat)) throw new Error(`Invalid locale '${locale}'`);
+
+
+ if (locale in multipleTaxIdLocale) {
+ try { assertString(localeOption); } catch (err) { throw new Error(`${err.message} for localeOption`); }
+ if (!(localeOption in multipleTaxIdLocale[locale])) throw new Error(`Invalid localeOption '${localeOption}'`);
+ } else if (localeOption || localeOption === '') {
+ throw new Error(`Invalid localeOption for locale '${locale}'`);
+ }
+}
+
/*
* Validator function
* Return true if the passed string is a valid tax identification number
* for the specified locale.
* Throw an error exception if the locale is not supported.
*/
-export default function isTaxID(str, locale = 'en-US') {
- assertString(str);
+export default function isTaxID(str, locale = 'en-US', options) {
+ validateArgs(str, locale, options);
+ const localeOption = options?.localeOption;
+
// Copy TIN to avoid replacement if sanitized
let strcopy = str.slice(0);
+ if (locale in sanitizeRegexes) {
+ strcopy = strcopy.replace(sanitizeRegexes[locale], '');
+ }
- if (locale in taxIdFormat) {
- if (locale in sanitizeRegexes) {
- strcopy = strcopy.replace(sanitizeRegexes[locale], '');
- }
- if (!taxIdFormat[locale].test(strcopy)) {
- return false;
- }
-
- if (locale in taxIdCheck) {
- return taxIdCheck[locale](strcopy);
- }
- // Fallthrough; not all locales have algorithmic checks
+ if (locale in multipleTaxIdLocale) {
+ if (!taxIdFormat[locale][localeOption].test(strcopy)) return false;
return true;
}
- throw new Error(`Invalid locale '${locale}'`);
+ if (!taxIdFormat[locale].test(strcopy)) return false;
+ if (locale in taxIdCheck) return taxIdCheck[locale](strcopy);
+
+ return true;
}
+
diff --git a/test/validators.test.js b/test/validators.test.js
index 239f172ca..908cd503a 100644
--- a/test/validators.test.js
+++ b/test/validators.test.js
@@ -12237,6 +12237,40 @@ describe('Validators', () => {
'1234577A',
'1234577JA'],
});
+ test({
+ validator: 'isTaxID',
+ args: ['en-IN', { localeOption: 'GSTIN' }],
+ valid: [
+ '27AAPFU0939F1ZV',
+ '27AASCS2460H1Z0',
+ '29AAGCB7383J1Z4'],
+ invalid: [
+ '',
+ '00AAGCB0000J1Z4',
+ '29AAGCB7383J114',
+ '88AAPFU093921ZV',
+ '**AAGCB7689J1Z4',
+ 'S AAGCB7893J1Z4'],
+ });
+ test({
+ validator: 'isTaxID',
+ args: ['en-IN', { localeOption: 'PAN' }],
+ valid: [
+ 'MFWAA0001A',
+ 'ABPPA2020K',
+ 'KLSPK0909Z',
+ 'ACSLY4499S'],
+ invalid: [
+ '123PA0001A',
+ 'ACSZA2020K',
+ 'ACSP19090K',
+ 'ACSPA0000K',
+ 'ACSYA44999',
+ '',
+ 'ACS A4499A',
+ '*#$PA4499J',
+ 'ACSLY4499SK'],
+ });
test({
validator: 'isTaxID',
args: ['en-US'],
@@ -12573,6 +12607,42 @@ describe('Validators', () => {
valid: [
'01-1234567'],
});
+ test({
+ validator: 'isTaxID',
+ args: [123, { localeOption: 'GSTIN' }],
+ error: [
+ '27AAPFU0939F1ZV'],
+ });
+ test({
+ validator: 'isTaxID',
+ args: ['en-IN', { localeOption: 123 }],
+ error: [
+ '27AAPFU0939F1ZV'],
+ });
+ test({
+ validator: 'isTaxID',
+ args: ['en-IN', { localeOption: 'is-NOT' }],
+ error: [
+ '27AAPFU0939F1ZV'],
+ });
+ test({
+ validator: 'isTaxID',
+ args: ['en-IN', { isNOT: 'is-NOT' }],
+ error: [
+ '27AAPFU0939F1ZV'],
+ });
+ test({
+ validator: 'isTaxID',
+ args: ['en-US', { localeOption: 'GSTIN' }],
+ error: [
+ '27AAPFU0939F1ZV'],
+ });
+ test({
+ validator: 'isTaxID',
+ args: ['en-IN', { localeOption: 'GSTIN' }],
+ error: [
+ 12345566],
+ });
test({
validator: 'isTaxID',
args: ['is-NOT'],