valvat 0.4.7 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rbenv-version +1 -1
- data/CHANGES.md +8 -1
- data/Guardfile +1 -1
- data/README.md +27 -2
- data/lib/valvat.rb +5 -0
- data/lib/valvat/active_model.rb +4 -1
- data/lib/valvat/checksum.rb +60 -0
- data/lib/valvat/checksum/at.rb +18 -0
- data/lib/valvat/checksum/be.rb +13 -0
- data/lib/valvat/checksum/bg.rb +61 -0
- data/lib/valvat/checksum/de.rb +21 -0
- data/lib/valvat/checksum/dk.rb +20 -0
- data/lib/valvat/checksum/es.rb +45 -0
- data/lib/valvat/checksum/fi.rb +15 -0
- data/lib/valvat/checksum/gr.rb +14 -0
- data/lib/valvat/checksum/ie.rb +29 -0
- data/lib/valvat/checksum/it.rb +22 -0
- data/lib/valvat/checksum/lu.rb +13 -0
- data/lib/valvat/checksum/nl.rb +17 -0
- data/lib/valvat/checksum/pl.rb +14 -0
- data/lib/valvat/checksum/pt.rb +14 -0
- data/lib/valvat/checksum/se.rb +30 -0
- data/lib/valvat/checksum/si.rb +19 -0
- data/lib/valvat/lookup.rb +8 -5
- data/lib/valvat/lookup/request.rb +1 -3
- data/lib/valvat/syntax.rb +0 -1
- data/lib/valvat/version.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/valvat/active_model_spec.rb +18 -0
- data/spec/valvat/checksum/at_spec.rb +15 -0
- data/spec/valvat/checksum/be_spec.rb +15 -0
- data/spec/valvat/checksum/bg_spec.rb +15 -0
- data/spec/valvat/checksum/de_spec.rb +15 -0
- data/spec/valvat/checksum/dk_spec.rb +15 -0
- data/spec/valvat/checksum/es_spec.rb +15 -0
- data/spec/valvat/checksum/fi_spec.rb +15 -0
- data/spec/valvat/checksum/gr_spec.rb +15 -0
- data/spec/valvat/checksum/ie_spec.rb +15 -0
- data/spec/valvat/checksum/it_spec.rb +23 -0
- data/spec/valvat/checksum/lu_spec.rb +15 -0
- data/spec/valvat/checksum/nl_spec.rb +15 -0
- data/spec/valvat/checksum/pl_spec.rb +19 -0
- data/spec/valvat/checksum/pt_spec.rb +15 -0
- data/spec/valvat/checksum/se_spec.rb +19 -0
- data/spec/valvat/checksum/si_spec.rb +23 -0
- data/spec/valvat/checksum_spec.rb +25 -0
- data/spec/valvat/lookup_spec.rb +1 -1
- data/spec/valvat_spec.rb +13 -0
- data/valvat.gemspec +2 -1
- metadata +83 -44
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2422dd0589a6a76903426d5b4e738e3b3c66c9c8
|
4
|
+
data.tar.gz: 0f473b7a536cc5f8eb48a9071821e09f88f4e213
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c871aec23db77c958785bb116b03348dd030946aeec3ed85b923b991fc98c084ebf146bda3a8a77459b411d556f14f39d8b0d612f758ec45122587999c2fc47e
|
7
|
+
data.tar.gz: ddea5c3424e6f95c39858a6d1b1ef418b9b9afc8f1d65ce17cdf189ea8106c3446136230159c322307dd71e337e58000254df5d6c980a62cefba344c775fb6e2
|
data/.rbenv-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0-p247
|
data/CHANGES.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
### dev
|
2
2
|
|
3
|
-
[full changelog](http://github.com/yolk/valvat/compare/v0.
|
3
|
+
[full changelog](http://github.com/yolk/valvat/compare/v0.5.0...master)
|
4
|
+
|
5
|
+
### 0.5.0 / 2013-07-18
|
6
|
+
|
7
|
+
[full changelog](http://github.com/yolk/valvat/compare/v0.4.7...v0.5.0)
|
8
|
+
|
9
|
+
* Added experimental checksum verification for 17 european countries (with help from [kirichkov](https://github.com/kirichkov))
|
10
|
+
* Works now with current version of savon gem (2.2.0) (by [nevesenin](https://github.com/nevesenin))
|
4
11
|
|
5
12
|
### 0.4.7 / 2013-07-18
|
6
13
|
|
data/Guardfile
CHANGED
data/README.md
CHANGED
@@ -8,9 +8,10 @@ Validates european vat numbers. Standalone or as a ActiveModel validator.
|
|
8
8
|
* Lookup via the VIES web service
|
9
9
|
* (Optional) ActiveModel/Rails3 integration
|
10
10
|
* Works standalone without ActiveModel
|
11
|
-
* I18n locales for country specific error messages in
|
11
|
+
* I18n locales for country specific error messages in English, German, Swedish, Italian, Portuguese, Polish, Bulgarian, Romanian and Latvian.
|
12
|
+
* *Experimental* checksum verification
|
12
13
|
|
13
|
-
valvat is tested and works with ruby 1.8.7/1.9.3 and ActiveModel 3.2.
|
14
|
+
valvat is tested and works with ruby 1.8.7/1.9.3/2.0 and ActiveModel 3.2/4.0
|
14
15
|
|
15
16
|
## Installation
|
16
17
|
|
@@ -42,6 +43,22 @@ Or to lookup a vat number string directly via VIES web service:
|
|
42
43
|
Valvat::Lookup.validate("DE345789003")
|
43
44
|
=> true or false or nil
|
44
45
|
|
46
|
+
## Experimental checksum verification
|
47
|
+
|
48
|
+
valvat allows to check vat numbers from AT, BE, BG, DE, DK, ES, FI, GR, IE, IT, LU, NL, PL, PT, SE and SI against a checksum calculation. All other countries will fall back to a simple syntax check:
|
49
|
+
|
50
|
+
Valvat.new("DE345789003").valid_checksum?
|
51
|
+
=> true or false
|
52
|
+
|
53
|
+
These results are more valuabel than a simple syntax check, but keep in mind: they can not replace a lookup via VIES.
|
54
|
+
|
55
|
+
*IMPORTANT* This feature was tested against all vat numbers I could get my hand on, but it is still marked as *experimental* because these calculations are not documented and may return wrong results.
|
56
|
+
|
57
|
+
To bypass initializing a Valvat instance:
|
58
|
+
|
59
|
+
Valvat::Checksum.validate("DE345789003")
|
60
|
+
=> true or false
|
61
|
+
|
45
62
|
## Details & request identifier
|
46
63
|
|
47
64
|
If you need all details and not only if the VAT is valid, pass {:detail => true} as second parameter to the lookup call.
|
@@ -93,6 +110,12 @@ By default this will validate to true if the VIES web service is down. To fail i
|
|
93
110
|
|
94
111
|
validates :vat_number, :valvat => {:lookup => :fail_if_down}
|
95
112
|
|
113
|
+
### Additional (and experimental) checksum validation
|
114
|
+
|
115
|
+
To additionally perform a checksum validation:
|
116
|
+
|
117
|
+
validates :vat_number, :valvat => {:checksum => true}
|
118
|
+
|
96
119
|
### Additional ISO country code validation
|
97
120
|
|
98
121
|
If you want the vat number’s (ISO) country to match another country attribute, use the _match_country_ option:
|
@@ -158,6 +181,8 @@ This basically just removes trailing spaces and ensures all chars are uppercase.
|
|
158
181
|
|
159
182
|
## Contributions by
|
160
183
|
|
184
|
+
* [nevesenin](https://github.com/nevesenin)
|
185
|
+
* [shaundaley39](https://github.com/shaundaley39)
|
161
186
|
* [lcx](https://github.com/lcx)
|
162
187
|
* [kirichkov](https://github.com/kirichkov)
|
163
188
|
* [borodiychuk](https://github.com/borodiychuk)
|
data/lib/valvat.rb
CHANGED
@@ -10,6 +10,10 @@ class Valvat
|
|
10
10
|
Valvat::Syntax.validate(self)
|
11
11
|
end
|
12
12
|
|
13
|
+
def valid_checksum?
|
14
|
+
Valvat::Checksum.validate(self)
|
15
|
+
end
|
16
|
+
|
13
17
|
def exists?(options={})
|
14
18
|
Valvat::Lookup.validate(self, options)
|
15
19
|
end
|
@@ -42,6 +46,7 @@ end
|
|
42
46
|
|
43
47
|
require 'valvat/utils'
|
44
48
|
require 'valvat/syntax'
|
49
|
+
require 'valvat/checksum'
|
45
50
|
require 'valvat/lookup'
|
46
51
|
require 'valvat/lookup/request'
|
47
52
|
require 'valvat/lookup/request_with_id'
|
data/lib/valvat/active_model.rb
CHANGED
@@ -17,7 +17,10 @@ module ActiveModel
|
|
17
17
|
end
|
18
18
|
|
19
19
|
if is_valid
|
20
|
-
is_valid =
|
20
|
+
is_valid = vat.valid?
|
21
|
+
|
22
|
+
is_valid = vat.valid_checksum? if is_valid && options[:checksum]
|
23
|
+
is_valid = vat.exists? if is_valid && options[:lookup]
|
21
24
|
|
22
25
|
if is_valid.nil?
|
23
26
|
is_valid = options[:lookup] != :fail_if_down
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'valvat'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
ALGORITHMS = {}
|
6
|
+
|
7
|
+
def self.validate(vat)
|
8
|
+
vat = Valvat(vat)
|
9
|
+
algo = ALGORITHMS[vat.iso_country_code]
|
10
|
+
Valvat::Syntax.validate(vat) && !!(algo.nil? || algo.new(vat).validate)
|
11
|
+
end
|
12
|
+
|
13
|
+
class Base
|
14
|
+
def self.inherited(klass)
|
15
|
+
ALGORITHMS[klass.name.split(/::/).last] = klass
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :vat
|
19
|
+
|
20
|
+
def self.check_digit_length(len=nil)
|
21
|
+
@check_digit_length = len if len
|
22
|
+
@check_digit_length || 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(vat)
|
26
|
+
@vat = vat
|
27
|
+
end
|
28
|
+
|
29
|
+
def validate
|
30
|
+
check_digit == given_check_digit
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def given_check_digit_str
|
36
|
+
str_wo_country[-self.class.check_digit_length..-1]
|
37
|
+
end
|
38
|
+
|
39
|
+
def given_check_digit
|
40
|
+
given_check_digit_str.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
def figures_str
|
44
|
+
str_wo_country[0..-(self.class.check_digit_length+1)]
|
45
|
+
end
|
46
|
+
|
47
|
+
def figures
|
48
|
+
figures_str.split("").map(&:to_i)
|
49
|
+
end
|
50
|
+
|
51
|
+
def str_wo_country
|
52
|
+
vat.to_s_wo_country
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Dir[File.dirname(__FILE__) + "/checksum/*.rb"].each do |file|
|
59
|
+
require file
|
60
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'valvat/checksum'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
class AT < Base
|
6
|
+
def check_digit
|
7
|
+
chk = 96 - figures.reverse.each_with_index.map do |fig, i|
|
8
|
+
(fig*(i.modulo(2) == 0 ? 1 : 2)).to_s.split("").inject(0) { |sum, n| sum + n.to_i }
|
9
|
+
end.inject(:+)
|
10
|
+
chk.to_s[-1].to_i
|
11
|
+
end
|
12
|
+
|
13
|
+
def str_wo_country
|
14
|
+
super[1..-1]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Valvat
|
2
|
+
module Checksum
|
3
|
+
class BG < Base
|
4
|
+
def check_digit
|
5
|
+
natural_person? ? check_digit_natural_person : check_digit_legal_person
|
6
|
+
end
|
7
|
+
|
8
|
+
def check_digit_natural_person
|
9
|
+
local_person_chk = check_digit_local_natural_person
|
10
|
+
|
11
|
+
return local_person_chk if given_check_digit == local_person_chk
|
12
|
+
check_digit_foreign_natural_person
|
13
|
+
end
|
14
|
+
|
15
|
+
def check_digit_local_natural_person
|
16
|
+
weight = [2, 4, 8, 5, 10, 9, 7, 3, 6]
|
17
|
+
chk = figures.map do |fig|
|
18
|
+
fig * weight.shift
|
19
|
+
end.inject(:+).modulo(11)
|
20
|
+
|
21
|
+
return chk if chk < 10
|
22
|
+
return 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def check_digit_foreign_natural_person
|
26
|
+
weight = [21, 19, 17, 13, 11, 9, 7, 3, 1]
|
27
|
+
|
28
|
+
chk = figures.map do |fig|
|
29
|
+
fig * weight.shift
|
30
|
+
end.inject(:+).modulo(10)
|
31
|
+
|
32
|
+
chk
|
33
|
+
end
|
34
|
+
|
35
|
+
def check_digit_legal_person
|
36
|
+
prod = 0
|
37
|
+
figures.each_with_index do |fig, index|
|
38
|
+
prod += (index + 1) * fig.to_i
|
39
|
+
end
|
40
|
+
|
41
|
+
chk = prod % 11
|
42
|
+
|
43
|
+
if chk == 10
|
44
|
+
prod = 0
|
45
|
+
figures.each_with_index do |fig, index|
|
46
|
+
prod += (index + 3) * fig.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
chk = prod % 11
|
50
|
+
chk = 0 if chk == 10
|
51
|
+
end
|
52
|
+
|
53
|
+
chk
|
54
|
+
end
|
55
|
+
|
56
|
+
def natural_person?
|
57
|
+
vat.to_s_wo_country.length == 10
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'valvat/checksum'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
class DE < Base
|
6
|
+
M = 10
|
7
|
+
N = 11
|
8
|
+
|
9
|
+
def check_digit
|
10
|
+
prod = M
|
11
|
+
figures.each do |fig|
|
12
|
+
sum = (prod + fig).modulo(M)
|
13
|
+
sum = M if sum == 0
|
14
|
+
prod = (2*sum).modulo(N)
|
15
|
+
end
|
16
|
+
chk = N - prod
|
17
|
+
chk == 10 ? 0 : chk
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'valvat/checksum'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
class DK < Base
|
6
|
+
check_digit_length 0
|
7
|
+
|
8
|
+
def check_digit
|
9
|
+
weight = [2, 7, 6, 5, 4, 3, 2, 1]
|
10
|
+
figures.map do |fig|
|
11
|
+
fig * weight.shift
|
12
|
+
end.inject(:+).modulo(11)
|
13
|
+
end
|
14
|
+
|
15
|
+
def given_check_digit
|
16
|
+
0
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'valvat/checksum'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
class ES < Base
|
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)
|
7
|
+
LEGAL_FOREIGN_PERSON_CHARS = [false] + %w(A B C D E F G H I J)
|
8
|
+
|
9
|
+
def check_digit
|
10
|
+
natural_person? ? check_digit_natural_person : check_digit_legal_person
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_digit_natural_person
|
14
|
+
NATURAL_PERSON_CHARS[figures_str.to_i.modulo(23)]
|
15
|
+
end
|
16
|
+
|
17
|
+
def check_digit_legal_person
|
18
|
+
chk = 10 - figures.reverse.each_with_index.map do |fig, i|
|
19
|
+
(fig*(i.modulo(2) == 0 ? 2 : 1)).to_s.split("").inject(0) { |sum, n| sum + n.to_i }
|
20
|
+
end.inject(:+).modulo(10)
|
21
|
+
legal_foreign_person? ?
|
22
|
+
LEGAL_FOREIGN_PERSON_CHARS[chk] :
|
23
|
+
(chk == 10 ? 0 : chk)
|
24
|
+
end
|
25
|
+
|
26
|
+
def given_check_digit
|
27
|
+
natural_person? || legal_foreign_person? ? str_wo_country[-1] : super
|
28
|
+
end
|
29
|
+
|
30
|
+
def str_wo_country
|
31
|
+
str = super
|
32
|
+
str[0] =~ /\d/ ? str : str[1..-1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def natural_person?
|
36
|
+
!!(vat.to_s_wo_country =~ /\A[\d]{8}[ABCDEFGHJKLMNPQRSTVWXYZ]\Z/) ||
|
37
|
+
!!(vat.to_s_wo_country =~ /\A[KLMX][\d]{7}[ABCDEFGHJKLMNPQRSTVWXYZ]\Z/)
|
38
|
+
end
|
39
|
+
|
40
|
+
def legal_foreign_person?
|
41
|
+
!!(vat.to_s_wo_country =~ /\A[NPQRSW][\d]{7}[ABCDEFGHIJ]\Z/)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'valvat/checksum'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
class FI < Base
|
6
|
+
def check_digit
|
7
|
+
weight = [7, 9, 10, 5, 8, 4, 2]
|
8
|
+
chk = 11 - figures.map do |fig|
|
9
|
+
fig * weight.shift
|
10
|
+
end.inject(:+).modulo(11)
|
11
|
+
chk == 11 ? 0 : chk
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'valvat/checksum'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
class IE < Base
|
6
|
+
def check_digit
|
7
|
+
figures.reverse.each_with_index.map do |fig, i|
|
8
|
+
fig*(i+2)
|
9
|
+
end.inject(:+).modulo(23)
|
10
|
+
end
|
11
|
+
|
12
|
+
CHARS = "WABCDEFGHIJKLMNOPQRSTUV".split("")
|
13
|
+
|
14
|
+
def given_check_digit
|
15
|
+
CHARS.index(given_check_digit_str)
|
16
|
+
end
|
17
|
+
|
18
|
+
def str_wo_country
|
19
|
+
str = super
|
20
|
+
# Convert old irish vat format to new one
|
21
|
+
if str =~ /\A[0-9][A-Z][0-9]{5}[A-Z]\Z/
|
22
|
+
"0#{str[2..6]}#{str[0]}#{str[7]}"
|
23
|
+
else
|
24
|
+
str
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'valvat/checksum'
|
2
|
+
|
3
|
+
class Valvat
|
4
|
+
module Checksum
|
5
|
+
class IT < Base
|
6
|
+
def validate
|
7
|
+
# IT02762750210
|
8
|
+
y = figures_str[7..9].to_i
|
9
|
+
y >= 1 && (y <= 100 || [120, 121].include?(y)) &&
|
10
|
+
figures_str[0..6] != "0000000" &&
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_digit
|
15
|
+
chk = 10 - figures.reverse.each_with_index.map do |fig, i|
|
16
|
+
(fig*(i.modulo(2) == 0 ? 2 : 1)).to_s.split("").inject(0) { |sum, n| sum + n.to_i }
|
17
|
+
end.inject(:+).modulo(10)
|
18
|
+
chk == 10 ? 0 : chk
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|