valvat 0.9.1 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.gitignore +3 -1
- data/.rubocop.yml +15 -0
- data/CHANGES.md +36 -1
- data/Gemfile +4 -3
- data/Guardfile +6 -4
- data/README.md +99 -47
- data/Rakefile +3 -1
- data/certs/yolk.pem +21 -21
- data/gemfiles/activemodel-5 +4 -5
- data/gemfiles/activemodel-6 +7 -0
- data/gemfiles/standalone +3 -5
- data/lib/active_model/validations/valvat_validator.rb +70 -22
- data/lib/valvat.rb +7 -3
- data/lib/valvat/checksum.rb +24 -28
- data/lib/valvat/checksum/at.rb +3 -1
- data/lib/valvat/checksum/be.rb +3 -1
- data/lib/valvat/checksum/bg.rb +17 -17
- data/lib/valvat/checksum/cy.rb +2 -0
- data/lib/valvat/checksum/de.rb +5 -3
- data/lib/valvat/checksum/dk.rb +3 -1
- data/lib/valvat/checksum/ee.rb +2 -0
- data/lib/valvat/checksum/es.rb +14 -16
- data/lib/valvat/checksum/fi.rb +3 -1
- data/lib/valvat/checksum/fr.rb +4 -2
- data/lib/valvat/checksum/gb.rb +53 -21
- data/lib/valvat/checksum/gr.rb +4 -2
- data/lib/valvat/checksum/hr.rb +2 -0
- data/lib/valvat/checksum/hu.rb +2 -0
- data/lib/valvat/checksum/ie.rb +5 -6
- data/lib/valvat/checksum/it.rb +7 -5
- data/lib/valvat/checksum/lt.rb +2 -0
- data/lib/valvat/checksum/lu.rb +3 -1
- data/lib/valvat/checksum/mt.rb +2 -0
- data/lib/valvat/checksum/nl.rb +5 -3
- data/lib/valvat/checksum/pl.rb +3 -1
- data/lib/valvat/checksum/pt.rb +3 -1
- data/lib/valvat/checksum/ro.rb +2 -0
- data/lib/valvat/checksum/se.rb +5 -3
- data/lib/valvat/checksum/si.rb +4 -2
- data/lib/valvat/error.rb +31 -0
- data/lib/valvat/local.rb +8 -5
- data/lib/valvat/locales/de.yml +2 -1
- data/lib/valvat/locales/en.yml +2 -1
- data/lib/valvat/locales/es.yml +1 -0
- data/lib/valvat/locales/fr.yml +1 -0
- data/lib/valvat/locales/hu.yml +1 -0
- data/lib/valvat/locales/nl.yml +1 -0
- data/lib/valvat/locales/pl.yml +1 -0
- data/lib/valvat/locales/pt.yml +28 -28
- data/lib/valvat/lookup.rb +14 -45
- data/lib/valvat/lookup/fault.rb +31 -0
- data/lib/valvat/lookup/request.rb +37 -11
- data/lib/valvat/lookup/response.rb +37 -0
- data/lib/valvat/syntax.rb +31 -30
- data/lib/valvat/utils.rb +20 -9
- data/lib/valvat/version.rb +3 -1
- data/spec/active_model/validations/valvat_validator_spec.rb +132 -100
- data/spec/spec_helper.rb +8 -8
- data/spec/valvat/checksum/at_spec.rb +8 -6
- data/spec/valvat/checksum/be_spec.rb +8 -6
- data/spec/valvat/checksum/bg_spec.rb +9 -6
- data/spec/valvat/checksum/cy_spec.rb +5 -3
- data/spec/valvat/checksum/de_spec.rb +8 -6
- data/spec/valvat/checksum/dk_spec.rb +8 -6
- data/spec/valvat/checksum/ee_spec.rb +5 -3
- data/spec/valvat/checksum/es_spec.rb +9 -6
- data/spec/valvat/checksum/fi_spec.rb +8 -6
- data/spec/valvat/checksum/fr_spec.rb +8 -6
- data/spec/valvat/checksum/gb_spec.rb +9 -5
- data/spec/valvat/checksum/gr_spec.rb +8 -6
- data/spec/valvat/checksum/hr_spec.rb +5 -3
- data/spec/valvat/checksum/hu_spec.rb +5 -3
- data/spec/valvat/checksum/ie_spec.rb +13 -5
- data/spec/valvat/checksum/it_spec.rb +12 -10
- data/spec/valvat/checksum/lt_spec.rb +5 -3
- data/spec/valvat/checksum/lu_spec.rb +8 -6
- data/spec/valvat/checksum/mt_spec.rb +5 -3
- data/spec/valvat/checksum/nl_spec.rb +9 -7
- data/spec/valvat/checksum/pl_spec.rb +10 -8
- data/spec/valvat/checksum/pt_spec.rb +12 -13
- data/spec/valvat/checksum/ro_spec.rb +5 -3
- data/spec/valvat/checksum/se_spec.rb +10 -8
- data/spec/valvat/checksum/si_spec.rb +12 -10
- data/spec/valvat/checksum_spec.rb +25 -10
- data/spec/valvat/lookup/fault_spec.rb +34 -0
- data/spec/valvat/lookup/request_spec.rb +19 -0
- data/spec/valvat/lookup/response_spec.rb +29 -0
- data/spec/valvat/lookup_spec.rb +202 -84
- data/spec/valvat/syntax_spec.rb +59 -280
- data/spec/valvat/utils_spec.rb +76 -43
- data/spec/valvat_spec.rb +140 -152
- data/valvat.gemspec +18 -17
- metadata +61 -41
- metadata.gz.sig +0 -0
- data/.travis.yml +0 -33
- data/gemfiles/activemodel-3-2 +0 -8
- data/gemfiles/activemodel-4 +0 -8
- data/gemfiles/before-ruby21/activemodel-3-2 +0 -9
- data/gemfiles/before-ruby21/activemodel-4 +0 -9
- data/gemfiles/before-ruby21/standalone +0 -8
- data/lib/valvat/lookup/request_with_id.rb +0 -31
data/gemfiles/activemodel-5
CHANGED
data/gemfiles/standalone
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_model'
|
2
4
|
require 'valvat/syntax'
|
3
5
|
require 'valvat/lookup'
|
@@ -5,47 +7,93 @@ require 'valvat/lookup'
|
|
5
7
|
module ActiveModel
|
6
8
|
module Validations
|
7
9
|
class ValvatValidator < EachValidator
|
10
|
+
def initialize(options)
|
11
|
+
normalize_options(options)
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
8
15
|
def validate_each(record, attribute, value)
|
9
16
|
vat = Valvat(value)
|
10
17
|
iso_country_code = vat.iso_country_code
|
11
18
|
|
12
|
-
if country_does_not_match?(record, iso_country_code)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
19
|
+
is_valid = if country_does_not_match?(record, iso_country_code)
|
20
|
+
iso_country_code = iso_country_code_of(record)
|
21
|
+
false
|
22
|
+
else
|
23
|
+
vat_valid?(vat)
|
24
|
+
end
|
17
25
|
|
18
|
-
iso_country_code =
|
19
|
-
|
20
|
-
|
21
|
-
:country_adjective => I18n.t(
|
22
|
-
:"valvat.country_adjectives.#{iso_country_code.downcase}",
|
23
|
-
:default => [:"valvat.country_adjectives.eu", "european"]
|
24
|
-
)
|
25
|
-
)
|
26
|
+
iso_country_code = 'eu' if iso_country_code.blank?
|
27
|
+
|
28
|
+
add_error(is_valid, record, attribute, iso_country_code)
|
26
29
|
end
|
27
30
|
|
28
31
|
private
|
29
32
|
|
33
|
+
def vat_valid?(vat)
|
34
|
+
vat.valid? && valid_checksum?(vat) && vat_exists?(vat)
|
35
|
+
end
|
36
|
+
|
37
|
+
def valid_checksum?(vat)
|
38
|
+
return true unless options[:checksum]
|
39
|
+
|
40
|
+
vat.valid_checksum?
|
41
|
+
end
|
42
|
+
|
43
|
+
def vat_exists?(vat)
|
44
|
+
return true unless options[:lookup]
|
45
|
+
|
46
|
+
exists = vat.exists?(options[:lookup])
|
47
|
+
|
48
|
+
return true if exists.nil? && !options[:lookup][:fail_if_down]
|
49
|
+
|
50
|
+
exists
|
51
|
+
end
|
52
|
+
|
30
53
|
def country_does_not_match?(record, iso_country_code)
|
31
54
|
return false unless options[:match_country]
|
55
|
+
|
32
56
|
iso_country_code_of(record) != iso_country_code
|
33
57
|
end
|
34
58
|
|
35
59
|
def iso_country_code_of(record)
|
36
|
-
(record.send(options[:match_country]) ||
|
60
|
+
(record.send(options[:match_country]) || '').upcase
|
37
61
|
end
|
38
62
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
63
|
+
def add_error(is_valid, record, attribute, iso_country_code)
|
64
|
+
case is_valid
|
65
|
+
when false
|
66
|
+
add_invalid_vat_error(record, attribute, iso_country_code)
|
67
|
+
when nil
|
68
|
+
add_vies_down_error(record, attribute)
|
69
|
+
end
|
42
70
|
end
|
43
71
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
72
|
+
def add_invalid_vat_error(record, attribute, iso_country_code)
|
73
|
+
record.errors.add(attribute, :invalid_vat,
|
74
|
+
message: options[:message],
|
75
|
+
country_adjective: I18n.t(
|
76
|
+
:"valvat.country_adjectives.#{iso_country_code.downcase}",
|
77
|
+
default: [:"valvat.country_adjectives.eu", 'european']
|
78
|
+
))
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_vies_down_error(record, attribute)
|
82
|
+
record.errors.add(attribute, :vies_down,
|
83
|
+
message: options[:vies_down_message])
|
84
|
+
end
|
85
|
+
|
86
|
+
def normalize_options(options)
|
87
|
+
return unless options[:lookup]
|
88
|
+
|
89
|
+
options[:lookup] = case options[:lookup]
|
90
|
+
when :fail_if_down
|
91
|
+
{ fail_if_down: true }
|
92
|
+
when Hash
|
93
|
+
options[:lookup]
|
94
|
+
else
|
95
|
+
{}
|
96
|
+
end
|
49
97
|
end
|
50
98
|
end
|
51
99
|
end
|
data/lib/valvat.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'valvat/error'
|
1
4
|
require 'valvat/local'
|
2
5
|
require 'valvat/lookup'
|
3
6
|
require 'valvat/lookup/request'
|
4
|
-
require 'valvat/lookup/
|
7
|
+
require 'valvat/lookup/response'
|
8
|
+
require 'valvat/lookup/fault'
|
5
9
|
require 'active_model/validations/valvat_validator' if defined?(ActiveModel)
|
6
10
|
|
7
11
|
class Valvat
|
8
|
-
def exists?(options={})
|
12
|
+
def exists?(options = {})
|
9
13
|
Valvat::Lookup.validate(self, options)
|
10
14
|
end
|
11
|
-
|
15
|
+
alias exist? exists?
|
12
16
|
end
|
data/lib/valvat/checksum.rb
CHANGED
@@ -1,21 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Valvat
|
2
4
|
module Checksum
|
3
|
-
ALGORITHMS = {}
|
4
|
-
|
5
|
-
def self.validate(vat)
|
6
|
-
vat = Valvat(vat)
|
7
|
-
algo = ALGORITHMS[vat.iso_country_code]
|
8
|
-
Valvat::Syntax.validate(vat) && !!(algo.nil? || algo.new(vat).validate)
|
9
|
-
end
|
10
|
-
|
11
5
|
class Base
|
12
|
-
def self.inherited(klass)
|
13
|
-
ALGORITHMS[klass.name.split(/::/).last] = klass
|
14
|
-
end
|
15
|
-
|
16
6
|
attr_reader :vat
|
17
7
|
|
18
|
-
def self.check_digit_length(len=nil)
|
8
|
+
def self.check_digit_length(len = nil)
|
19
9
|
@check_digit_length = len if len
|
20
10
|
@check_digit_length || 1
|
21
11
|
end
|
@@ -39,44 +29,50 @@ class Valvat
|
|
39
29
|
end
|
40
30
|
|
41
31
|
def figures_str
|
42
|
-
str_wo_country[0..-(self.class.check_digit_length+1)]
|
32
|
+
str_wo_country[0..-(self.class.check_digit_length + 1)]
|
43
33
|
end
|
44
34
|
|
45
35
|
def figures
|
46
|
-
figures_str.split(
|
36
|
+
figures_str.split('').map(&:to_i)
|
47
37
|
end
|
48
38
|
|
49
39
|
def str_wo_country
|
50
40
|
vat.to_s_wo_country
|
51
41
|
end
|
52
42
|
|
53
|
-
def sum_figures_by
|
54
|
-
figures.reverse.each_with_index.map
|
55
|
-
yield(fig, i)
|
56
|
-
end.inject(:+)
|
43
|
+
def sum_figures_by(&block)
|
44
|
+
figures.reverse.each_with_index.map(&block).inject(:+)
|
57
45
|
end
|
58
46
|
|
59
47
|
def sum_of_squares(num)
|
60
|
-
num.to_s.split(
|
48
|
+
num.to_s.split('').map(&:to_i).inject(:+)
|
61
49
|
end
|
62
50
|
|
63
|
-
def sum_of_figures_for_at_es_it_se(reverse_ints
|
51
|
+
def sum_of_figures_for_at_es_it_se(reverse_ints: false)
|
64
52
|
ints = reverse_ints ? [2, 1] : [1, 2]
|
65
53
|
sum_figures_by do |fig, i|
|
66
|
-
sum_of_squares(fig*(i.modulo(2)
|
54
|
+
sum_of_squares(fig * (i.modulo(2).zero? ? ints[0] : ints[1]))
|
67
55
|
end
|
68
56
|
end
|
69
57
|
|
70
58
|
def sum_of_figues_for_pt_si
|
71
59
|
11 - sum_figures_by do |fig, i|
|
72
|
-
fig*(i+2)
|
60
|
+
fig * (i + 2)
|
73
61
|
end.modulo(11)
|
74
62
|
end
|
75
63
|
end
|
64
|
+
|
65
|
+
def self.validate(vat)
|
66
|
+
vat = Valvat(vat)
|
67
|
+
algo = ALGORITHMS[vat.iso_country_code]
|
68
|
+
Valvat::Syntax.validate(vat) && !!(algo.nil? || algo.new(vat).validate)
|
69
|
+
end
|
70
|
+
|
71
|
+
ALGORITHMS = Dir[File.join(__dir__, 'checksum', '*.rb')].each_with_object({}) do |path, algos|
|
72
|
+
require path.gsub(/\.rb$/, '')
|
73
|
+
|
74
|
+
classname = File.basename(path, '.rb').upcase
|
75
|
+
algos[classname] = ['Valvat', 'Checksum', classname].inject(Object, :const_get)
|
76
|
+
end.freeze
|
76
77
|
end
|
77
78
|
end
|
78
|
-
|
79
|
-
Dir[File.join(File.dirname(__FILE__), 'checksum', '*.rb')].each do |path|
|
80
|
-
# On Ruby 2.1.0 ActiveSupport goes mad if you pass it paths with .rb at the end
|
81
|
-
require path.gsub(/\.rb$/, '')
|
82
|
-
end
|
data/lib/valvat/checksum/at.rb
CHANGED
data/lib/valvat/checksum/be.rb
CHANGED
data/lib/valvat/checksum/bg.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Valvat
|
2
4
|
module Checksum
|
3
5
|
class BG < Base
|
@@ -9,6 +11,7 @@ class Valvat
|
|
9
11
|
local_person_chk = check_digit_local_natural_person
|
10
12
|
|
11
13
|
return local_person_chk if given_check_digit == local_person_chk
|
14
|
+
|
12
15
|
check_digit_foreign_natural_person
|
13
16
|
end
|
14
17
|
|
@@ -19,43 +22,40 @@ class Valvat
|
|
19
22
|
end.inject(:+).modulo(11)
|
20
23
|
|
21
24
|
return chk if chk < 10
|
22
|
-
|
25
|
+
|
26
|
+
0
|
23
27
|
end
|
24
28
|
|
25
29
|
def check_digit_foreign_natural_person
|
26
30
|
weight = [21, 19, 17, 13, 11, 9, 7, 3, 1]
|
27
31
|
|
28
|
-
|
32
|
+
figures.map do |fig|
|
29
33
|
fig * weight.shift
|
30
34
|
end.inject(:+).modulo(10)
|
31
|
-
|
32
|
-
chk
|
33
35
|
end
|
34
36
|
|
35
37
|
def check_digit_legal_person
|
36
|
-
|
37
|
-
figures.each_with_index do |fig, index|
|
38
|
-
prod += (index + 1) * fig.to_i
|
39
|
-
end
|
40
|
-
|
41
|
-
chk = prod % 11
|
38
|
+
chk = build_prod % 11
|
42
39
|
|
43
40
|
if chk == 10
|
44
|
-
|
45
|
-
figures.each_with_index do |fig, index|
|
46
|
-
prod += (index + 3) * fig.to_i
|
47
|
-
end
|
48
|
-
|
49
|
-
chk = prod % 11
|
41
|
+
chk = build_prod(3) % 11
|
50
42
|
chk = 0 if chk == 10
|
51
43
|
end
|
52
44
|
|
53
45
|
chk
|
54
46
|
end
|
55
47
|
|
48
|
+
def build_prod(add = 1)
|
49
|
+
prod = 0
|
50
|
+
figures.each_with_index do |fig, index|
|
51
|
+
prod += (index + add) * fig.to_i
|
52
|
+
end
|
53
|
+
prod
|
54
|
+
end
|
55
|
+
|
56
56
|
def natural_person?
|
57
57
|
vat.to_s_wo_country.length == 10
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
|
-
end
|
61
|
+
end
|
data/lib/valvat/checksum/cy.rb
CHANGED
data/lib/valvat/checksum/de.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Valvat
|
2
4
|
module Checksum
|
3
5
|
class DE < Base
|
@@ -8,12 +10,12 @@ class Valvat
|
|
8
10
|
prod = M
|
9
11
|
figures.each do |fig|
|
10
12
|
sum = (prod + fig).modulo(M)
|
11
|
-
sum = M if sum
|
12
|
-
prod = (2*sum).modulo(N)
|
13
|
+
sum = M if sum.zero?
|
14
|
+
prod = (2 * sum).modulo(N)
|
13
15
|
end
|
14
16
|
chk = N - prod
|
15
17
|
chk == 10 ? 0 : chk
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
19
|
-
end
|
21
|
+
end
|
data/lib/valvat/checksum/dk.rb
CHANGED
data/lib/valvat/checksum/ee.rb
CHANGED
data/lib/valvat/checksum/es.rb
CHANGED
@@ -1,33 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Valvat
|
2
4
|
module Checksum
|
3
5
|
class ES < Base
|
4
|
-
NATURAL_PERSON_CHARS = %w
|
5
|
-
NATURAL_PERSON_EXP = /\A(
|
6
|
-
LEGAL_PERSON_CHARS = [false] + %w
|
7
|
-
LEGAL_PERSON_EXP = /\A[NPQRSW]
|
6
|
+
NATURAL_PERSON_CHARS = %w[T R W A G M Y F P D X B N J Z S Q V H L C K E].freeze
|
7
|
+
NATURAL_PERSON_EXP = /\A(\d{8}[ABCDEFGHJKLMNPQRSTVWXYZ]|[KLMXYZ]\d{7}[ABCDEFGHJKLMNPQRSTVWXYZ])\Z/.freeze
|
8
|
+
LEGAL_PERSON_CHARS = [false] + %w[A B C D E F G H I J]
|
9
|
+
LEGAL_PERSON_EXP = /\A[NPQRSW]\d{7}[ABCDEFGHIJ]\Z/.freeze
|
10
|
+
NIE_DIGIT_BY_LETTER = %w[X Y Z].freeze
|
8
11
|
|
9
12
|
def check_digit
|
10
13
|
natural_person? ? check_digit_natural_person : check_digit_legal_person
|
11
14
|
end
|
12
15
|
|
13
16
|
def check_digit_natural_person
|
14
|
-
nie_digit = nil
|
15
17
|
letter = vat.to_s_wo_country[0]
|
16
|
-
|
17
|
-
nie_digit = 0
|
18
|
-
elsif letter == 'Y'
|
19
|
-
nie_digit = 1
|
20
|
-
elsif letter == 'Z'
|
21
|
-
nie_digit = 2
|
22
|
-
end
|
18
|
+
nie_digit = NIE_DIGIT_BY_LETTER.index(letter)
|
23
19
|
NATURAL_PERSON_CHARS["#{nie_digit}#{figures_str}".to_i.modulo(23)]
|
24
20
|
end
|
25
21
|
|
26
22
|
def check_digit_legal_person
|
27
|
-
chk = 10 - sum_of_figures_for_at_es_it_se(true).modulo(10)
|
28
|
-
legal_foreign_person?
|
29
|
-
LEGAL_PERSON_CHARS[chk]
|
23
|
+
chk = 10 - sum_of_figures_for_at_es_it_se(reverse_ints: true).modulo(10)
|
24
|
+
if legal_foreign_person?
|
25
|
+
LEGAL_PERSON_CHARS[chk]
|
26
|
+
else
|
30
27
|
(chk == 10 ? 0 : chk)
|
28
|
+
end
|
31
29
|
end
|
32
30
|
|
33
31
|
def given_check_digit
|
@@ -52,4 +50,4 @@ class Valvat
|
|
52
50
|
end
|
53
51
|
end
|
54
52
|
end
|
55
|
-
end
|
53
|
+
end
|