Skip to content

Commit 4ee1655

Browse files
authored
feat(isTaxID): Canadian Social Insurance Number (SIN) validator (#1867)
* Additional test cases for isPostalCode validator CA, PL, UA * [The Social Insurance Number (SIN)](https://www.canada.ca/en/employment-social-development/services/sin.html) * Function docstring corrected * Canadian SIN is a part of general TaxID validator * Redundant line of code + test fixed * Valid ISO 639-1 + ISO 3166-1 alpha 2 locale
1 parent 2fad8ab commit 4ee1655

3 files changed

Lines changed: 95 additions & 6 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ Validator | Description
110110
**isDecimal(str [, options])** | check if the string represents a decimal number, such as 0.1, .3, 1.1, 1.00003, 4.0, etc.<br/><br/>`options` is an object which defaults to `{force_decimal: false, decimal_digits: '1,', locale: 'en-US'}`<br/><br/>`locale` determine the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'el-GR', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fa', 'fa-AF', 'fa-IR', 'fr-FR', 'fr-CA', 'hu-HU', 'id-ID', 'it-IT', 'ku-IQ', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pl-Pl', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA', 'vi-VN']`.<br/>**Note:** `decimal_digits` is given as a range like '1,3', a specific value like '3' or min like '1,'.
111111
**isDivisibleBy(str, number)** | check if the string is a number that's divisible by another.
112112
**isEAN(str)** | check if the string is an EAN (European Article Number).
113-
**isEmail(str [, options])** | check if the string is an email.<br/><br/>`options` is an object which defaults to `{ allow_display_name: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, allow_ip_domain: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name <email-address>`. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name <email-address>`. If `allow_utf8_local_part` is set to false, the validator will not allow any non-English UTF8 character in email address' local part. If `require_tld` is set to false, e-mail addresses without having TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by GMail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails.
113+
**isEmail(str [, options])** | check if the string is an email.<br/><br/>`options` is an object which defaults to `{ allow_display_name: false, require_display_name: false, allow_utf8_local_part: true, require_tld: true, allow_ip_domain: false, domain_specific_validation: false, blacklisted_chars: '', host_blacklist: [] }`. If `allow_display_name` is set to true, the validator will also match `Display Name <email-address>`. If `require_display_name` is set to true, the validator will reject strings without the format `Display Name <email-address>`. If `allow_utf8_local_part` is set to false, the validator will not allow any non-English UTF8 character in email address' local part. If `require_tld` is set to false, e-mail addresses without having TLD in their domain will also be matched. If `ignore_max_length` is set to true, the validator will not check for the standard max length of an email. If `allow_ip_domain` is set to true, the validator will allow IP addresses in the host part. If `domain_specific_validation` is true, some additional validation will be enabled, e.g. disallowing certain syntactically valid email addresses that are rejected by GMail. If `blacklisted_chars` receives a string, then the validator will reject emails that include any of the characters in the string, in the name part. If `host_blacklist` is set to an array of strings and the part of the email after the `@` symbol matches one of the strings defined in it, the validation fails.
114114
**isEmpty(str [, options])** | check if the string has a length of zero.<br/><br/>`options` is an object which defaults to `{ ignore_whitespace:false }`.
115115
**isEthereumAddress(str)** | check if the string is an [Ethereum](https://ethereum.org/) address using basic regex. Does not validate address checksums.
116116
**isFloat(str [, options])** | check if the string is a float.<br/><br/>`options` is an object which can contain the keys `min`, `max`, `gt`, and/or `lt` to validate the float is within boundaries (e.g. `{ min: 7.22, max: 9.55 }`) it also has `locale` as an option.<br/><br/>`min` and `max` are equivalent to 'greater or equal' and 'less or equal', respectively while `gt` and `lt` are their strict counterparts.<br/><br/>`locale` determine the decimal separator and is one of `['ar', 'ar-AE', 'ar-BH', 'ar-DZ', 'ar-EG', 'ar-IQ', 'ar-JO', 'ar-KW', 'ar-LB', 'ar-LY', 'ar-MA', 'ar-QA', 'ar-QM', 'ar-SA', 'ar-SD', 'ar-SY', 'ar-TN', 'ar-YE', 'bg-BG', 'cs-CZ', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-HK', 'en-IN', 'en-NZ', 'en-US', 'en-ZA', 'en-ZM', 'es-ES', 'fr-CA', 'fr-FR', 'hu-HU', 'it-IT', 'nb-NO', 'nl-NL', 'nn-NO', 'pl-PL', 'pt-BR', 'pt-PT', 'ru-RU', 'sl-SI', 'sr-RS', 'sr-RS@latin', 'sv-SE', 'tr-TR', 'uk-UA']`. Locale list is `validator.isFloatLocales`.
@@ -162,7 +162,7 @@ Validator | Description
162162
**isUppercase(str)** | check if the string is uppercase.
163163
**isSlug** | Check if the string is of type slug. `Options` allow a single hyphen between string. e.g. [`cn-cn`, `cn-c-c`]
164164
**isStrongPassword(str [, options])** | Check if a password is strong 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.<br/>Default options: <br/>`{ 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 }`
165-
**isTaxID(str, locale)** | Check if the given value is a valid Tax Identification Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`<br/><br/>Supported locales: `[ 'bg-BG', 'cs-CZ', 'de-AT', 'de-DE', 'dk-DK', 'el-CY', 'el-GR', 'en-GB', 'en-IE', 'en-US', 'es-ES', 'et-EE', 'fi-FI', 'fr-BE', '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' ]`
165+
**isTaxID(str, locale)** | Check if the given value is a valid Tax Identification Number. Default locale is `en-US`.<br/><br/>More info about exact TIN support can be found in `src/lib/isTaxID.js`<br/><br/>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' ]`
166166
**isURL(str [, options])** | check if the string is an URL.<br/><br/>`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 }`.<br/><br/>require_protocol - if set as true isURL will return false if protocol is not present in the URL.<br/>require_valid_protocol - isURL will check if the URL's protocol is present in the protocols option.<br/>protocols - valid protocols can be modified with this option.<br/>require_host - if set as false isURL will not check if host is present in the URL.<br/>require_port - if set as true isURL will check if port is present in the URL.<br/>allow_protocol_relative_urls - if set as true protocol relative URLs will be allowed.<br/>allow_fragments - if set as false isURL will return false if fragments are present.<br/>allow_query_components - if set as false isURL will return false if query components are present.<br/>validate_length - if set as false isURL will skip string length validation (2083 characters is IE max URL length).
167167
**isUUID(str [, version])** | check if the string is a UUID (version 1, 2, 3, 4 or 5).
168168
**isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars.

src/lib/isTaxID.js

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,36 @@ function bgBgCheck(tin) {
6060
return checksum === digits[9];
6161
}
6262

63+
/**
64+
* Check if an input is a valid Canadian SIN (Social Insurance Number)
65+
*
66+
* The Social Insurance Number (SIN) is a 9 digit number that
67+
* you need to work in Canada or to have access to government programs and benefits.
68+
*
69+
* https://en.wikipedia.org/wiki/Social_Insurance_Number
70+
* https://www.canada.ca/en/employment-social-development/services/sin.html
71+
* https://www.codercrunch.com/challenge/819302488/sin-validator
72+
*
73+
* @param {string} input
74+
* @return {boolean}
75+
*/
76+
function isCanadianSIN(input) {
77+
const digitsArray = input.split('');
78+
const even = digitsArray
79+
.filter((_, idx) => idx % 2)
80+
.map(i => Number(i) * 2)
81+
.join('')
82+
.split('');
83+
84+
const total = digitsArray
85+
.filter((_, idx) => !(idx % 2))
86+
.concat(even)
87+
.map(i => Number(i))
88+
.reduce((acc, cur) => acc + cur);
89+
90+
return (total % 10 === 0);
91+
}
92+
6393
/*
6494
* cs-CZ validation function
6595
* (Rodné číslo (RČ), persons only)
@@ -1096,14 +1126,14 @@ function svSeCheck(tin) {
10961126
* uppercase and lowercase letters are acceptable.
10971127
*/
10981128
const taxIdFormat = {
1099-
11001129
'bg-BG': /^\d{10}$/,
11011130
'cs-CZ': /^\d{6}\/{0,1}\d{3,4}$/,
11021131
'de-AT': /^\d{9}$/,
11031132
'de-DE': /^[1-9]\d{10}$/,
11041133
'dk-DK': /^\d{6}-{0,1}\d{4}$/,
11051134
'el-CY': /^[09]\d{7}[A-Z]$/,
11061135
'el-GR': /^([0-4]|[7-9])\d{8}$/,
1136+
'en-CA': /^\d{9}$/,
11071137
'en-GB': /^\d{10}$|^(?!GB|NK|TN|ZZ)(?![DFIQUV])[A-Z](?![DFIQUVO])[A-Z]\d{6}[ABCD ]$/i,
11081138
'en-IE': /^\d{7}[A-W][A-IW]{0,1}$/i,
11091139
'en-US': /^\d{2}[- ]{0,1}\d{7}$/,
@@ -1126,23 +1156,23 @@ const taxIdFormat = {
11261156
'sk-SK': /^\d{6}\/{0,1}\d{3,4}$/,
11271157
'sl-SI': /^[1-9]\d{7}$/,
11281158
'sv-SE': /^(\d{6}[-+]{0,1}\d{4}|(18|19|20)\d{6}[-+]{0,1}\d{4})$/,
1129-
11301159
};
11311160
// taxIdFormat locale aliases
11321161
taxIdFormat['lb-LU'] = taxIdFormat['fr-LU'];
11331162
taxIdFormat['lt-LT'] = taxIdFormat['et-EE'];
11341163
taxIdFormat['nl-BE'] = taxIdFormat['fr-BE'];
1164+
taxIdFormat['fr-CA'] = taxIdFormat['en-CA'];
11351165

11361166
// Algorithmic tax id check functions for various locales
11371167
const taxIdCheck = {
1138-
11391168
'bg-BG': bgBgCheck,
11401169
'cs-CZ': csCzCheck,
11411170
'de-AT': deAtCheck,
11421171
'de-DE': deDeCheck,
11431172
'dk-DK': dkDkCheck,
11441173
'el-CY': elCyCheck,
11451174
'el-GR': elGrCheck,
1175+
'en-CA': isCanadianSIN,
11461176
'en-IE': enIeCheck,
11471177
'en-US': enUsCheck,
11481178
'es-ES': esEsCheck,
@@ -1164,12 +1194,12 @@ const taxIdCheck = {
11641194
'sk-SK': skSkCheck,
11651195
'sl-SI': slSiCheck,
11661196
'sv-SE': svSeCheck,
1167-
11681197
};
11691198
// taxIdCheck locale aliases
11701199
taxIdCheck['lb-LU'] = taxIdCheck['fr-LU'];
11711200
taxIdCheck['lt-LT'] = taxIdCheck['et-EE'];
11721201
taxIdCheck['nl-BE'] = taxIdCheck['fr-BE'];
1202+
taxIdCheck['fr-CA'] = taxIdCheck['en-CA'];
11731203

11741204
// Regexes for locales where characters should be omitted before checking format
11751205
const allsymbols = /[-\\\/!@#$%\^&\*\(\)\+\=\[\]]+/g;

test/validators.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10312,6 +10312,23 @@ describe('Validators', () => {
1031210312
'A1A 1A1',
1031310313
'X0A-0H0',
1031410314
'V5K 0A1',
10315+
'A1C 3S4',
10316+
'A1C3S4',
10317+
'a1c 3s4',
10318+
'V9A 7N2',
10319+
'B3K 5X5',
10320+
'K8N 5W6',
10321+
'K1A 0B1',
10322+
'B1Z 0B9',
10323+
],
10324+
invalid: [
10325+
' ',
10326+
'invalid value',
10327+
'a1a1a',
10328+
'A1A 1A1',
10329+
'K1A 0D1',
10330+
'W1A 0B1',
10331+
'Z1A 0B1',
1031510332
],
1031610333
},
1031710334
{
@@ -10517,6 +10534,8 @@ describe('Validators', () => {
1051710534
'78-399',
1051810535
'39-490',
1051910536
'38-483',
10537+
'05-800',
10538+
'54-060',
1052010539
],
1052110540
},
1052210541
{
@@ -10575,6 +10594,9 @@ describe('Validators', () => {
1057510594
'65000',
1057610595
'65080',
1057710596
'01000',
10597+
'51901',
10598+
'51909',
10599+
'49125',
1057810600
],
1057910601
},
1058010602
{
@@ -10950,6 +10972,43 @@ describe('Validators', () => {
1095010972
'658426713',
1095110973
'558426713'],
1095210974
});
10975+
test({
10976+
validator: 'isTaxID',
10977+
args: ['en-CA'],
10978+
valid: [
10979+
'000000000',
10980+
'521719666',
10981+
'469317481',
10982+
'120217450',
10983+
'480534858',
10984+
'325268597',
10985+
'336475660',
10986+
'744797853',
10987+
'130692544',
10988+
'046454286',
10989+
],
10990+
invalid: [
10991+
' ',
10992+
'any value',
10993+
'012345678',
10994+
'111111111',
10995+
'999999999',
10996+
'657449110',
10997+
'74 47 978 53',
10998+
'744 797 853',
10999+
'744-797-853',
11000+
'981062432',
11001+
'267500713',
11002+
'2675o0713',
11003+
'70597312',
11004+
'7058973122',
11005+
'069437151',
11006+
'046454281',
11007+
'146452286',
11008+
'30x92544',
11009+
'30692544',
11010+
],
11011+
});
1095311012
test({
1095411013
validator: 'isTaxID',
1095511014
args: ['en-GB'],

0 commit comments

Comments
 (0)