social_security_number 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +97 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/social_security_number/country/be.rb +33 -0
- data/lib/social_security_number/country/ca.rb +48 -0
- data/lib/social_security_number/country/ch.rb +34 -0
- data/lib/social_security_number/country/cn.rb +42 -0
- data/lib/social_security_number/country/cz.rb +46 -0
- data/lib/social_security_number/country/de.rb +80 -0
- data/lib/social_security_number/country/dk.rb +48 -0
- data/lib/social_security_number/country/es.rb +41 -0
- data/lib/social_security_number/country/fi.rb +30 -0
- data/lib/social_security_number/country/fr.rb +32 -0
- data/lib/social_security_number/country/ie.rb +38 -0
- data/lib/social_security_number/country/is.rb +38 -0
- data/lib/social_security_number/country/it.rb +27 -0
- data/lib/social_security_number/country/lt.rb +57 -0
- data/lib/social_security_number/country/mx.rb +56 -0
- data/lib/social_security_number/country/nl.rb +26 -0
- data/lib/social_security_number/country/no.rb +56 -0
- data/lib/social_security_number/country/pk.rb +14 -0
- data/lib/social_security_number/country/se.rb +54 -0
- data/lib/social_security_number/country/uk.rb +68 -0
- data/lib/social_security_number/country/us.rb +52 -0
- data/lib/social_security_number/country.rb +103 -0
- data/lib/social_security_number/validator.rb +40 -0
- data/lib/social_security_number/version.rb +3 -0
- data/lib/social_security_number.rb +7 -0
- data/social_security_number.gemspec +36 -0
- metadata +126 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Fr validates France INSEE code
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#France
|
4
|
+
class Fr < Country
|
5
|
+
def validate
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
7
|
+
'bad number format'
|
8
|
+
elsif !check_control_sum
|
9
|
+
'number control sum invalid'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
REGEXP = /^(?<gnd>\d{1})(?<year>\d{2})(?<month>\d{2})(?<department1>\d{1})(?<department2>[0-9AB]{1})(?<place>\d{3})(?<indv>\d{3})(?<ctrl>\d{2})$/
|
16
|
+
|
17
|
+
def check_control_sum
|
18
|
+
count_last_number == @control_number.to_i
|
19
|
+
end
|
20
|
+
|
21
|
+
def count_last_number
|
22
|
+
number = @civil_number[0..12]
|
23
|
+
department = @civil_number[5..6]
|
24
|
+
if department == '2A'
|
25
|
+
number = ("#{@civil_number[0..4]}19#{@civil_number[7..12]}")
|
26
|
+
elsif department == '2B'
|
27
|
+
number = ("#{@civil_number[0..4]}18#{@civil_number[7..12]}")
|
28
|
+
end
|
29
|
+
97 - (number.to_i % 97)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Ie validates Ireland Personal Public Service Number (PPS No)
|
3
|
+
# https://en.wikipedia.org/wiki/Personal_Public_Service_Number
|
4
|
+
# http://www.welfare.ie/en/Pages/Extension-of-the-Personal-Public-Service-Number-Range.aspx
|
5
|
+
class Ie < Country
|
6
|
+
def validate
|
7
|
+
@error = if !check_by_regexp(REGEXP)
|
8
|
+
'bad number format'
|
9
|
+
elsif !check_control_simbol
|
10
|
+
'number control sum invalid'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
MODULUS = 23
|
17
|
+
|
18
|
+
CONTROLCIPHERS = [8, 7, 6, 5, 4, 3, 2].freeze
|
19
|
+
|
20
|
+
REGEXP = /^(?<indv>\d{7})[- .]?(?<ctrl>[A-W])[AHWTX]?$/
|
21
|
+
|
22
|
+
def check_control_simbol
|
23
|
+
count_last_simbol.to_s == @control_number.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def count_last_simbol
|
27
|
+
sum = calc_sum(@individual, CONTROLCIPHERS)
|
28
|
+
alfabet = %w[W A B C D E F G H I J K L M N O P Q R S U V]
|
29
|
+
value = if @civil_number[-1] != @control_number.to_s
|
30
|
+
alfabet.index(@civil_number[-1]).to_i
|
31
|
+
else
|
32
|
+
0
|
33
|
+
end
|
34
|
+
last_simbol = (sum + (value * 9)) % MODULUS
|
35
|
+
'WABCDEFGHIJKLMNOPQRSTUV'[last_simbol]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Is validates Iceland personal and organisation identity code (Kennitala)
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Iceland
|
4
|
+
class Is < Country
|
5
|
+
def validate
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
7
|
+
'bad number format'
|
8
|
+
elsif !birth_date
|
9
|
+
'number birth date is invalid'
|
10
|
+
elsif !check_control_sum
|
11
|
+
'number control sum invalid'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def day
|
16
|
+
@day = @parsed_civil_number[:day].to_i % 40
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
MODULUS = 11
|
22
|
+
|
23
|
+
CONTROLCIPHERS = [3, 2, 7, 6, 5, 4, 3, 2].freeze
|
24
|
+
|
25
|
+
DATE_REGEXP = /(?<day>[01234567]\d)(?<month>\d{2})(?<year>\d{2})/
|
26
|
+
REGEXP = /^#{DATE_REGEXP}[ .-]?(?<indv>\d{2})(?<ctrl>\d{1})(?<cntr>[09])$/
|
27
|
+
|
28
|
+
|
29
|
+
def check_control_sum
|
30
|
+
count_control_number == @control_number.to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
def count_control_number
|
34
|
+
sum = calc_sum(digit_number[0..9], CONTROLCIPHERS)
|
35
|
+
11 - sum % MODULUS
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::It validates Italy tax code for individuals (Codice fiscale)
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Italy
|
4
|
+
class It < Country
|
5
|
+
def validate
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
7
|
+
'bad number format'
|
8
|
+
elsif !birth_date
|
9
|
+
'number birth date is invalid'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def month
|
14
|
+
months = %w[A B C D E H L M P R S T]
|
15
|
+
@month = months.index(@parsed_civil_number[:month]).to_i + 1
|
16
|
+
end
|
17
|
+
|
18
|
+
def day
|
19
|
+
d = @parsed_civil_number[:day].to_i
|
20
|
+
@day = d >= 40 ? d - 40 : d
|
21
|
+
end
|
22
|
+
|
23
|
+
DATE_REGEXP = /(?<year>[\dL-V]{2})(?<month>[ABCDEHLMPRST])(?<day>[\dL-V]{2})/
|
24
|
+
INVD_REGEXP = /(?<indv>[A-Z][\dL-V]{3})(?<indv2>[A-Z]{1})/
|
25
|
+
REGEXP = /^(?<nm>[A-Z]{6})-?#{DATE_REGEXP}#{INVD_REGEXP}$/
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Lt validates Lithuania Personal Code (Asmens kodas)
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Lithuania
|
4
|
+
class Lt < Country
|
5
|
+
def validate
|
6
|
+
@error = if !check_digits
|
7
|
+
'it is not number'
|
8
|
+
elsif !check_length(11)
|
9
|
+
'number should be length of 11'
|
10
|
+
elsif @parsed_civil_number[:gnd].to_i > 6
|
11
|
+
'gender number is not recognized'
|
12
|
+
elsif !birth_date
|
13
|
+
'number birth date is invalid'
|
14
|
+
elsif !check_control_sum
|
15
|
+
'number control sum invalid'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def year
|
20
|
+
if @parsed_civil_number
|
21
|
+
base = case @parsed_civil_number[:gnd].to_i
|
22
|
+
when 1..2 then 1800
|
23
|
+
when 3..4 then 1900
|
24
|
+
when 5..6 then 2000
|
25
|
+
else
|
26
|
+
0
|
27
|
+
end
|
28
|
+
@year = base + @parsed_civil_number[:year].to_i
|
29
|
+
else
|
30
|
+
0
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
MODULUS = 11
|
37
|
+
|
38
|
+
CONTROLCIPHERS_1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 1].freeze
|
39
|
+
CONTROLCIPHERS_2 = [3, 4, 5, 6, 7, 8, 9, 1, 2, 3].freeze
|
40
|
+
|
41
|
+
REGEXP = /^(?<gnd>\d{1})#{SHORT_DATE_REGEXP}-?(?<indv>\d{3})(?<ctrl>\d{1})$/
|
42
|
+
|
43
|
+
def check_control_sum
|
44
|
+
count_last_number == @control_number.to_i
|
45
|
+
end
|
46
|
+
|
47
|
+
def count_last_number
|
48
|
+
sum = calc_sum(@civil_number[0..9], CONTROLCIPHERS_1)
|
49
|
+
last_number = sum % MODULUS
|
50
|
+
return last_number if last_number < 10
|
51
|
+
sum = calc_sum(@civil_number[0..9], CONTROLCIPHERS_2)
|
52
|
+
last_number = sum % MODULUS
|
53
|
+
return last_number if last_number < 10
|
54
|
+
0
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Mx validates Mexico Unique Population Registry Code (Clave Única de Registro de Población (CURP))
|
3
|
+
# https://es.stackoverflow.com/questions/31039/c%C3%B3mo-validar-una-curp-de-m%C3%A9xico
|
4
|
+
# https://en.wikipedia.org/wiki/Unique_Population_Registry_Code
|
5
|
+
# https://es.wikipedia.org/wiki/Registro_Federal_de_Contribuyentes_(M%C3%A9xico)
|
6
|
+
class Mx < Country
|
7
|
+
def validate
|
8
|
+
@error = if !check_by_regexp(REGEXP)
|
9
|
+
'bad number format'
|
10
|
+
elsif !birth_date
|
11
|
+
'number birth date is invalid'
|
12
|
+
elsif !check_control_sum
|
13
|
+
'number control sum invalid'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def gender
|
18
|
+
@gender ||= @parsed_civil_number[:gnd].to_s == 'H' ? :male : :female
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
MODULUS = 10
|
24
|
+
|
25
|
+
# the first character is the initial of the first last name
|
26
|
+
LINIT = /(?<linit>[A-Z])/
|
27
|
+
# the initials of the second last name and the name
|
28
|
+
LINIT_2 = /(?<linit2>[A-Z]{2})/
|
29
|
+
# the first internal consonants of surnames and names
|
30
|
+
FIC = /(?<fic>[B-DF-HJ-NP-TV-Z]{3})/
|
31
|
+
FEDERAL = /(?<federal>(AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE))/
|
32
|
+
SURNAME = /(?<surname>[AEIOUX])/
|
33
|
+
|
34
|
+
REGEXP = /^#{LINIT}#{SURNAME}#{LINIT_2}[ .-]?#{SHORT_DATE_REGEXP}[ .-]?(?<gnd>[HM]{1})[ .-]?#{FEDERAL}[ .-]?#{FIC}[ .-]?(?<homoclave>[A-Z\d])[ .-]?(?<ctrl>\d{1})$/
|
35
|
+
|
36
|
+
def check_control_sum
|
37
|
+
count_last_number.to_i == @control_number.to_i
|
38
|
+
end
|
39
|
+
|
40
|
+
def count_last_number
|
41
|
+
n = number
|
42
|
+
alfabet = %W[0 1 2 3 4 5 6 7 8 9 A B C
|
43
|
+
D E F G H I J K L M N Ñ O P Q R S T U V W X Y Z]
|
44
|
+
sum = 0
|
45
|
+
17.times do |i|
|
46
|
+
sum += alfabet.index(n[i]).to_i * (18 - i)
|
47
|
+
end
|
48
|
+
last_number = 10 - sum % MODULUS
|
49
|
+
last_number == 10 ? 0 : last_number
|
50
|
+
end
|
51
|
+
|
52
|
+
def number
|
53
|
+
@civil_number.gsub(' |.|-', '')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Nl validates Netherlands Citizen's Service Number (Burgerservicenummer)
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Netherlands
|
4
|
+
class Nl < Country
|
5
|
+
def validate
|
6
|
+
@error = if !check_digits
|
7
|
+
'it is not number'
|
8
|
+
elsif !check_length(CONTROLCIPHERS.size)
|
9
|
+
'number should be length of 9 or 8'
|
10
|
+
elsif !check_control_sum
|
11
|
+
'number control sum invalid'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
MODULUS = 11
|
18
|
+
|
19
|
+
CONTROLCIPHERS = [9, 8, 7, 6, 5, 4, 3, 2, -1].freeze
|
20
|
+
|
21
|
+
def check_control_sum
|
22
|
+
sum = calc_sum(@civil_number, CONTROLCIPHERS)
|
23
|
+
(sum % MODULUS).zero?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::No validates Norway eleven-digit birth number (fødselsnummer)
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Norway
|
4
|
+
class No < Country
|
5
|
+
def validate
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
7
|
+
'bad number format'
|
8
|
+
elsif !birth_date
|
9
|
+
'number birth date is invalid'
|
10
|
+
elsif !check_control_digit_1
|
11
|
+
'first control code invalid'
|
12
|
+
elsif !check_control_digit_2
|
13
|
+
'second control code invalid'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def year
|
18
|
+
if @parsed_civil_number
|
19
|
+
year_value = (@parsed_civil_number[:indv].to_i * 10 + @parsed_civil_number[:gnd].to_i).to_i
|
20
|
+
base = case year_value
|
21
|
+
when 000..499 then 1900
|
22
|
+
when 500..899 then @parsed_civil_number[:year].to_i >= 54 ? 1800 : 2000
|
23
|
+
when 900..999 then @parsed_civil_number[:year].to_i >= 40 ? 1900 : 2000
|
24
|
+
else
|
25
|
+
0
|
26
|
+
end
|
27
|
+
@year = base + @parsed_civil_number[:year].to_i
|
28
|
+
else
|
29
|
+
0
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def day
|
34
|
+
@day = @parsed_civil_number[:day].to_i % 40
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
MODULUS = 11
|
40
|
+
|
41
|
+
CONTROLCIPHERS_1 = [3, 7, 6, 1, 8, 9, 4, 5, 2].freeze
|
42
|
+
CONTROLCIPHERS_2 = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2].freeze
|
43
|
+
|
44
|
+
REGEXP = /^#{SHORT_DATE2_REGEXP}[-+]?(?<indv>\d{2})(?<gnd>\d{1})(?<ctrl>\d{2})$/
|
45
|
+
|
46
|
+
def check_control_digit_1
|
47
|
+
ctrl = 11 - calc_sum(digit_number[0, 9], CONTROLCIPHERS_1) % MODULUS
|
48
|
+
(ctrl % MODULUS).to_s == @civil_number[-2]
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_control_digit_2
|
52
|
+
ctrl = 11 - calc_sum(digit_number[0, 10], CONTROLCIPHERS_2) % MODULUS
|
53
|
+
(ctrl % MODULUS).to_s == @civil_number[-1]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Pk validates Pakistan computerised national identity card number (CNIC)
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Pakistan
|
4
|
+
# https://www.geo.tv/latest/157233-secret-behind-every-digit-of-the-cnic-number
|
5
|
+
class Pk < Country
|
6
|
+
def validate
|
7
|
+
@error = unless check_by_regexp(REGEXP)
|
8
|
+
'bad number format'
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
REGEXP = /^(?<location>\d{5})-?(?<family_number>\d{7})-?(?<gnd>\d{1})$/
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Se validates Sweden Personal Identity Number (personnummer)
|
3
|
+
# https://en.wikipedia.org/wiki/National_identification_number#Sweden
|
4
|
+
class Se < Country
|
5
|
+
def validate
|
6
|
+
@error = if !check_by_regexp(REGEXP)
|
7
|
+
'bad number format'
|
8
|
+
elsif !birth_date
|
9
|
+
'number birth date is invalid'
|
10
|
+
elsif !check_control_digit
|
11
|
+
'number control sum invalid'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def year
|
16
|
+
if @parsed_civil_number
|
17
|
+
base = case @parsed_civil_number[:year].to_i
|
18
|
+
when 1..40 then 2000
|
19
|
+
when 40..99 then 1900
|
20
|
+
else
|
21
|
+
0
|
22
|
+
end
|
23
|
+
@year = base + @parsed_civil_number[:year].to_i
|
24
|
+
else
|
25
|
+
0
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
MODULUS = 10
|
32
|
+
|
33
|
+
CONTROLCIPHERS = [2, 1, 2, 1, 2, 1, 2, 1, 2].freeze
|
34
|
+
|
35
|
+
REGEXP = /^#{DATE_REGEXP}-(?<indv>\d{2})(?<gnd>\d{1})(?<ctrl>\d{1})$/
|
36
|
+
|
37
|
+
def check_control_digit
|
38
|
+
sum = checksum(:even)
|
39
|
+
control_number = (sum % 10 != 0) ? 10 - (sum % 10) : 0
|
40
|
+
control_number.to_i == @control_number.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def checksum(operation)
|
44
|
+
i = 0
|
45
|
+
compare_method = operation == :even ? :== : :>
|
46
|
+
digit_number[0..8].reverse.split('').reduce(0) do |sum, c|
|
47
|
+
n = c.to_i
|
48
|
+
weight = (i % 2).send(compare_method, 0) ? n * 2 : n
|
49
|
+
i += 1
|
50
|
+
sum += weight < 10 ? weight : weight - 9
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Uk validates United Kingdom National Insurance number (NINO) and
|
3
|
+
# National Health Service number, CHI (Community Health Index) number
|
4
|
+
# https://en.wikipedia.org/wiki/National_identification_number#United_Kingdom
|
5
|
+
class Uk < Country
|
6
|
+
def validate
|
7
|
+
@error = if !validate_formats
|
8
|
+
'bad number format'
|
9
|
+
elsif check_length(10) && !(nhs_validation || chi_validation)
|
10
|
+
'bad CHI, NHS number'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
NINO_REGEXP = /^(?<first_letter>[A-CEGHJ-PRSTW-Z])[- ]?(?<second_letter>[A-CEGHJ-NPRSTW-Z])[- ]?(?<first_numbers>\d{2})[- ]?(?<second_numbers>\d{2})[- ]?(?<last_numbers>\d{2})[- ]?(?<last_simbols>[A-D\s])$/
|
17
|
+
NHS_REGEXP = /^(?<first_number>[0-9]{3})[- ]?(?<second_number>[0-9]{3})[- ]?(?<last_number>[0-9]{4})$/
|
18
|
+
CHI_REGEXP = /^#{SHORT_DATE2_REGEXP}[- ]?(?<last_number>[0-9]{4})$/
|
19
|
+
|
20
|
+
def validate_formats
|
21
|
+
check_nino_format || check_by_regexp(NHS_REGEXP) || check_by_regexp(CHI_REGEXP)
|
22
|
+
end
|
23
|
+
|
24
|
+
def check_nino_format
|
25
|
+
check_by_regexp(NINO_REGEXP) && !check_by_regexp(/^(GB|BG|NK|KN|TN|NT|ZZ)/)
|
26
|
+
end
|
27
|
+
|
28
|
+
def nhs_validation
|
29
|
+
check_control_sum
|
30
|
+
end
|
31
|
+
|
32
|
+
def chi_validation
|
33
|
+
check_date
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_date
|
37
|
+
day = @civil_number[0..1]
|
38
|
+
month = @civil_number[2..3]
|
39
|
+
year = @civil_number[4..6]
|
40
|
+
|
41
|
+
Date.valid_date?(base_year(year).to_i, month.to_i, day.to_i)
|
42
|
+
end
|
43
|
+
|
44
|
+
def base_year(year)
|
45
|
+
current_year = Time.now.year % 100
|
46
|
+
offset_year = year.to_i
|
47
|
+
offset_year += 100 if year and offset_year < current_year
|
48
|
+
1900 + offset_year
|
49
|
+
end
|
50
|
+
|
51
|
+
def check_control_sum
|
52
|
+
count_last_number == @civil_number[9].to_i
|
53
|
+
end
|
54
|
+
|
55
|
+
def count_last_number
|
56
|
+
result_array = []
|
57
|
+
9.times do |i|
|
58
|
+
result_array << ((11 - (i + 1)) * @civil_number[i].to_i)
|
59
|
+
end
|
60
|
+
if (11 - (result_array.inject(:+) % 11)) == 10
|
61
|
+
return false
|
62
|
+
else
|
63
|
+
return 0 if result_array.inject(:+) % 11 == 0
|
64
|
+
return 11 - (result_array.inject(:+) % 11)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Us validates U.S. (America) Social Security number (SSN) and
|
3
|
+
# Individual Taxpayer Identification Number (ITIN), Employer Identification Number (EIN)
|
4
|
+
# https://en.wikipedia.org/wiki/National_identification_number#United_States
|
5
|
+
# https://www.ssa.gov/employer/verifySSN.htm
|
6
|
+
# https://en.wikipedia.org/wiki/Social_Security_number
|
7
|
+
# https://en.wikipedia.org/wiki/Individual_Taxpayer_Identification_Number
|
8
|
+
class Us < Country
|
9
|
+
def validate
|
10
|
+
@error = if !validate_formats
|
11
|
+
'bad number format'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
SSN_REGEXP = /^(?<area>\d{3})-(?<group>\d{2})-(?<invidual>\d{4})$/
|
18
|
+
ITIN_REGEXP = /^(?<area>\d{3})-(?<group>\d{2})-(?<invidual>\d{4})$/
|
19
|
+
EIN_REGEXP = /^(?<area>\d{2})-(?<group>\d{7})$/
|
20
|
+
|
21
|
+
def validate_formats
|
22
|
+
(check_by_regexp(SSN_REGEXP) && validate_ssn) ||
|
23
|
+
(check_by_regexp(ITIN_REGEXP) && validate_itin) ||
|
24
|
+
(check_by_regexp(EIN_REGEXP) && validate_ein)
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_ssn
|
28
|
+
matches = @civil_number.match(self.class::SSN_REGEXP) || (return nil)
|
29
|
+
|
30
|
+
if matches[:area] == '000' || matches[:area] == '666' || matches[:group] == '00' ||
|
31
|
+
matches[:invidual] == '0000' || matches[:area][0] == '9'
|
32
|
+
return false
|
33
|
+
else
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate_itin
|
39
|
+
matches = @civil_number.match(self.class::ITIN_REGEXP) || (return nil)
|
40
|
+
if [70..88].include?(matches[:group].to_i) || matches[:area][0] != '9'
|
41
|
+
return false
|
42
|
+
else
|
43
|
+
return true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate_ein
|
48
|
+
matches = @civil_number.match(self.class::EIN_REGEXP) || (return nil)
|
49
|
+
matches[:area] =~ /^(0[1-6]||1[0-6]|2[0-7]|[35]\d|[468][0-8]|7[1-7]|9[0-58-9])$/
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Country
|
3
|
+
class Country
|
4
|
+
require 'date'
|
5
|
+
|
6
|
+
attr_accessor :civil_number, :birth_date, :individual,
|
7
|
+
:control_number, :error
|
8
|
+
|
9
|
+
DATE_REGEXP = /(?<year>\d{4})(?<month>\d{2})(?<day>\d{2})/
|
10
|
+
SHORT_DATE2_REGEXP = /(?<day>\d{2})(?<month>\d{2})(?<year>\d{2})/
|
11
|
+
SHORT_DATE_REGEXP = /(?<year>\d{2})(?<month>\d{2})(?<day>\d{2})/
|
12
|
+
|
13
|
+
def initialize(civil_number)
|
14
|
+
@civil_number = civil_number.to_s.upcase
|
15
|
+
values_from_number if self.class.const_defined?('REGEXP')
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
@error = nil
|
20
|
+
validate
|
21
|
+
@error.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
def parsed_civil_number
|
25
|
+
@parsed_civil_number ||= @civil_number.match(self.class::REGEXP)
|
26
|
+
end
|
27
|
+
|
28
|
+
def digit_number
|
29
|
+
@civil_number.gsub(/[^\d]/, '')
|
30
|
+
end
|
31
|
+
|
32
|
+
def gender
|
33
|
+
return unless @parsed_civil_number.names.include?('gnd')
|
34
|
+
@gender ||= @parsed_civil_number[:gnd].to_i.odd? ? :male : :female
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def calc_sum(number, ciphers)
|
40
|
+
digits = number.split(//)
|
41
|
+
|
42
|
+
sum = 0
|
43
|
+
digits.each_with_index do |digit, i|
|
44
|
+
sum += digit.to_i * ciphers[i].to_i
|
45
|
+
end
|
46
|
+
sum
|
47
|
+
end
|
48
|
+
|
49
|
+
def check_digits
|
50
|
+
return false unless @civil_number =~ /\A\d+\z/
|
51
|
+
true
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_by_regexp(regexp)
|
55
|
+
return false unless @civil_number =~ regexp
|
56
|
+
true
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_length(size)
|
60
|
+
return false unless @civil_number.length == size
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def year
|
65
|
+
return unless @parsed_civil_number.names.include?('year')
|
66
|
+
current_year = Time.now.year % 100
|
67
|
+
offset_year = @parsed_civil_number[:year].to_i
|
68
|
+
offset_year += 100 if offset_year && offset_year < current_year
|
69
|
+
@year ||= 1900 + offset_year.to_i
|
70
|
+
end
|
71
|
+
|
72
|
+
def month
|
73
|
+
@month ||= value_from_parsed_civil_number('month').to_i
|
74
|
+
end
|
75
|
+
|
76
|
+
def day
|
77
|
+
@day ||= value_from_parsed_civil_number('day').to_i
|
78
|
+
end
|
79
|
+
|
80
|
+
def date
|
81
|
+
year
|
82
|
+
month
|
83
|
+
day
|
84
|
+
return unless @year && @month && @day &&
|
85
|
+
Date.valid_date?(@year, @month, @day)
|
86
|
+
@birth_date = Date.new(@year, @month, @day)
|
87
|
+
end
|
88
|
+
|
89
|
+
def value_from_parsed_civil_number(key)
|
90
|
+
return unless @parsed_civil_number.names.include?(key)
|
91
|
+
@parsed_civil_number[key.to_sym].to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
def values_from_number
|
95
|
+
parsed_civil_number
|
96
|
+
return unless @parsed_civil_number
|
97
|
+
@individual = value_from_parsed_civil_number('indv')
|
98
|
+
@control_number = value_from_parsed_civil_number('ctrl')
|
99
|
+
gender
|
100
|
+
date
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module SocialSecurityNumber
|
2
|
+
# SocialSecurityNumber::Validator
|
3
|
+
class Validator
|
4
|
+
SUPPORTED_COUNTRY_CODES = %w[BE CA CH CN DE DK ES FI FR IE
|
5
|
+
IS IT LT MX NL NO PK SE UK US].freeze
|
6
|
+
|
7
|
+
attr_accessor :civil_number, :country_code, :error
|
8
|
+
|
9
|
+
def initialize(params = {})
|
10
|
+
@civil_number = params[:number].to_s.strip.gsub(/\s+/, '').upcase
|
11
|
+
@country_code = params[:country_code].to_s.strip.gsub(/\s+/, '').upcase
|
12
|
+
|
13
|
+
@birth_date = params[:birth_date] ? params[:birth_date] : nil
|
14
|
+
@gender = params[:gender] ? params[:gender] : nil
|
15
|
+
|
16
|
+
unless self.class::SUPPORTED_COUNTRY_CODES.include?(@country_code)
|
17
|
+
raise "Unexpected country code '#{country_code}' that is not yet supported"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
civil_number = SocialSecurityNumber.const_get(@country_code.capitalize).new(@civil_number)
|
23
|
+
|
24
|
+
if civil_number.valid?
|
25
|
+
if !@birth_date.nil? && !civil_number.birth_date.nil? && civil_number.birth_date.to_s != @birth_date.to_s
|
26
|
+
@error = "birth date #{@birth_date} dont match #{civil_number.birth_date}"
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
if !@gender.nil? && !civil_number.gender.nil? && civil_number.gender.to_s.strip != @gender.to_s.strip
|
30
|
+
@error = "gender #{@gender} dont match #{civil_number.gender}"
|
31
|
+
return false
|
32
|
+
end
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
@error = civil_number.error
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'social_security_number/version'
|
2
|
+
|
3
|
+
module SocialSecurityNumber
|
4
|
+
autoload :Validator, 'social_security_number/validator'
|
5
|
+
autoload :Country, 'social_security_number/country'
|
6
|
+
Dir["#{__dir__}/social_security_number/country/*.rb"].each { |f| require f }
|
7
|
+
end
|