valvat 0.4.7 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|