valvat 0.4.7 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.rbenv-version +1 -1
  3. data/CHANGES.md +8 -1
  4. data/Guardfile +1 -1
  5. data/README.md +27 -2
  6. data/lib/valvat.rb +5 -0
  7. data/lib/valvat/active_model.rb +4 -1
  8. data/lib/valvat/checksum.rb +60 -0
  9. data/lib/valvat/checksum/at.rb +18 -0
  10. data/lib/valvat/checksum/be.rb +13 -0
  11. data/lib/valvat/checksum/bg.rb +61 -0
  12. data/lib/valvat/checksum/de.rb +21 -0
  13. data/lib/valvat/checksum/dk.rb +20 -0
  14. data/lib/valvat/checksum/es.rb +45 -0
  15. data/lib/valvat/checksum/fi.rb +15 -0
  16. data/lib/valvat/checksum/gr.rb +14 -0
  17. data/lib/valvat/checksum/ie.rb +29 -0
  18. data/lib/valvat/checksum/it.rb +22 -0
  19. data/lib/valvat/checksum/lu.rb +13 -0
  20. data/lib/valvat/checksum/nl.rb +17 -0
  21. data/lib/valvat/checksum/pl.rb +14 -0
  22. data/lib/valvat/checksum/pt.rb +14 -0
  23. data/lib/valvat/checksum/se.rb +30 -0
  24. data/lib/valvat/checksum/si.rb +19 -0
  25. data/lib/valvat/lookup.rb +8 -5
  26. data/lib/valvat/lookup/request.rb +1 -3
  27. data/lib/valvat/syntax.rb +0 -1
  28. data/lib/valvat/version.rb +1 -1
  29. data/spec/spec_helper.rb +1 -0
  30. data/spec/valvat/active_model_spec.rb +18 -0
  31. data/spec/valvat/checksum/at_spec.rb +15 -0
  32. data/spec/valvat/checksum/be_spec.rb +15 -0
  33. data/spec/valvat/checksum/bg_spec.rb +15 -0
  34. data/spec/valvat/checksum/de_spec.rb +15 -0
  35. data/spec/valvat/checksum/dk_spec.rb +15 -0
  36. data/spec/valvat/checksum/es_spec.rb +15 -0
  37. data/spec/valvat/checksum/fi_spec.rb +15 -0
  38. data/spec/valvat/checksum/gr_spec.rb +15 -0
  39. data/spec/valvat/checksum/ie_spec.rb +15 -0
  40. data/spec/valvat/checksum/it_spec.rb +23 -0
  41. data/spec/valvat/checksum/lu_spec.rb +15 -0
  42. data/spec/valvat/checksum/nl_spec.rb +15 -0
  43. data/spec/valvat/checksum/pl_spec.rb +19 -0
  44. data/spec/valvat/checksum/pt_spec.rb +15 -0
  45. data/spec/valvat/checksum/se_spec.rb +19 -0
  46. data/spec/valvat/checksum/si_spec.rb +23 -0
  47. data/spec/valvat/checksum_spec.rb +25 -0
  48. data/spec/valvat/lookup_spec.rb +1 -1
  49. data/spec/valvat_spec.rb +13 -0
  50. data/valvat.gemspec +2 -1
  51. 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.9.3-p286
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.4.7...master)
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
@@ -1,4 +1,4 @@
1
- guard 'rspec', :version => 2 do
1
+ guard 'rspec' do
2
2
  watch(/^spec\/(.*)_spec.rb/)
3
3
  watch(/^lib\/(.*)\.rb/) { |m| "spec/#{m[1]}_spec.rb" }
4
4
  watch(/^spec\/spec_helper.rb/) { "spec" }
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 english, german, swedish and bulgarian
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.9
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'
@@ -17,7 +17,10 @@ module ActiveModel
17
17
  end
18
18
 
19
19
  if is_valid
20
- is_valid = options[:lookup] ? vat.valid? && vat.exists? : vat.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,13 @@
1
+ require 'valvat/checksum'
2
+
3
+ class Valvat
4
+ module Checksum
5
+ class BE < Base
6
+ check_digit_length 2
7
+
8
+ def check_digit
9
+ 97 - figures_str.to_i.modulo(97)
10
+ end
11
+ end
12
+ end
13
+ 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,14 @@
1
+ require 'valvat/checksum'
2
+
3
+ class Valvat
4
+ module Checksum
5
+ class GR < Base
6
+ def check_digit
7
+ chk = figures.reverse.each_with_index.map do |fig, i|
8
+ fig*(2**(i+1))
9
+ end.inject(:+).modulo(11)
10
+ chk > 9 ? 0 : chk
11
+ end
12
+ end
13
+ end
14
+ 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