valvat 0.8.1 → 1.0.1

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