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.
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