valvat 0.9.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.github/workflows/ruby.yml +35 -0
  5. data/.gitignore +3 -1
  6. data/.rubocop.yml +15 -0
  7. data/CHANGES.md +30 -1
  8. data/Gemfile +4 -3
  9. data/Guardfile +6 -4
  10. data/README.md +99 -47
  11. data/Rakefile +3 -1
  12. data/certs/yolk.pem +21 -21
  13. data/gemfiles/activemodel-5 +4 -5
  14. data/gemfiles/activemodel-6 +7 -0
  15. data/gemfiles/standalone +3 -5
  16. data/lib/active_model/validations/valvat_validator.rb +70 -22
  17. data/lib/valvat.rb +7 -3
  18. data/lib/valvat/checksum.rb +14 -13
  19. data/lib/valvat/checksum/at.rb +3 -1
  20. data/lib/valvat/checksum/be.rb +3 -1
  21. data/lib/valvat/checksum/bg.rb +17 -17
  22. data/lib/valvat/checksum/cy.rb +2 -0
  23. data/lib/valvat/checksum/de.rb +5 -3
  24. data/lib/valvat/checksum/dk.rb +3 -1
  25. data/lib/valvat/checksum/ee.rb +2 -0
  26. data/lib/valvat/checksum/es.rb +14 -16
  27. data/lib/valvat/checksum/fi.rb +3 -1
  28. data/lib/valvat/checksum/fr.rb +4 -2
  29. data/lib/valvat/checksum/gb.rb +53 -21
  30. data/lib/valvat/checksum/gr.rb +4 -2
  31. data/lib/valvat/checksum/hr.rb +2 -0
  32. data/lib/valvat/checksum/hu.rb +2 -0
  33. data/lib/valvat/checksum/ie.rb +5 -6
  34. data/lib/valvat/checksum/it.rb +6 -4
  35. data/lib/valvat/checksum/lt.rb +2 -0
  36. data/lib/valvat/checksum/lu.rb +3 -1
  37. data/lib/valvat/checksum/mt.rb +2 -0
  38. data/lib/valvat/checksum/nl.rb +5 -3
  39. data/lib/valvat/checksum/pl.rb +3 -1
  40. data/lib/valvat/checksum/pt.rb +3 -1
  41. data/lib/valvat/checksum/ro.rb +2 -0
  42. data/lib/valvat/checksum/se.rb +5 -3
  43. data/lib/valvat/checksum/si.rb +4 -2
  44. data/lib/valvat/error.rb +31 -0
  45. data/lib/valvat/local.rb +8 -5
  46. data/lib/valvat/locales/de.yml +2 -1
  47. data/lib/valvat/locales/en.yml +2 -1
  48. data/lib/valvat/locales/es.yml +1 -0
  49. data/lib/valvat/locales/fr.yml +1 -0
  50. data/lib/valvat/locales/hu.yml +1 -0
  51. data/lib/valvat/locales/nl.yml +1 -0
  52. data/lib/valvat/locales/pl.yml +1 -0
  53. data/lib/valvat/locales/pt.yml +28 -28
  54. data/lib/valvat/lookup.rb +14 -45
  55. data/lib/valvat/lookup/fault.rb +31 -0
  56. data/lib/valvat/lookup/request.rb +37 -11
  57. data/lib/valvat/lookup/response.rb +37 -0
  58. data/lib/valvat/syntax.rb +31 -30
  59. data/lib/valvat/utils.rb +20 -9
  60. data/lib/valvat/version.rb +3 -1
  61. data/spec/active_model/validations/valvat_validator_spec.rb +132 -100
  62. data/spec/spec_helper.rb +8 -8
  63. data/spec/valvat/checksum/at_spec.rb +8 -6
  64. data/spec/valvat/checksum/be_spec.rb +8 -6
  65. data/spec/valvat/checksum/bg_spec.rb +9 -6
  66. data/spec/valvat/checksum/cy_spec.rb +5 -3
  67. data/spec/valvat/checksum/de_spec.rb +8 -6
  68. data/spec/valvat/checksum/dk_spec.rb +8 -6
  69. data/spec/valvat/checksum/ee_spec.rb +5 -3
  70. data/spec/valvat/checksum/es_spec.rb +9 -6
  71. data/spec/valvat/checksum/fi_spec.rb +8 -6
  72. data/spec/valvat/checksum/fr_spec.rb +8 -6
  73. data/spec/valvat/checksum/gb_spec.rb +9 -5
  74. data/spec/valvat/checksum/gr_spec.rb +8 -6
  75. data/spec/valvat/checksum/hr_spec.rb +5 -3
  76. data/spec/valvat/checksum/hu_spec.rb +5 -3
  77. data/spec/valvat/checksum/ie_spec.rb +13 -5
  78. data/spec/valvat/checksum/it_spec.rb +12 -10
  79. data/spec/valvat/checksum/lt_spec.rb +5 -3
  80. data/spec/valvat/checksum/lu_spec.rb +8 -6
  81. data/spec/valvat/checksum/mt_spec.rb +5 -3
  82. data/spec/valvat/checksum/nl_spec.rb +9 -7
  83. data/spec/valvat/checksum/pl_spec.rb +10 -8
  84. data/spec/valvat/checksum/pt_spec.rb +12 -13
  85. data/spec/valvat/checksum/ro_spec.rb +5 -3
  86. data/spec/valvat/checksum/se_spec.rb +10 -8
  87. data/spec/valvat/checksum/si_spec.rb +12 -10
  88. data/spec/valvat/checksum_spec.rb +12 -10
  89. data/spec/valvat/lookup/fault_spec.rb +34 -0
  90. data/spec/valvat/lookup/request_spec.rb +19 -0
  91. data/spec/valvat/lookup/response_spec.rb +29 -0
  92. data/spec/valvat/lookup_spec.rb +202 -84
  93. data/spec/valvat/syntax_spec.rb +59 -280
  94. data/spec/valvat/utils_spec.rb +76 -43
  95. data/spec/valvat_spec.rb +140 -152
  96. data/valvat.gemspec +18 -17
  97. metadata +61 -41
  98. metadata.gz.sig +3 -3
  99. data/.travis.yml +0 -33
  100. data/gemfiles/activemodel-3-2 +0 -8
  101. data/gemfiles/activemodel-4 +0 -8
  102. data/gemfiles/before-ruby21/activemodel-3-2 +0 -9
  103. data/gemfiles/before-ruby21/activemodel-4 +0 -9
  104. data/gemfiles/before-ruby21/standalone +0 -8
  105. data/lib/valvat/lookup/request_with_id.rb +0 -31
@@ -2,6 +2,7 @@ hu:
2
2
  errors:
3
3
  messages:
4
4
  invalid_vat: a közösségi adószám %{country_adjective} helytelen
5
+ vies_down: "Az adószám ellenőrzése sikertelen: a VIES szolgáltatás nem elérhető. Kérjük próbálja újra később."
5
6
  valvat:
6
7
  country_adjectives:
7
8
  eu: európai
@@ -2,6 +2,7 @@ nl:
2
2
  errors:
3
3
  messages:
4
4
  invalid_vat: is geen geldig %{country_adjective} btw nummer
5
+ vies_down: "We kunnen uw btw-nummer niet bevestigen: de VIES-service is momenteel niet beschikbaar. Probeer het later opnieuw."
5
6
  valvat:
6
7
  country_adjectives:
7
8
  eu: Europees
@@ -2,6 +2,7 @@ pl:
2
2
  errors:
3
3
  messages:
4
4
  invalid_vat: nie jest prawidłowym %{country_adjective} numerem VAT
5
+ vies_down: "Nie można zweryfikować numeru VAT: serwis VIES nie działa. Spróbuj ponownie później."
5
6
  valvat:
6
7
  country_adjectives:
7
8
  eu: europejskim
@@ -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
data/lib/valvat/lookup.rb CHANGED
@@ -1,36 +1,24 @@
1
- require 'savon'
1
+ # frozen_string_literal: true
2
2
 
3
3
  class Valvat
4
4
  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
-
10
- def initialize(vat, options={})
5
+ def initialize(vat, options = {})
11
6
  @vat = Valvat(vat)
12
7
  @options = options || {}
13
- @options[:requester_vat] = Valvat(requester_vat) if requester_vat
8
+ @options[:requester] ||= @options[:requester_vat]
14
9
  end
15
10
 
16
11
  def validate
17
- return false unless vat.european?
12
+ return false if !@options[:skip_local_validation] && !@vat.valid?
13
+ return handle_vies_error(response[:error]) if response[:error]
18
14
 
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
15
+ response[:valid] && show_details? ? response.to_hash : response[:valid]
24
16
  end
25
17
 
26
18
  class << self
27
- def validate(vat, options={})
19
+ def validate(vat, options = {})
28
20
  new(vat, options).validate
29
21
  end
30
-
31
- def client
32
- @client ||= Savon::Client.new(wsdl: VIES_WSDL_URL, log: false, follow_redirects: true)
33
- end
34
22
  end
35
23
 
36
24
  private
@@ -40,37 +28,18 @@ class Valvat
40
28
  end
41
29
 
42
30
  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/
31
+ @response ||= Request.new(@vat, @options).perform
61
32
  end
62
33
 
63
34
  def show_details?
64
- requester_vat || options[:detail]
35
+ @options[:requester] || @options[:detail]
65
36
  end
66
37
 
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
38
+ def handle_vies_error(error)
39
+ if error.is_a?(ViesMaintenanceError)
40
+ raise error if @options[:raise_error]
41
+ else
42
+ raise error unless @options[:raise_error] == false
74
43
  end
75
44
  end
76
45
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valvat
4
+ class Lookup
5
+ class Fault < Response
6
+ def self.cleanup(hash)
7
+ fault = hash[:fault][:faultstring]
8
+ return { valid: false } if fault == 'INVALID_INPUT'
9
+
10
+ { error: fault_to_error(fault) }
11
+ end
12
+
13
+ FAULTS = {
14
+ 'SERVICE_UNAVAILABLE' => ServiceUnavailable,
15
+ 'MS_UNAVAILABLE' => MemberStateUnavailable,
16
+ 'INVALID_REQUESTER_INFO' => InvalidRequester,
17
+ 'TIMEOUT' => Timeout,
18
+ 'VAT_BLOCKED' => BlockedError,
19
+ 'IP_BLOCKED' => BlockedError,
20
+ 'GLOBAL_MAX_CONCURRENT_REQ' => RateLimitError,
21
+ 'GLOBAL_MAX_CONCURRENT_REQ_TIME' => RateLimitError,
22
+ 'MS_MAX_CONCURRENT_REQ' => RateLimitError,
23
+ 'MS_MAX_CONCURRENT_REQ_TIME' => RateLimitError
24
+ }.freeze
25
+
26
+ def self.fault_to_error(fault)
27
+ (FAULTS[fault] || UnknownViesError).new(fault)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,31 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'savon'
4
+
1
5
  class Valvat
2
6
  class Lookup
3
7
  class Request
4
- def initialize(vat)
5
- @vat = vat
8
+ VIES_WSDL_URL = 'https://ec.europa.eu/taxation_customs/vies/checkVatService.wsdl'
9
+
10
+ def initialize(vat, options)
11
+ @vat = Valvat(vat)
12
+ @options = options || {}
13
+ @requester = @options[:requester] && Valvat(@options[:requester])
6
14
  end
7
15
 
8
- def perform(client)
9
- client.call(action, :message => body, :message_tag => message_tag).to_hash[response_key]
16
+ def perform
17
+ Response.new(
18
+ client.call(action, message: message, message_tag: message_tag)
19
+ )
20
+ rescue Savon::SOAPFault => e
21
+ Fault.new(e)
10
22
  end
11
23
 
12
24
  private
13
25
 
14
- def body
15
- {:country_code => @vat.vat_country_code, :vat_number => @vat.to_s_wo_country}
26
+ def client
27
+ Savon::Client.new({
28
+ wsdl: VIES_WSDL_URL, log: false, follow_redirects: true
29
+ }.merge(@options[:savon] || {}))
30
+ end
31
+
32
+ def message
33
+ add_requester({
34
+ country_code: @vat.vat_country_code,
35
+ vat_number: @vat.to_s_wo_country
36
+ })
16
37
  end
17
38
 
18
39
  def message_tag
19
- :checkVat
40
+ @requester ? :checkVatApprox : :checkVat
20
41
  end
21
42
 
22
43
  def action
23
- :check_vat
44
+ @requester ? :check_vat_approx : :check_vat
24
45
  end
25
46
 
26
- def response_key
27
- :check_vat_response
47
+ def add_requester(message)
48
+ return message unless @requester
49
+
50
+ message[:requester_country_code] = @requester.vat_country_code
51
+ message[:requester_vat_number] = @requester.to_s_wo_country
52
+
53
+ message
28
54
  end
29
55
  end
30
56
  end
31
- end
57
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Valvat
4
+ class Lookup
5
+ class Response
6
+ def initialize(raw)
7
+ @raw = raw
8
+ end
9
+
10
+ def [](key)
11
+ to_hash[key]
12
+ end
13
+
14
+ def to_hash
15
+ @to_hash ||= self.class.cleanup(@raw.to_hash)
16
+ end
17
+
18
+ def self.cleanup(hash)
19
+ (
20
+ hash[:check_vat_approx_response] || hash[:check_vat_response] || {}
21
+ ).each_with_object({}) do |(key, value), result|
22
+ result[cleanup_key(key)] = cleanup_value(value) unless key == :"@xmlns"
23
+ end
24
+ end
25
+
26
+ TRADER_PREFIX = /\Atrader_/.freeze
27
+
28
+ def self.cleanup_key(key)
29
+ key.to_s.sub(TRADER_PREFIX, '').to_sym
30
+ end
31
+
32
+ def self.cleanup_value(value)
33
+ value == '---' ? nil : value
34
+ end
35
+ end
36
+ end
37
+ end
data/lib/valvat/syntax.rb CHANGED
@@ -1,36 +1,37 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Valvat
2
4
  module Syntax
3
-
4
5
  VAT_PATTERNS = {
5
- 'AT' => /\AATU[0-9]{8}\Z/, # Austria
6
- 'BE' => /\ABE0[0-9]{9}\Z/, # Belgium
7
- 'BG' => /\ABG[0-9]{9,10}\Z/, # Bulgaria
8
- 'CY' => /\ACY(?!12)[0-59][0-9]{7}[A-Z]\Z/, # Cyprus
9
- 'CZ' => /\ACZ[0-9]{8,10}\Z/, # Czech Republic
10
- 'DE' => /\ADE[0-9]{9}\Z/, # Germany
11
- 'DK' => /\ADK[0-9]{8}\Z/, # Denmark
12
- 'EE' => /\AEE10[0-9]{7}\Z/, # Estonia
13
- 'GR' => /\AEL[0-9]{9}\Z/, # Greece
14
- 'ES' => /\AES([A-Z][0-9]{8}|[0-9]{8}[A-Z]|[A-Z][0-9]{7}[A-Z])\Z/, # Spain
15
- 'FI' => /\AFI[0-9]{8}\Z/, # Finland
16
- 'FR' => /\AFR[A-Z0-9]{2}[0-9]{9}\Z/, # France
17
- 'GB' => /\AGB([0-9]{9}|[0-9]{12}|(HA|GD)[0-9]{3})\Z/, # United Kingdom
18
- 'HR' => /\AHR[0-9]{11}\Z/, # Croatia
19
- 'HU' => /\AHU[0-9]{8}\Z/, # Hungary
20
- 'IE' => /\AIE([0-9][A-Z][0-9]{5}|[0-9]{7}[A-Z]?)[A-Z]\Z/, # Ireland
21
- 'IT' => /\AIT[0-9]{11}\Z/, # Italy
22
- 'LT' => /\ALT([0-9]{7}1[0-9]|[0-9]{10}1[0-9])\Z/, # Lithuania
23
- 'LU' => /\ALU[0-9]{8}\Z/, # Luxembourg
24
- 'LV' => /\ALV[0-9]{11}\Z/, # Latvia
25
- 'MT' => /\AMT[0-9]{8}\Z/, # Malta
26
- 'NL' => /\ANL[0-9]{9}B[0-9]{2}\Z/, # Netherlands
27
- 'PL' => /\APL[0-9]{10}\Z/, # Poland
28
- 'PT' => /\APT[0-9]{9}\Z/, # Portugal
29
- 'RO' => /\ARO[1-9][0-9]{1,9}\Z/, # Romania
30
- 'SE' => /\ASE[0-9]{10}01\Z/, # Sweden
31
- 'SI' => /\ASI[0-9]{8}\Z/, # Slovenia
32
- 'SK' => /\ASK[0-9]{10}\Z/ # Slovakia
33
- }
6
+ 'AT' => /\AATU[0-9]{8}\Z/, # Austria
7
+ 'BE' => /\ABE0[0-9]{9}\Z/, # Belgium
8
+ 'BG' => /\ABG[0-9]{9,10}\Z/, # Bulgaria
9
+ 'CY' => /\ACY(?!12)[0-59][0-9]{7}[A-Z]\Z/, # Cyprus
10
+ 'CZ' => /\ACZ[0-9]{8,10}\Z/, # Czech Republic
11
+ 'DE' => /\ADE[0-9]{9}\Z/, # Germany
12
+ 'DK' => /\ADK[0-9]{8}\Z/, # Denmark
13
+ 'EE' => /\AEE10[0-9]{7}\Z/, # Estonia
14
+ 'GR' => /\AEL[0-9]{9}\Z/, # Greece
15
+ 'ES' => /\AES([A-Z][0-9]{8}|[0-9]{8}[A-Z]|[A-Z][0-9]{7}[A-Z])\Z/, # Spain
16
+ 'FI' => /\AFI[0-9]{8}\Z/, # Finland
17
+ 'FR' => /\AFR[A-Z0-9]{2}[0-9]{9}\Z/, # France
18
+ 'GB' => /\A(GB|XI)([0-9]{9}|[0-9]{12}|(HA|GD)[0-9]{3})\Z/, # United Kingdom
19
+ 'HR' => /\AHR[0-9]{11}\Z/, # Croatia
20
+ 'HU' => /\AHU[0-9]{8}\Z/, # Hungary
21
+ 'IE' => /\AIE([0-9][A-Z][0-9]{5}|[0-9]{7}[A-Z]?)[A-Z]\Z/, # Ireland
22
+ 'IT' => /\AIT[0-9]{11}\Z/, # Italy
23
+ 'LT' => /\ALT([0-9]{7}1[0-9]|[0-9]{10}1[0-9])\Z/, # Lithuania
24
+ 'LU' => /\ALU[0-9]{8}\Z/, # Luxembourg
25
+ 'LV' => /\ALV[0-9]{11}\Z/, # Latvia
26
+ 'MT' => /\AMT[0-9]{8}\Z/, # Malta
27
+ 'NL' => /\ANL[0-9]{9}B[0-9]{2}\Z/, # Netherlands
28
+ 'PL' => /\APL[0-9]{10}\Z/, # Poland
29
+ 'PT' => /\APT[0-9]{9}\Z/, # Portugal
30
+ 'RO' => /\ARO[1-9][0-9]{1,9}\Z/, # Romania
31
+ 'SE' => /\ASE[0-9]{10}01\Z/, # Sweden
32
+ 'SI' => /\ASI[0-9]{8}\Z/, # Slovenia
33
+ 'SK' => /\ASK[0-9]{10}\Z/ # Slovakia
34
+ }.freeze
34
35
 
35
36
  def self.validate(vat)
36
37
  vat = Valvat(vat)
data/lib/valvat/utils.rb CHANGED
@@ -1,27 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+
1
5
  class Valvat
2
6
  module Utils
3
-
4
- EU_COUNTRIES = %w(AT BE BG CY CZ DE DK EE ES FI FR GB GR HR HU IE IT LT LU LV MT NL PL PT RO SE SI SK)
5
- COUNTRY_PATTERN = /\A([A-Z]{2})(.+)\Z/
6
- NORMALIZE_PATTERN = /[[:space:][:punct:][:cntrl:]]+/
7
+ EU_MEMBER_STATES = %w[AT BE BG CY CZ DE DK EE ES FI FR GR HR HU IE IT LT LU LV MT NL PL PT RO SE SI SK].freeze
8
+ SUPPORTED_STATES = EU_MEMBER_STATES + %w[GB]
9
+ EU_COUNTRIES = EU_MEMBER_STATES # TODO: Remove constant
10
+ COUNTRY_PATTERN = /\A([A-Z]{2})(.+)\Z/.freeze
11
+ NORMALIZE_PATTERN = /[[:space:][:punct:][:cntrl:]]+/.freeze
12
+ CONVERT_VAT_TO_ISO_COUNTRY = { 'EL' => 'GR', 'XI' => 'GB' }.freeze
13
+ CONVERT_ISO_TO_VAT_COUNTRY = CONVERT_VAT_TO_ISO_COUNTRY.invert.freeze
7
14
 
8
15
  def self.split(vat)
9
16
  COUNTRY_PATTERN =~ vat
10
- result = [$1, $2]
17
+ result = [Regexp.last_match(1), Regexp.last_match(2)]
11
18
  iso_country = vat_country_to_iso_country(result[0])
12
- EU_COUNTRIES.include?(iso_country) ? result : [nil, nil]
19
+ country_is_supported?(iso_country) ? result : [nil, nil]
13
20
  end
14
21
 
15
22
  def self.normalize(vat)
16
- vat.to_s.upcase.gsub(NORMALIZE_PATTERN, "")
23
+ vat.to_s.upcase.gsub(NORMALIZE_PATTERN, '')
17
24
  end
18
25
 
19
26
  def self.vat_country_to_iso_country(vat_country)
20
- vat_country == "EL" ? "GR" : vat_country
27
+ CONVERT_VAT_TO_ISO_COUNTRY[vat_country] || vat_country
21
28
  end
22
29
 
23
30
  def self.iso_country_to_vat_country(iso_country)
24
- iso_country == "GR" ? "EL" : iso_country
31
+ CONVERT_ISO_TO_VAT_COUNTRY[iso_country] || iso_country
32
+ end
33
+
34
+ def self.country_is_supported?(iso_country)
35
+ SUPPORTED_STATES.include?(iso_country)
25
36
  end
26
37
  end
27
38
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Valvat
2
- VERSION = "0.9.1"
4
+ VERSION = '1.1.0'
3
5
  end
@@ -1,28 +1,30 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  if defined?(ActiveModel)
4
6
  class Invoice < ModelBase
5
- validates :vat_number, :valvat => true
7
+ validates :vat_number, valvat: true
6
8
  end
7
9
 
8
10
  class InvoiceWithLookup < ModelBase
9
- validates :vat_number, :valvat => {:lookup => true}
11
+ validates :vat_number, valvat: { lookup: true }
10
12
  end
11
13
 
12
14
  class InvoiceWithLookupAndFailIfDown < ModelBase
13
- validates :vat_number, :valvat => {:lookup => :fail_if_down}
15
+ validates :vat_number, valvat: { lookup: :fail_if_down }
14
16
  end
15
17
 
16
18
  class InvoiceAllowBlank < ModelBase
17
- validates :vat_number, :valvat => {:allow_blank => true}
19
+ validates :vat_number, valvat: { allow_blank: true }
18
20
  end
19
21
 
20
22
  class InvoiceAllowBlankOnAll < ModelBase
21
- validates :vat_number, :valvat => true, :allow_blank => true
23
+ validates :vat_number, valvat: true, allow_blank: true
22
24
  end
23
25
 
24
26
  class InvoiceCheckCountry < ModelBase
25
- validates :vat_number, :valvat => {:match_country => :country}
27
+ validates :vat_number, valvat: { match_country: :country }
26
28
 
27
29
  def country
28
30
  @attributes[:country]
@@ -30,7 +32,7 @@ if defined?(ActiveModel)
30
32
  end
31
33
 
32
34
  class InvoiceCheckCountryWithLookup < ModelBase
33
- validates :vat_number, :valvat => {:match_country => :country, :lookup => true}
35
+ validates :vat_number, valvat: { match_country: :country, lookup: true }
34
36
 
35
37
  def country
36
38
  @attributes[:country]
@@ -38,205 +40,235 @@ if defined?(ActiveModel)
38
40
  end
39
41
 
40
42
  class InvoiceWithChecksum < ModelBase
41
- validates :vat_number, :valvat => {:checksum => true}
43
+ validates :vat_number, valvat: { checksum: true }
42
44
  end
43
45
 
44
46
  describe Invoice do
45
- context "with valid vat number" do
46
- it "should be valid" do
47
- expect(Invoice.new(:vat_number => "DE259597697")).to be_valid
47
+ before do
48
+ I18n.locale = :en
49
+ end
50
+
51
+ context 'with valid VAT number' do
52
+ it 'is valid' do
53
+ expect(described_class.new(vat_number: 'DE259597697')).to be_valid
48
54
  end
49
55
  end
50
56
 
51
- context "with invalid vat number" do
52
- let(:invoice) { Invoice.new(:vat_number => "DE259597697123") }
57
+ context 'with invalid VAT number' do
58
+ let(:invoice) { described_class.new(vat_number: 'DE259597697123') }
53
59
 
54
- it "should not be valid" do
60
+ it 'is not valid' do
55
61
  expect(invoice).not_to be_valid
56
62
  end
57
63
 
58
- it "should add default (country specific) error message" do
64
+ it 'adds default (country specific) error message' do
59
65
  invoice.valid?
60
- expect(invoice.errors[:vat_number]).to eql(["is not a valid German vat number"])
66
+ expect(invoice.errors[:vat_number]).to eql(['is not a valid German VAT number'])
61
67
  end
62
68
 
63
- context "with i18n translation in place" do
69
+ context 'with DE locale' do
64
70
  before do
65
- I18n.backend.store_translations(:en, :activemodel => {
66
- :errors => {:models => {:invoice => {:invalid_vat => "is ugly."}}}
67
- })
71
+ I18n.locale = :de
72
+ end
73
+
74
+ it 'adds translated error message' do
75
+ invoice.valid?
76
+ expect(invoice.errors[:vat_number]).to eql(['ist keine gültige deutsche USt-IdNr.'])
77
+ end
78
+ end
79
+
80
+ context 'with PT locale' do
81
+ before do
82
+ I18n.locale = :pt
83
+ end
84
+
85
+ it 'adds translated error message' do
86
+ invoice.valid?
87
+ expect(invoice.errors[:vat_number]).to eql(['O NIF alemão não é válido.'])
88
+ end
89
+ end
90
+
91
+ context 'with i18n translation in place' do
92
+ before do
93
+ I18n.backend.store_translations(:en, activemodel: {
94
+ errors: { models: { invoice: { invalid_vat: 'is ugly.' } } }
95
+ })
68
96
  end
69
97
 
70
98
  after { I18n.reload! }
71
99
 
72
- it "should use translation" do
100
+ it 'uses translation' do
73
101
  invoice.valid?
74
- expect(invoice.errors[:vat_number]).to eql(["is ugly."])
102
+ expect(invoice.errors[:vat_number]).to eql(['is ugly.'])
75
103
  end
76
104
  end
77
105
 
78
- context "with i18n translation with country adjective placeholder in place" do
106
+ context 'with i18n translation with country_adjective placeholder in place' do
79
107
  before do
80
- I18n.backend.store_translations(:en, :activemodel => {
81
- :errors => {:models => {:invoice => {:invalid_vat => "is not a %{country_adjective} vat"}}}
82
- })
108
+ msg = 'is not a %{country_adjective} vat' # rubocop:disable Style/FormatStringToken
109
+ translation = { activemodel: { errors: { models: { invoice: { invalid_vat: msg } } } } }
110
+ I18n.backend.store_translations(:en, translation)
83
111
  end
84
112
 
85
113
  after { I18n.reload! }
86
114
 
87
- it "should replace country adjective placeholder" do
88
- invoice = Invoice.new(:vat_number => "IE123")
115
+ it 'replaces country_adjective placeholder' do
116
+ invoice = described_class.new(vat_number: 'IE123')
89
117
  invoice.valid?
90
- expect(invoice.errors[:vat_number]).to eql(["is not a Irish vat"])
118
+ expect(invoice.errors[:vat_number]).to eql(['is not a Irish vat'])
91
119
  end
92
120
 
93
- it "should fall back to 'European' if country is missing" do
94
- invoice = Invoice.new(:vat_number => "XX123")
121
+ it "falls back to 'European' if country is missing" do
122
+ invoice = described_class.new(vat_number: 'XX123')
95
123
  invoice.valid?
96
- expect(invoice.errors[:vat_number]).to eql(["is not a European vat"])
124
+ expect(invoice.errors[:vat_number]).to eql(['is not a European vat'])
97
125
  end
98
126
  end
99
127
  end
100
128
 
101
- context "with blank vat number" do
102
- it "should not be valid" do
103
- expect(Invoice.new(:vat_number => "")).not_to be_valid
104
- expect(Invoice.new(:vat_number => nil)).not_to be_valid
129
+ context 'with blank VAT number' do
130
+ it 'is not valid' do
131
+ expect(described_class.new(vat_number: '')).not_to be_valid
132
+ expect(described_class.new(vat_number: nil)).not_to be_valid
105
133
  end
106
134
  end
107
135
  end
108
136
 
109
137
  describe InvoiceWithLookup do
110
- context "with valid but not existing vat number" do
138
+ context 'with valid but not existing VAT number' do
111
139
  before do
112
- allow(Valvat::Syntax).to receive_messages(:validate => true)
113
- allow(Valvat::Lookup).to receive_messages(:validate => false)
140
+ allow(Valvat::Syntax).to receive_messages(validate: true)
141
+ allow(Valvat::Lookup).to receive_messages(validate: false)
114
142
  end
115
143
 
116
- it "should not be valid" do
117
- expect(InvoiceWithLookup.new(:vat_number => "DE123")).not_to be_valid
144
+ it 'is not valid' do
145
+ expect(described_class.new(vat_number: 'DE123')).not_to be_valid
118
146
  end
119
147
  end
120
148
 
121
- context "with valid and existing vat number" do
149
+ context 'with valid and existing VAT number' do
122
150
  before do
123
- allow(Valvat::Syntax).to receive_messages(:validate => true)
124
- allow(Valvat::Lookup).to receive_messages(:validate => true)
151
+ allow(Valvat::Syntax).to receive_messages(validate: true)
152
+ allow(Valvat::Lookup).to receive_messages(validate: true)
125
153
  end
126
154
 
127
- it "should be valid" do
128
- expect(InvoiceWithLookup.new(:vat_number => "DE123")).to be_valid
155
+ it 'is valid' do
156
+ expect(described_class.new(vat_number: 'DE123')).to be_valid
129
157
  end
130
158
  end
131
159
 
132
- context "with valid vat number and VIES country service down" do
160
+ context 'with valid VAT number and VIES country service down' do
133
161
  before do
134
- allow(Valvat::Syntax).to receive_messages(:validate => true)
135
- allow(Valvat::Lookup).to receive_messages(:validate => nil)
162
+ allow(Valvat::Syntax).to receive_messages(validate: true)
163
+ allow(Valvat::Lookup).to receive_messages(validate: nil)
136
164
  end
137
165
 
138
- it "should be valid" do
139
- expect(InvoiceWithLookup.new(:vat_number => "DE123")).to be_valid
166
+ it 'is valid' do
167
+ expect(described_class.new(vat_number: 'DE123')).to be_valid
140
168
  end
141
169
  end
142
170
  end
143
171
 
144
172
  describe InvoiceWithLookupAndFailIfDown do
145
- context "with valid vat number and VIES country service down" do
173
+ let(:record) { described_class.new }
174
+
175
+ context 'with valid VAT number and VIES country service down' do
146
176
  before do
147
- allow(Valvat::Syntax).to receive_messages(:validate => true)
148
- allow(Valvat::Lookup).to receive_messages(:validate => nil)
177
+ allow(Valvat::Syntax).to receive_messages(validate: true)
178
+ allow(Valvat::Lookup).to receive_messages(validate: nil)
149
179
  end
150
180
 
151
- it "should not be valid" do
152
- expect(InvoiceWithLookupAndFailIfDown.new(:vat_number => "DE123")).not_to be_valid
181
+ it 'is not valid' do
182
+ expect(record).not_to be_valid
183
+ msg = 'Unable to validate your VAT number: the VIES service is down. Please try again later.'
184
+ expect(record.errors[:vat_number]).to eql([msg])
153
185
  end
154
186
  end
155
187
  end
156
188
 
157
189
  describe InvoiceAllowBlank do
158
- context "with blank vat number" do
159
- it "should be valid" do
160
- expect(InvoiceAllowBlank.new(:vat_number => "")).to be_valid
161
- expect(InvoiceAllowBlank.new(:vat_number => nil)).to be_valid
190
+ context 'with blank VAT number' do
191
+ it 'is valid' do
192
+ expect(described_class.new(vat_number: '')).to be_valid
193
+ expect(described_class.new(vat_number: nil)).to be_valid
162
194
  end
163
195
  end
164
196
  end
165
197
 
166
198
  describe InvoiceAllowBlankOnAll do
167
- context "with blank vat number" do
168
- it "should be valid" do
169
- expect(InvoiceAllowBlankOnAll.new(:vat_number => "")).to be_valid
170
- expect(InvoiceAllowBlankOnAll.new(:vat_number => nil)).to be_valid
199
+ context 'with blank VAT number' do
200
+ it 'is valid' do
201
+ expect(described_class.new(vat_number: '')).to be_valid
202
+ expect(described_class.new(vat_number: nil)).to be_valid
171
203
  end
172
204
  end
173
205
  end
174
206
 
175
207
  describe InvoiceCheckCountry do
176
- it "should be not valid on blank country" do
177
- expect(InvoiceCheckCountry.new(:country => nil, :vat_number => "DE259597697")).not_to be_valid
178
- expect(InvoiceCheckCountry.new(:country => "", :vat_number => "DE259597697")).not_to be_valid
208
+ it 'is not valid on blank country' do
209
+ expect(described_class.new(country: nil, vat_number: 'DE259597697')).not_to be_valid
210
+ expect(described_class.new(country: '', vat_number: 'DE259597697')).not_to be_valid
179
211
  end
180
212
 
181
- it "should be not valid on wired country" do
182
- expect(InvoiceCheckCountry.new(:country => "XAXXX", :vat_number => "DE259597697")).not_to be_valid
183
- expect(InvoiceCheckCountry.new(:country => "ZO", :vat_number => "DE259597697")).not_to be_valid
213
+ it 'is not valid on wired country' do
214
+ expect(described_class.new(country: 'XAXXX', vat_number: 'DE259597697')).not_to be_valid
215
+ expect(described_class.new(country: 'ZO', vat_number: 'DE259597697')).not_to be_valid
184
216
  end
185
217
 
186
- it "should be not valid on mismatching (eu) country" do
187
- expect(InvoiceCheckCountry.new(:country => "FR", :vat_number => "DE259597697")).not_to be_valid
188
- expect(InvoiceCheckCountry.new(:country => "AT", :vat_number => "DE259597697")).not_to be_valid
189
- expect(InvoiceCheckCountry.new(:country => "DE", :vat_number => "ATU65931334")).not_to be_valid
218
+ it 'is not valid on mismatching (eu) country' do
219
+ expect(described_class.new(country: 'FR', vat_number: 'DE259597697')).not_to be_valid
220
+ expect(described_class.new(country: 'AT', vat_number: 'DE259597697')).not_to be_valid
221
+ expect(described_class.new(country: 'DE', vat_number: 'ATU65931334')).not_to be_valid
190
222
  end
191
223
 
192
- it "should be valid on matching country" do
193
- expect(InvoiceCheckCountry.new(:country => "DE", :vat_number => "DE259597697")).to be_valid
194
- expect(InvoiceCheckCountry.new(:country => "AT", :vat_number => "ATU65931334")).to be_valid
224
+ it 'is valid on matching country' do
225
+ expect(described_class.new(country: 'DE', vat_number: 'DE259597697')).to be_valid
226
+ expect(described_class.new(country: 'AT', vat_number: 'ATU65931334')).to be_valid
195
227
  end
196
228
 
197
- it "should give back error message with country from :country_match" do
198
- invoice = InvoiceCheckCountry.new(:country => "FR", :vat_number => "DE259597697")
229
+ it 'gives back error message with country from :country_match' do
230
+ invoice = described_class.new(country: 'FR', vat_number: 'DE259597697')
199
231
  invoice.valid?
200
- expect(invoice.errors[:vat_number]).to eql(["is not a valid French vat number"])
232
+ expect(invoice.errors[:vat_number]).to eql(['is not a valid French VAT number'])
201
233
  end
202
234
 
203
- it "should give back error message with country from :country_match even on invalid vat number" do
204
- invoice = InvoiceCheckCountry.new(:country => "FR", :vat_number => "DE259597697123")
235
+ it 'gives back error message with country from :country_match even on invalid VAT number' do
236
+ invoice = described_class.new(country: 'FR', vat_number: 'DE259597697123')
205
237
  invoice.valid?
206
- expect(invoice.errors[:vat_number]).to eql(["is not a valid French vat number"])
238
+ expect(invoice.errors[:vat_number]).to eql(['is not a valid French VAT number'])
207
239
  end
208
240
  end
209
241
 
210
242
  describe InvoiceCheckCountryWithLookup do
211
243
  before do
212
- allow(Valvat::Syntax).to receive_messages(:validate => true)
213
- allow(Valvat::Lookup).to receive_messages(:validate => true)
244
+ allow(Valvat::Syntax).to receive_messages(validate: true)
245
+ allow(Valvat::Lookup).to receive_messages(validate: true)
214
246
  end
215
247
 
216
- it "avoids lookup or syntax check on failed because of mismatching country" do
217
- expect(Valvat::Syntax).not_to receive(:validate)
218
- expect(Valvat::Lookup).not_to receive(:validate)
219
- InvoiceCheckCountryWithLookup.new(:country => "FR", :vat_number => "DE259597697").valid?
248
+ it 'avoids lookup or syntax check on failed because of mismatching country' do
249
+ described_class.new(country: 'FR', vat_number: 'DE259597697').valid?
250
+ expect(Valvat::Syntax).not_to have_received(:validate)
251
+ expect(Valvat::Lookup).not_to have_received(:validate)
220
252
  end
221
253
 
222
- it "check syntax and looup on matching country" do
223
- expect(Valvat::Syntax).to receive(:validate).and_return(true)
224
- expect(Valvat::Lookup).to receive(:validate).and_return(true)
225
- InvoiceCheckCountryWithLookup.new(:country => "DE", :vat_number => "DE259597697").valid?
254
+ it 'check syntax and looup on matching country' do
255
+ described_class.new(country: 'DE', vat_number: 'DE259597697').valid?
256
+ expect(Valvat::Syntax).to have_received(:validate)
257
+ expect(Valvat::Lookup).to have_received(:validate)
226
258
  end
227
259
  end
228
260
 
229
261
  describe InvoiceWithChecksum do
230
- context "with valid vat number" do
231
- it "should be valid" do
232
- expect(InvoiceWithChecksum.new(:vat_number => "DE259597697")).to be_valid
262
+ context 'with valid VAT number' do
263
+ it 'is valid' do
264
+ expect(described_class.new(vat_number: 'DE259597697')).to be_valid
233
265
  end
234
266
  end
235
267
 
236
- context "with invalid vat number" do
237
- it "should not be valid" do
238
- expect(InvoiceWithChecksum.new(:vat_number => "DE259597687")).not_to be_valid
268
+ context 'with invalid VAT number' do
269
+ it 'is not valid' do
270
+ expect(described_class.new(vat_number: 'DE259597687')).not_to be_valid
239
271
  end
240
272
  end
241
273
  end
242
- end
274
+ end