valvat 0.8.1 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +2 -1
  5. data/.travis.yml +30 -16
  6. data/CHANGES.md +45 -2
  7. data/Gemfile +2 -3
  8. data/README.md +96 -46
  9. data/certs/yolk.pem +24 -20
  10. data/gemfiles/activemodel-3-2 +3 -4
  11. data/gemfiles/activemodel-4 +3 -4
  12. data/gemfiles/activemodel-5 +4 -5
  13. data/gemfiles/activemodel-6 +7 -0
  14. data/gemfiles/before-ruby21/activemodel-3-2 +3 -4
  15. data/gemfiles/before-ruby21/activemodel-4 +3 -4
  16. data/gemfiles/before-ruby21/standalone +3 -4
  17. data/gemfiles/standalone +3 -5
  18. data/lib/active_model/validations/valvat_validator.rb +20 -5
  19. data/lib/valvat.rb +3 -1
  20. data/lib/valvat/checksum/gb.rb +30 -13
  21. data/lib/valvat/checksum/ie.rb +1 -1
  22. data/lib/valvat/checksum/nl.rb +5 -0
  23. data/lib/valvat/checksum/si.rb +2 -2
  24. data/lib/valvat/error.rb +28 -0
  25. data/lib/valvat/local.rb +2 -1
  26. data/lib/valvat/locales/en.yml +1 -1
  27. data/lib/valvat/locales/fr.yml +17 -17
  28. data/lib/valvat/locales/pt.yml +28 -28
  29. data/lib/valvat/lookup.rb +11 -43
  30. data/lib/valvat/lookup/fault.rb +30 -0
  31. data/lib/valvat/lookup/request.rb +33 -12
  32. data/lib/valvat/lookup/response.rb +36 -0
  33. data/lib/valvat/utils.rb +11 -3
  34. data/lib/valvat/version.rb +1 -1
  35. data/spec/active_model/validations/valvat_validator_spec.rb +92 -66
  36. data/spec/valvat/checksum/at_spec.rb +2 -2
  37. data/spec/valvat/checksum/be_spec.rb +2 -2
  38. data/spec/valvat/checksum/bg_spec.rb +2 -2
  39. data/spec/valvat/checksum/cy_spec.rb +2 -2
  40. data/spec/valvat/checksum/de_spec.rb +2 -2
  41. data/spec/valvat/checksum/dk_spec.rb +2 -2
  42. data/spec/valvat/checksum/ee_spec.rb +2 -2
  43. data/spec/valvat/checksum/es_spec.rb +2 -2
  44. data/spec/valvat/checksum/fi_spec.rb +2 -2
  45. data/spec/valvat/checksum/fr_spec.rb +2 -2
  46. data/spec/valvat/checksum/gb_spec.rb +5 -3
  47. data/spec/valvat/checksum/gr_spec.rb +2 -2
  48. data/spec/valvat/checksum/hr_spec.rb +2 -2
  49. data/spec/valvat/checksum/hu_spec.rb +2 -2
  50. data/spec/valvat/checksum/ie_spec.rb +8 -2
  51. data/spec/valvat/checksum/it_spec.rb +4 -4
  52. data/spec/valvat/checksum/lt_spec.rb +2 -2
  53. data/spec/valvat/checksum/lu_spec.rb +2 -2
  54. data/spec/valvat/checksum/mt_spec.rb +2 -2
  55. data/spec/valvat/checksum/nl_spec.rb +4 -4
  56. data/spec/valvat/checksum/pl_spec.rb +3 -3
  57. data/spec/valvat/checksum/pt_spec.rb +2 -2
  58. data/spec/valvat/checksum/ro_spec.rb +2 -2
  59. data/spec/valvat/checksum/se_spec.rb +3 -3
  60. data/spec/valvat/checksum/si_spec.rb +7 -7
  61. data/spec/valvat/checksum_spec.rb +2 -2
  62. data/spec/valvat/lockup/fault_spec.rb +32 -0
  63. data/spec/valvat/lockup/request_spec.rb +15 -0
  64. data/spec/valvat/lockup/response_spec.rb +27 -0
  65. data/spec/valvat/lookup_spec.rb +168 -35
  66. data/spec/valvat/syntax_spec.rb +29 -29
  67. data/spec/valvat/utils_spec.rb +28 -8
  68. data/spec/valvat_spec.rb +18 -18
  69. data/valvat.gemspec +1 -0
  70. metadata +52 -25
  71. metadata.gz.sig +0 -0
  72. data/lib/valvat/lookup/request_with_id.rb +0 -31
@@ -1,8 +1,7 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
- gem "savon", ">=2.3.0"
4
3
  gem "activemodel", "~> 3.2"
5
4
 
6
- gem 'rspec', '~> 3.0'
7
5
  gem 'rack', '< 2'
8
- gem 'rake'
6
+
7
+ gemspec path: "../"
@@ -1,8 +1,7 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
- gem "savon", ">=2.3.0"
4
3
  gem "activemodel", "~> 4.2"
5
4
 
6
- gem 'rspec', '~> 3.0'
7
5
  gem 'rack', '< 2'
8
- gem 'rake'
6
+
7
+ gemspec path: "../"
@@ -1,8 +1,7 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
- gem "savon", ">=2.3.0"
4
- gem "activemodel", "~> 5.0.2"
3
+ gem "activemodel", "~> 5.2.0"
5
4
 
6
- gem 'rspec', '~> 3.0'
7
5
  gem 'rack'
8
- gem 'rake'
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem "activemodel", "~> 6.0.0"
4
+
5
+ gem 'rack'
6
+
7
+ gemspec path: "../"
@@ -1,9 +1,8 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gem "nokogiri", "< 1.7"
4
- gem "savon", ">=2.3.0"
5
4
  gem "activemodel", "~> 3.2"
6
5
 
7
- gem 'rspec', '~> 3.0'
8
6
  gem 'rack', '< 2'
9
- gem 'rake'
7
+
8
+ gemspec path: "../../"
@@ -1,9 +1,8 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gem "nokogiri", "< 1.7"
4
- gem "savon", ">=2.3.0"
5
4
  gem "activemodel", "~> 4.2"
6
5
 
7
- gem 'rspec', '~> 3.0'
8
6
  gem 'rack', '< 2'
9
- gem 'rake'
7
+
8
+ gemspec path: "../../"
@@ -1,8 +1,7 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gem "nokogiri", "< 1.7"
4
- gem "savon", ">=2.3.0"
5
4
 
6
- gem 'rspec', '~> 3.0'
7
5
  gem 'rack', '< 2'
8
- gem 'rake'
6
+
7
+ gemspec path: "../../"
@@ -1,7 +1,5 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
- gem "savon", ">=2.3.0"
4
-
5
- gem 'rspec', '~> 3.0'
6
3
  gem 'rack', '< 2'
7
- gem 'rake'
4
+
5
+ gemspec path: "../"
@@ -5,6 +5,20 @@ require 'valvat/lookup'
5
5
  module ActiveModel
6
6
  module Validations
7
7
  class ValvatValidator < EachValidator
8
+ def initialize(options)
9
+ if options[:lookup]
10
+ options[:lookup] = if options[:lookup] == :fail_if_down
11
+ {fail_if_down: true}
12
+ elsif options[:lookup].is_a?(Hash)
13
+ options[:lookup]
14
+ else
15
+ {}
16
+ end
17
+ end
18
+
19
+ super
20
+ end
21
+
8
22
  def validate_each(record, attribute, value)
9
23
  vat = Valvat(value)
10
24
  iso_country_code = vat.iso_country_code
@@ -17,10 +31,10 @@ module ActiveModel
17
31
 
18
32
  iso_country_code = "eu" if iso_country_code.blank?
19
33
  record.errors.add(attribute, :invalid_vat,
20
- :message => options[:message],
21
- :country_adjective => I18n.t(
34
+ message: options[:message],
35
+ country_adjective: I18n.t(
22
36
  :"valvat.country_adjectives.#{iso_country_code.downcase}",
23
- :default => [:"valvat.country_adjectives.eu", "european"]
37
+ default: [:"valvat.country_adjectives.eu", "european"]
24
38
  )
25
39
  )
26
40
  end
@@ -43,9 +57,10 @@ module ActiveModel
43
57
 
44
58
  def vat_exists?(vat)
45
59
  return true unless options[:lookup]
46
- is_valid = vat.exists?
60
+
61
+ is_valid = vat.exists?(options[:lookup])
47
62
  return is_valid unless is_valid.nil?
48
- options[:lookup] != :fail_if_down
63
+ !options[:lookup][:fail_if_down]
49
64
  end
50
65
  end
51
66
  end
@@ -1,7 +1,9 @@
1
+ require 'valvat/error'
1
2
  require 'valvat/local'
2
3
  require 'valvat/lookup'
3
4
  require 'valvat/lookup/request'
4
- require 'valvat/lookup/request_with_id'
5
+ require 'valvat/lookup/response'
6
+ require 'valvat/lookup/fault'
5
7
  require 'active_model/validations/valvat_validator' if defined?(ActiveModel)
6
8
 
7
9
  class Valvat
@@ -3,33 +3,50 @@ class Valvat
3
3
  class GB < Base
4
4
  OLD_FORMAT_FORBIDDEN_RANGES = [(100_000..999_999), (9_490_001..9_700_000), (9_990_001..9_999_999)]
5
5
  NEW_FORMAT_FORBIDDEN_RANGES = [(1..100_000), (100_001..1_000_000)]
6
+ GOV_NUMBER = /\A(GD[0-4]{1}\d{2})\Z/
7
+ HEALTH_NUMBER = /\A(HA[5-9]{1}\d{2})\Z/
6
8
 
7
9
  def validate
8
10
  vat_number = vat.to_s_wo_country
9
- check_sum = vat.to_s_wo_country[7..8].to_i
10
- vat_base = vat_number[0..6]
11
11
 
12
12
  # government departments and health authorities, so no checksum
13
- return true if vat_number =~ /\A(GD[0-4]{1}\d{2})\Z/ || vat_number =~ /\A(HA[5-9]{1}\d{2})\Z/
14
- return false if vat_number =~ /\A0{9}\Z/ || vat_number =~ /\A0{12}\Z/
13
+ return true if gov_or_health?(vat_number)
14
+ return false if all_zero?(vat_number)
15
15
 
16
- vat_base_sum = vat_base.split('').
17
- map(&:to_i).
18
- zip([8, 7, 6, 5, 4, 3, 2]).
19
- map { |vat_number_digit, multiplier| vat_number_digit * multiplier }.
20
- inject(:+)
16
+ checksum = vat_number[7..8].to_i
17
+ vat_base = vat_number[0..6]
18
+ vat_base_int = vat_base.to_i
19
+ vat_base_sum = generate_vat_base_sum(vat_base)
21
20
 
22
- old_format_remainder = (vat_base_sum + check_sum).modulo(97)
23
- new_format_remainder = (vat_base_sum + 55 + check_sum).modulo(97)
21
+ old_format_remainder = (vat_base_sum + checksum).modulo(97)
22
+ new_format_remainder = (vat_base_sum + 55 + checksum).modulo(97)
24
23
 
25
24
  return false if old_format_remainder == 0 &&
26
- OLD_FORMAT_FORBIDDEN_RANGES.any? { |range| range.include? vat_base.to_i }
25
+ OLD_FORMAT_FORBIDDEN_RANGES.any? { |range| range.include? vat_base_int }
27
26
 
28
27
  return false if new_format_remainder == 0 &&
29
- NEW_FORMAT_FORBIDDEN_RANGES.any? { |range| range.include? vat_base.to_i }
28
+ NEW_FORMAT_FORBIDDEN_RANGES.any? { |range| range.include? vat_base_int }
30
29
 
31
30
  old_format_remainder == 0 || new_format_remainder == 0
32
31
  end
32
+
33
+ private
34
+
35
+ def gov_or_health?(vat_number)
36
+ vat_number =~ GOV_NUMBER || vat_number =~ HEALTH_NUMBER
37
+ end
38
+
39
+ def all_zero?(vat_number)
40
+ vat_number =~ /\A0{9}\Z/ || vat_number =~ /\A0{12}\Z/
41
+ end
42
+
43
+ def generate_vat_base_sum(vat_base)
44
+ vat_base.split('').
45
+ map(&:to_i).
46
+ zip([8, 7, 6, 5, 4, 3, 2]).
47
+ map { |vat_number_digit, multiplier| vat_number_digit * multiplier }.
48
+ inject(:+)
49
+ end
33
50
  end
34
51
  end
35
52
  end
@@ -6,7 +6,7 @@ class Valvat
6
6
  fig*(i+2)
7
7
  end
8
8
  if str_wo_country.size == 9
9
- total += (CHARS.index(str_wo_country[8]) * 9)
9
+ total += ((CHARS.index(str_wo_country[8]) || 0) * 9)
10
10
  end
11
11
  total.modulo(23)
12
12
  end
@@ -1,6 +1,11 @@
1
1
  class Valvat
2
2
  module Checksum
3
3
  class NL < Base
4
+ def validate
5
+ vat.to_s.gsub(/[A-Z]/) { |let| (let.ord - 55).to_s }.to_i % 97 == 1 ||
6
+ super
7
+ end
8
+
4
9
  def check_digit
5
10
  sum_figures_by do |fig, i|
6
11
  fig*(i+2)
@@ -8,8 +8,8 @@ class Valvat
8
8
 
9
9
  def check_digit
10
10
  chk = sum_of_figues_for_pt_si
11
- chk == 1 ? 0 : chk
11
+ chk == 10 ? 0 : chk
12
12
  end
13
13
  end
14
14
  end
15
- end
15
+ end
@@ -0,0 +1,28 @@
1
+ class Valvat
2
+ Error = Class.new(RuntimeError)
3
+
4
+ class ViesError < Error
5
+ def initialize(faultstring='UNKNOWN')
6
+ @faultstring = faultstring
7
+ end
8
+
9
+ def to_s
10
+ "The VIES web service returned the error '#{@faultstring}'."
11
+ end
12
+
13
+ def eql?(other)
14
+ to_s.eql?(other.to_s)
15
+ end
16
+ end
17
+ ViesMaintenanceError = Class.new(ViesError)
18
+
19
+ ServiceUnavailable = Class.new(ViesMaintenanceError)
20
+ MemberStateUnavailable = Class.new(ViesMaintenanceError)
21
+
22
+ Timeout = Class.new(ViesError)
23
+ InvalidRequester = Class.new(ViesError)
24
+ BlockedError = Class.new(ViesError)
25
+ RateLimitError = Class.new(ViesError)
26
+
27
+ UnknownViesError = Class.new(ViesError)
28
+ end
@@ -22,8 +22,9 @@ class Valvat
22
22
  Valvat::Utils.vat_country_to_iso_country(vat_country_code)
23
23
  end
24
24
 
25
+ # TODO: Remove method / not in use
25
26
  def european?
26
- Valvat::Utils::EU_COUNTRIES.include?(iso_country_code)
27
+ Valvat::Utils::EU_MEMBER_STATES.include?(iso_country_code)
27
28
  end
28
29
 
29
30
  def to_a
@@ -1,7 +1,7 @@
1
1
  en:
2
2
  errors:
3
3
  messages:
4
- invalid_vat: is not a valid %{country_adjective} vat number
4
+ invalid_vat: is not a valid %{country_adjective} VAT number
5
5
  valvat:
6
6
  country_adjectives:
7
7
  eu: European
@@ -5,29 +5,29 @@ fr:
5
5
  valvat:
6
6
  country_adjectives:
7
7
  eu: intracommunautaire
8
- at: autrichienne
8
+ at: autrichien
9
9
  be: belge
10
10
  bg: bulgare
11
11
  cy: chypriote
12
12
  cz: tchèque
13
- de: allemande
14
- dk: danoise
13
+ de: allemand
14
+ dk: danois
15
15
  ee: estonien
16
- es: espagnole
17
- fi: finlandaise
18
- fr: française
16
+ es: espagnol
17
+ fi: finlandais
18
+ fr: français
19
19
  gb: britannique
20
- gr: grèque
21
- ie: irlandaise
22
- it: italienne
23
- lt: lituanienne
24
- lu: luxembourgeoise
25
- lv: lettone
20
+ gr: grec
21
+ ie: irlandais
22
+ it: italien
23
+ lt: lituanien
24
+ lu: luxembourgeois
25
+ lv: letton
26
26
  mt: maltais
27
- nl: hollandaise
28
- pl: polonaise
29
- pt: portugaise
30
- ro: roumaine
31
- se: suédoise
27
+ nl: hollandais
28
+ pl: polonais
29
+ pt: portugais
30
+ ro: roumain
31
+ se: suédois
32
32
  si: slovène
33
33
  sk: slovaque
@@ -4,31 +4,31 @@ pt:
4
4
  invalid_vat: O NIF %{country_adjective} não é válido.
5
5
  valvat:
6
6
  country_adjectives:
7
- eu: europeu
8
- at: austríaco
9
- be: belga
10
- bg: búlgaro
11
- cy: cipriota
12
- cz: checo
13
- de: alemão
14
- dk: dinamarquês
15
- ee: estónio
16
- es: espanhol
17
- fi: finlandês
18
- fr: francês
19
- gb: britânico
20
- gr: grego
21
- hu: húngaro
22
- ie: irlandês
23
- it: italiano
24
- lt: lituano
25
- lu: luxemburguês
26
- lv: letão
27
- mt: maltês
28
- nl: holandês
29
- pl: polaco
30
- pt: português
31
- ro: romeno
32
- se: sueco
33
- si: esloveno
34
- sk: eslovaco
7
+ eu: europeu
8
+ at: austríaco
9
+ be: belga
10
+ bg: búlgaro
11
+ cy: cipriota
12
+ cz: checo
13
+ de: alemão
14
+ dk: dinamarquês
15
+ ee: estónio
16
+ es: espanhol
17
+ fi: finlandês
18
+ fr: francês
19
+ gb: britânico
20
+ gr: grego
21
+ hu: húngaro
22
+ ie: irlandês
23
+ it: italiano
24
+ lt: lituano
25
+ lu: luxemburguês
26
+ lv: letão
27
+ mt: maltês
28
+ nl: holandês
29
+ pl: polaco
30
+ pt: português
31
+ ro: romeno
32
+ se: sueco
33
+ si: esloveno
34
+ sk: eslovaco
@@ -1,36 +1,23 @@
1
- require 'savon'
2
-
3
1
  class Valvat
4
2
  class Lookup
5
- VIES_WSDL_URL = 'http://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl'
6
- REMOVE_KEYS = [:valid, :@xmlns]
7
-
8
- attr_reader :vat, :options
9
3
 
10
4
  def initialize(vat, options={})
11
5
  @vat = Valvat(vat)
12
6
  @options = options || {}
13
- @options[:requester_vat] = Valvat(requester_vat) if requester_vat
7
+ @options[:requester] ||= @options[:requester_vat]
14
8
  end
15
9
 
16
10
  def validate
17
- return false unless vat.european?
11
+ return false if !@options[:skip_local_validation] && !@vat.valid?
12
+ return handle_vies_error(response[:error]) if response[:error]
18
13
 
19
- valid? && show_details? ? response_details : valid?
20
- rescue => error
21
- return false if invalid_input?(error)
22
- raise error if options[:raise_error]
23
- nil
14
+ response[:valid] && show_details? ? response.to_hash : response[:valid]
24
15
  end
25
16
 
26
17
  class << self
27
18
  def validate(vat, options={})
28
19
  new(vat, options).validate
29
20
  end
30
-
31
- def client
32
- @client ||= Savon::Client.new(wsdl: VIES_WSDL_URL, log: false)
33
- end
34
21
  end
35
22
 
36
23
  private
@@ -40,37 +27,18 @@ class Valvat
40
27
  end
41
28
 
42
29
  def response
43
- @response ||= request.perform(self.class.client)
44
- end
45
-
46
- def request
47
- if requester_vat
48
- RequestWithId.new(vat, requester_vat)
49
- else
50
- Request.new(vat)
51
- end
52
- end
53
-
54
- def requester_vat
55
- options[:requester_vat]
56
- end
57
-
58
- def invalid_input?(err)
59
- return if !err.respond_to?(:to_hash) || !err.to_hash[:fault]
60
- (err.to_hash[:fault][:faultstring] || "").upcase =~ /INVALID_INPUT/
30
+ @response ||= Request.new(@vat, @options).perform
61
31
  end
62
32
 
63
33
  def show_details?
64
- requester_vat || options[:detail]
34
+ @options[:requester] || @options[:detail]
65
35
  end
66
36
 
67
- def response_details
68
- response.inject({}) do |hash, kv|
69
- key, value = kv
70
- unless REMOVE_KEYS.include?(key)
71
- hash[key.to_s.sub(/^trader_/, "").to_sym] = (value == "---" ? nil : value)
72
- end
73
- hash
37
+ def handle_vies_error(error)
38
+ if ViesMaintenanceError === error
39
+ raise error if @options[:raise_error]
40
+ else
41
+ raise error unless @options[:raise_error] == false
74
42
  end
75
43
  end
76
44
  end