This repository has been archived by the owner on Apr 25, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 12
/
social-insurance-number.js
144 lines (123 loc) · 4.13 KB
/
social-insurance-number.js
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
(function(global) {
var SIN_LENGTH = 9;
var TEMPORARY_RESIDENT_FIRST_DIGIT = 9;
var BUSINESS_NUMBER_FIRST_DIGIT = 8;
// Map Canadian provinces to associated first SIN digits
var PROVINCES = {
"AB": [6],
"BC": [7],
"MB": [6],
"NB": [1],
"NF": [1],
"NS": [1],
"NT": [6],
"NU": [6],
"ON": [4, 5],
"PE": [1],
"QC": [2, 3],
"SK": [6],
"YT": [7]
};
var SocialInsuranceNumber = function(sin) {
this.sin = sin;
};
SocialInsuranceNumber.generate = function(options) {
options = options || {};
var startsWith = generateStartsWith(options.startsWith, options.doesNotStartWith, options.province);
var sinArray = String(startsWith).substring(0, (SIN_LENGTH - 1)).split("");
// Generate the next digits randomly
while(sinArray.length < (SIN_LENGTH - 1)) {
sinArray.push(randomIntBetween(0, 9));
}
sinArray.push(checkDigit(sinArray));
return sinArray.join("");
};
SocialInsuranceNumber.prototype.normalizedValue = function() {
this._normalizedValue = this._normalizedValue || String(this.sin).replace(/[^\d]/g, "");
return this._normalizedValue;
};
SocialInsuranceNumber.prototype.isValid = function() {
if(this.normalizedValue().length > SIN_LENGTH) {
return false;
}
return luhnChecksum(this.normalizedValue()) % 10 === 0;
};
SocialInsuranceNumber.prototype.isTemporary = function() {
return this.firstDigit() === TEMPORARY_RESIDENT_FIRST_DIGIT;
};
SocialInsuranceNumber.prototype.isBusinessNumber = function() {
return this.firstDigit() === BUSINESS_NUMBER_FIRST_DIGIT;
};
SocialInsuranceNumber.prototype.provinces = function() {
var provinces = [];
for(var province in PROVINCES) {
if (PROVINCES[province].indexOf(this.firstDigit()) >= 0) {
provinces.push(province);
}
}
return provinces;
};
SocialInsuranceNumber.prototype.firstDigit = function() {
return parseInt(this.normalizedValue().substring(0, 1), 10);
};
// Fast Luhn checksum code from luhn.js:
// https://gist.github.com/ShirtlessKirk/2134376
var luhnChecksum = function(sin) {
var len = SIN_LENGTH,
mul = 0,
luhnArr = [
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[0, 2, 4, 6, 8, 1, 3, 5, 7, 9]
],
sum = 0;
while (len--) {
sum += luhnArr[mul][parseInt(sin.charAt(len), 10)];
mul = mul ^ 1;
}
return sum % 10;
};
var generateStartsWith = function(startsWith, doesNotStartWith, province) {
if (startsWith) return startsWith;
if (doesNotStartWith) {
// convert a single entry to a list
doesNotStartWith = [].concat(doesNotStartWith);
var possibleSinPrefixes = Array.from(new Set([].concat.apply([], Object.values(PROVINCES))));
if (province) {
possibleSinPrefixes = PROVINCES[province];
}
var filter = function(prefix) {
return !doesNotStartWith.includes(String(prefix))
}
var validStartsWithChoices = possibleSinPrefixes.filter(filter);
if (!validStartsWithChoices.length) {
throw "Cannot find a valid number to start with.";
}
return randomChoice(validStartsWithChoices);
} else {
province = province || randomChoice(Object.keys(PROVINCES));
return randomChoice(PROVINCES[province]);
}
};
// `partialSin` has first 8 digits of the SIN for which to calculate check digit.
var checkDigit = function(partialSin) {
var checksum = luhnChecksum(partialSin.join("") + "0");
return checksum % 10 === 0 ? 0 : 10 - checksum;
};
var randomChoice = function(arr) {
return arr[Math.floor(Math.random() * arr.length)];
};
var randomIntBetween = function(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
SocialInsuranceNumber.PROVINCES = PROVINCES;
SocialInsuranceNumber.SIN_LENGTH = SIN_LENGTH;
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = SocialInsuranceNumber;
}
exports.SocialInsuranceNumber = SocialInsuranceNumber;
}
else {
global.SocialInsuranceNumber = SocialInsuranceNumber;
}
})(this);