valvat 1.1.4 → 1.2.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/ruby.yml +3 -3
- data/.rubocop.yml +11 -2
- data/CHANGES.md +19 -2
- data/MIT-LICENSE +1 -1
- data/README.md +151 -80
- data/gemfiles/activemodel-6 +3 -0
- data/gemfiles/activemodel-7 +3 -0
- data/gemfiles/standalone +3 -0
- data/lib/active_model/validations/valvat_validator.rb +1 -1
- data/lib/valvat/checksum/es.rb +49 -16
- data/lib/valvat/checksum/fr.rb +1 -1
- data/lib/valvat/checksum/hr.rb +1 -1
- data/lib/valvat/checksum/hu.rb +1 -1
- data/lib/valvat/error.rb +16 -16
- data/lib/valvat/local.rb +2 -49
- data/lib/valvat/lookup/base.rb +73 -0
- data/lib/valvat/lookup/hmrc.rb +86 -0
- data/lib/valvat/lookup/vies.rb +98 -0
- data/lib/valvat/lookup.rb +16 -5
- data/lib/valvat/utils.rb +0 -1
- data/lib/valvat/version.rb +1 -1
- data/lib/valvat.rb +50 -4
- data/spec/spec_helper.rb +2 -1
- data/spec/valvat/checksum/es_spec.rb +48 -1
- data/spec/valvat/checksum/gb_spec.rb +1 -0
- data/spec/valvat/lookup/hmrc_spec.rb +32 -0
- data/spec/valvat/lookup/vies_spec.rb +23 -0
- data/spec/valvat/lookup_spec.rb +272 -80
- data/spec/valvat/utils_spec.rb +1 -1
- data/spec/valvat_spec.rb +12 -12
- data/valvat.gemspec +3 -2
- data.tar.gz.sig +0 -0
- metadata +24 -25
- metadata.gz.sig +0 -0
- data/lib/valvat/lookup/fault.rb +0 -38
- data/lib/valvat/lookup/request.rb +0 -57
- data/lib/valvat/lookup/response.rb +0 -37
- data/spec/valvat/lookup/fault_spec.rb +0 -34
- data/spec/valvat/lookup/request_spec.rb +0 -32
- data/spec/valvat/lookup/response_spec.rb +0 -29
data/lib/valvat/error.rb
CHANGED
@@ -3,32 +3,32 @@
|
|
3
3
|
class Valvat
|
4
4
|
Error = Class.new(RuntimeError)
|
5
5
|
|
6
|
-
class
|
7
|
-
def initialize(
|
8
|
-
@
|
9
|
-
@
|
10
|
-
super(
|
6
|
+
class LookupError < Error
|
7
|
+
def initialize(message, kind)
|
8
|
+
@message = message.to_s
|
9
|
+
@kind = kind.is_a?(Class) ? kind.name.split('::').last : kind.to_s
|
10
|
+
super(@message)
|
11
11
|
end
|
12
12
|
|
13
13
|
def to_s
|
14
|
-
"The
|
14
|
+
"The #{@kind} web service returned the error: #{@message}"
|
15
15
|
end
|
16
16
|
|
17
17
|
def eql?(other)
|
18
18
|
to_s.eql?(other.to_s)
|
19
19
|
end
|
20
20
|
end
|
21
|
-
|
21
|
+
MaintenanceError = Class.new(LookupError)
|
22
22
|
|
23
|
-
ServiceUnavailable = Class.new(
|
24
|
-
MemberStateUnavailable = Class.new(
|
23
|
+
ServiceUnavailable = Class.new(MaintenanceError)
|
24
|
+
MemberStateUnavailable = Class.new(MaintenanceError)
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
BlockedError = Class.new(ViesError)
|
31
|
-
RateLimitError = Class.new(ViesError)
|
26
|
+
Timeout = Class.new(LookupError)
|
27
|
+
InvalidRequester = Class.new(LookupError)
|
28
|
+
BlockedError = Class.new(LookupError)
|
29
|
+
RateLimitError = Class.new(LookupError)
|
32
30
|
|
33
|
-
|
31
|
+
UnknownLookupError = Class.new(LookupError)
|
32
|
+
|
33
|
+
HTTPError = Class.new(LookupError)
|
34
34
|
end
|
data/lib/valvat/local.rb
CHANGED
@@ -1,52 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
def initialize(raw)
|
5
|
-
@raw = Valvat::Utils.normalize(raw || '')
|
6
|
-
@vat_country_code, @to_s_wo_country = to_a
|
7
|
-
end
|
3
|
+
puts "DEPRECATED: Requiring 'valvat/local' is deprecated. Please require 'valvat' directly."
|
8
4
|
|
9
|
-
|
10
|
-
|
11
|
-
def blank?
|
12
|
-
raw.nil? || raw.strip == ''
|
13
|
-
end
|
14
|
-
|
15
|
-
def valid?
|
16
|
-
Valvat::Syntax.validate(self)
|
17
|
-
end
|
18
|
-
|
19
|
-
def valid_checksum?
|
20
|
-
Valvat::Checksum.validate(self)
|
21
|
-
end
|
22
|
-
|
23
|
-
def iso_country_code
|
24
|
-
Valvat::Utils.vat_country_to_iso_country(vat_country_code)
|
25
|
-
end
|
26
|
-
|
27
|
-
# TODO: Remove method / not in use
|
28
|
-
def european?
|
29
|
-
Valvat::Utils::EU_MEMBER_STATES.include?(iso_country_code)
|
30
|
-
end
|
31
|
-
|
32
|
-
def to_a
|
33
|
-
Valvat::Utils.split(raw)
|
34
|
-
end
|
35
|
-
|
36
|
-
def to_s
|
37
|
-
raw
|
38
|
-
end
|
39
|
-
|
40
|
-
def inspect
|
41
|
-
"#<Valvat #{[raw, iso_country_code].compact.join(' ')}>"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def Valvat(vat) # rubocop:disable Naming/MethodName
|
46
|
-
vat.is_a?(Valvat) ? vat : Valvat.new(vat)
|
47
|
-
end
|
48
|
-
|
49
|
-
require 'valvat/utils'
|
50
|
-
require 'valvat/syntax'
|
51
|
-
require 'valvat/checksum'
|
52
|
-
require 'valvat/version'
|
5
|
+
require_relative '../valvat'
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
class Valvat
|
6
|
+
class Lookup
|
7
|
+
class Base
|
8
|
+
def initialize(vat, options = {})
|
9
|
+
@vat = Valvat(vat)
|
10
|
+
@options = options
|
11
|
+
@requester = @options[:requester] && Valvat(@options[:requester])
|
12
|
+
end
|
13
|
+
|
14
|
+
def perform
|
15
|
+
response = fetch(endpoint_uri)
|
16
|
+
|
17
|
+
case response
|
18
|
+
when Net::HTTPSuccess
|
19
|
+
parse(response.body)
|
20
|
+
else
|
21
|
+
{ error: Valvat::HTTPError.new(response.code, self.class) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def endpoint_uri
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_request(uri)
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def parse(body)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
def fetch(uri, limit = 0)
|
40
|
+
response = send_request(uri)
|
41
|
+
|
42
|
+
if Net::HTTPRedirection == response && limit < 5
|
43
|
+
fetch(URI.parse(response['Location']), limit + 1)
|
44
|
+
else
|
45
|
+
response
|
46
|
+
end
|
47
|
+
rescue Errno::ECONNRESET
|
48
|
+
raise if limit > 5
|
49
|
+
|
50
|
+
fetch(uri, limit + 1)
|
51
|
+
end
|
52
|
+
|
53
|
+
def send_request(uri)
|
54
|
+
request = build_request(uri)
|
55
|
+
|
56
|
+
Net::HTTP.start(uri.host, uri.port, options_for(uri)) do |http|
|
57
|
+
http.request(request)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def options_for(uri)
|
62
|
+
options = if @options.key?(:savon)
|
63
|
+
puts 'DEPRECATED: The option :savon is deprecated. Use :http instead.'
|
64
|
+
@options[:savon]
|
65
|
+
else
|
66
|
+
@options[:http]
|
67
|
+
end || {}
|
68
|
+
|
69
|
+
options.merge({ use_ssl: URI::HTTPS === uri })
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'net/http'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
class Valvat
|
8
|
+
class Lookup
|
9
|
+
class HMRC < Base
|
10
|
+
ENDPOINT_URL = 'https://api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup'
|
11
|
+
HEADERS = {
|
12
|
+
# https://developer.service.hmrc.gov.uk/api-documentation/docs/reference-guide#versioning
|
13
|
+
'Accept' => 'application/vnd.hmrc.1.0+json'
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
def perform
|
17
|
+
return { valid: false } unless @options[:uk] == true
|
18
|
+
|
19
|
+
parse(fetch(endpoint_uri).body)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def endpoint_uri
|
25
|
+
endpoint = "/#{@vat.to_s_wo_country}"
|
26
|
+
endpoint += "/#{@requester.to_s_wo_country}" if @requester
|
27
|
+
URI.parse(ENDPOINT_URL + endpoint)
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_request(uri)
|
31
|
+
Net::HTTP::Get.new(uri.request_uri, HEADERS)
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse(body)
|
35
|
+
convert(JSON.parse(body))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return a similar format to VIES
|
39
|
+
# Main differences are:
|
40
|
+
# - request_date is a (more precise) Time instead of Date
|
41
|
+
# - address is newline separated instead of coma (also more precise)
|
42
|
+
def convert(raw)
|
43
|
+
return build_fault(raw) if raw.key?('code')
|
44
|
+
|
45
|
+
{
|
46
|
+
address: format_address(raw.dig('target', 'address')),
|
47
|
+
country_code: raw.dig('target', 'address', 'countryCode'),
|
48
|
+
name: raw.dig('target', 'name'),
|
49
|
+
vat_number: raw.dig('target', 'vatNumber'), valid: true
|
50
|
+
}.tap do |hash|
|
51
|
+
hash[:request_date] = Time.parse(raw['processingDate']) if raw.key?('processingDate')
|
52
|
+
hash[:request_identifier] = raw['consultationNumber'] if raw.key?('consultationNumber')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Example raw address from the API:
|
57
|
+
# {
|
58
|
+
# "line1": "HM REVENUE AND CUSTOMS",
|
59
|
+
# "line2": "RUBY HOUSE",
|
60
|
+
# "line3": "8 RUBY PLACE",
|
61
|
+
# "line4": "ABERDEEN",
|
62
|
+
# "postcode": "AB10 1ZP",
|
63
|
+
# "countryCode": "GB"
|
64
|
+
# }
|
65
|
+
def format_address(address)
|
66
|
+
address&.values&.join("\n")
|
67
|
+
end
|
68
|
+
|
69
|
+
FAULTS = {
|
70
|
+
'MESSAGE_THROTTLED_OUT' => RateLimitError,
|
71
|
+
'SCHEDULED_MAINTENANCE' => ServiceUnavailable,
|
72
|
+
'SERVER_ERROR' => ServiceUnavailable,
|
73
|
+
'INVALID_REQUEST' => InvalidRequester,
|
74
|
+
'GATEWAY_TIMEOUT' => Timeout
|
75
|
+
}.freeze
|
76
|
+
|
77
|
+
def build_fault(raw)
|
78
|
+
fault = raw['code']
|
79
|
+
return { valid: false } if fault == 'NOT_FOUND'
|
80
|
+
|
81
|
+
exception = FAULTS[fault] || UnknownLookupError
|
82
|
+
{ error: exception.new("#{fault}#{raw['message'] ? " (#{raw['message']})" : ''}", self.class) }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
require 'net/http'
|
5
|
+
require 'erb'
|
6
|
+
require 'rexml'
|
7
|
+
|
8
|
+
class Valvat
|
9
|
+
class Lookup
|
10
|
+
class VIES < Base
|
11
|
+
ENDPOINT_URI = URI('https://ec.europa.eu/taxation_customs/vies/services/checkVatService').freeze
|
12
|
+
HEADERS = {
|
13
|
+
'Accept' => 'text/xml;charset=UTF-8',
|
14
|
+
'Content-Type' => 'text/xml;charset=UTF-8',
|
15
|
+
'SOAPAction' => ''
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
BODY = <<-XML.gsub(/^\s+/, '')
|
19
|
+
<soapenv:Envelope
|
20
|
+
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
21
|
+
xmlns:urn="urn:ec.europa.eu:taxud:vies:services:checkVat:types">
|
22
|
+
<soapenv:Header/>
|
23
|
+
<soapenv:Body>
|
24
|
+
<urn:checkVat<%= 'Approx' if @requester %>>
|
25
|
+
<urn:countryCode><%= @vat.vat_country_code %></urn:countryCode>
|
26
|
+
<urn:vatNumber><%= @vat.to_s_wo_country %></urn:vatNumber>
|
27
|
+
<% if @requester %>
|
28
|
+
<urn:requesterCountryCode><%= @vat.vat_country_code %></urn:requesterCountryCode>
|
29
|
+
<urn:requesterVatNumber><%= @vat.to_s_wo_country %></urn:requesterVatNumber>
|
30
|
+
<% end %>
|
31
|
+
</urn:checkVat<%= 'Approx' if @requester %>>
|
32
|
+
</soapenv:Body>
|
33
|
+
</soapenv:Envelope>
|
34
|
+
XML
|
35
|
+
BODY_TEMPLATE = ERB.new(BODY).freeze
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def endpoint_uri
|
40
|
+
ENDPOINT_URI
|
41
|
+
end
|
42
|
+
|
43
|
+
def build_request(uri)
|
44
|
+
request = Net::HTTP::Post.new(uri.request_uri, HEADERS)
|
45
|
+
request.body = BODY_TEMPLATE.result(binding)
|
46
|
+
request
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse(body)
|
50
|
+
doc = REXML::Document.new(body)
|
51
|
+
elements = doc.get_elements('/env:Envelope/env:Body').first.first
|
52
|
+
convert_values(elements.each_with_object({}) do |el, hash|
|
53
|
+
hash[convert_key(el.name)] = convert_value(el.text)
|
54
|
+
end)
|
55
|
+
end
|
56
|
+
|
57
|
+
def convert_key(key)
|
58
|
+
key.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
59
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
60
|
+
.tr('-', '_')
|
61
|
+
.sub(/\Atrader_/, '')
|
62
|
+
.downcase.to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
def convert_value(value)
|
66
|
+
value == '---' ? nil : value
|
67
|
+
end
|
68
|
+
|
69
|
+
def convert_values(hash)
|
70
|
+
return build_fault(hash) if hash[:faultstring]
|
71
|
+
|
72
|
+
hash[:valid] = hash[:valid] == 'true' || (hash[:valid] == 'false' ? false : nil) if hash.key?(:valid)
|
73
|
+
hash[:request_date] = Date.parse(hash[:request_date]) if hash.key?(:request_date)
|
74
|
+
hash
|
75
|
+
end
|
76
|
+
|
77
|
+
FAULTS = {
|
78
|
+
'SERVICE_UNAVAILABLE' => ServiceUnavailable,
|
79
|
+
'MS_UNAVAILABLE' => MemberStateUnavailable,
|
80
|
+
'INVALID_REQUESTER_INFO' => InvalidRequester,
|
81
|
+
'TIMEOUT' => Timeout,
|
82
|
+
'VAT_BLOCKED' => BlockedError,
|
83
|
+
'IP_BLOCKED' => BlockedError,
|
84
|
+
'GLOBAL_MAX_CONCURRENT_REQ' => RateLimitError,
|
85
|
+
'GLOBAL_MAX_CONCURRENT_REQ_TIME' => RateLimitError,
|
86
|
+
'MS_MAX_CONCURRENT_REQ' => RateLimitError,
|
87
|
+
'MS_MAX_CONCURRENT_REQ_TIME' => RateLimitError
|
88
|
+
}.freeze
|
89
|
+
|
90
|
+
def build_fault(hash)
|
91
|
+
fault = hash[:faultstring]
|
92
|
+
return hash.merge({ valid: false }) if fault == 'INVALID_INPUT'
|
93
|
+
|
94
|
+
hash.merge({ error: (FAULTS[fault] || UnknownLookupError).new(fault, self.class) })
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
data/lib/valvat/lookup.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'lookup/vies'
|
4
|
+
require_relative 'lookup/hmrc'
|
5
|
+
|
3
6
|
class Valvat
|
4
7
|
class Lookup
|
5
8
|
def initialize(vat, options = {})
|
@@ -10,9 +13,9 @@ class Valvat
|
|
10
13
|
|
11
14
|
def validate
|
12
15
|
return false if !@options[:skip_local_validation] && !@vat.valid?
|
13
|
-
return
|
16
|
+
return handle_error(response[:error]) if response[:error]
|
14
17
|
|
15
|
-
response[:valid] && show_details? ? response
|
18
|
+
response[:valid] && show_details? ? response : response[:valid]
|
16
19
|
end
|
17
20
|
|
18
21
|
class << self
|
@@ -28,15 +31,23 @@ class Valvat
|
|
28
31
|
end
|
29
32
|
|
30
33
|
def response
|
31
|
-
@response ||=
|
34
|
+
@response ||= webservice.new(@vat, @options).perform
|
35
|
+
end
|
36
|
+
|
37
|
+
def webservice
|
38
|
+
case @vat.vat_country_code
|
39
|
+
when 'GB' then HMRC
|
40
|
+
else
|
41
|
+
VIES
|
42
|
+
end
|
32
43
|
end
|
33
44
|
|
34
45
|
def show_details?
|
35
46
|
@options[:requester] || @options[:detail]
|
36
47
|
end
|
37
48
|
|
38
|
-
def
|
39
|
-
if error.is_a?(
|
49
|
+
def handle_error(error)
|
50
|
+
if error.is_a?(MaintenanceError)
|
40
51
|
raise error if @options[:raise_error]
|
41
52
|
else
|
42
53
|
raise error unless @options[:raise_error] == false
|
data/lib/valvat/utils.rb
CHANGED
@@ -6,7 +6,6 @@ class Valvat
|
|
6
6
|
module Utils
|
7
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
8
|
SUPPORTED_STATES = EU_MEMBER_STATES + %w[GB]
|
9
|
-
EU_COUNTRIES = EU_MEMBER_STATES # TODO: Remove constant
|
10
9
|
COUNTRY_PATTERN = /\A([A-Z]{2})(.+)\Z/.freeze
|
11
10
|
NORMALIZE_PATTERN = /([[:punct:][:cntrl:]]|[[:space:]])+/.freeze
|
12
11
|
CONVERT_VAT_TO_ISO_COUNTRY = { 'EL' => 'GR', 'XI' => 'GB' }.freeze
|
data/lib/valvat/version.rb
CHANGED
data/lib/valvat.rb
CHANGED
@@ -1,16 +1,62 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'valvat/error'
|
4
|
-
require 'valvat/
|
4
|
+
require 'valvat/utils'
|
5
|
+
require 'valvat/syntax'
|
6
|
+
require 'valvat/checksum'
|
7
|
+
require 'valvat/version'
|
5
8
|
require 'valvat/lookup'
|
6
|
-
require 'valvat/lookup/request'
|
7
|
-
require 'valvat/lookup/response'
|
8
|
-
require 'valvat/lookup/fault'
|
9
9
|
require 'active_model/validations/valvat_validator' if defined?(ActiveModel)
|
10
10
|
|
11
11
|
class Valvat
|
12
|
+
def initialize(raw)
|
13
|
+
@raw = Valvat::Utils.normalize(raw || '')
|
14
|
+
@vat_country_code, @to_s_wo_country = to_a
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :raw, :vat_country_code, :to_s_wo_country
|
18
|
+
|
19
|
+
def blank?
|
20
|
+
raw.nil? || raw.strip == ''
|
21
|
+
end
|
22
|
+
|
23
|
+
def valid?
|
24
|
+
Valvat::Syntax.validate(self)
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_checksum?
|
28
|
+
Valvat::Checksum.validate(self)
|
29
|
+
end
|
30
|
+
|
12
31
|
def exists?(options = {})
|
13
32
|
Valvat::Lookup.validate(self, options)
|
14
33
|
end
|
15
34
|
alias exist? exists?
|
35
|
+
|
36
|
+
def iso_country_code
|
37
|
+
Valvat::Utils.vat_country_to_iso_country(vat_country_code)
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: Remove method / not in use
|
41
|
+
def european?
|
42
|
+
puts 'DEPRECATED: #european? is deprecated. Instead access Valvat::Utils::EU_MEMBER_STATES directly.'
|
43
|
+
|
44
|
+
Valvat::Utils::EU_MEMBER_STATES.include?(iso_country_code)
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_a
|
48
|
+
Valvat::Utils.split(raw)
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
raw
|
53
|
+
end
|
54
|
+
|
55
|
+
def inspect
|
56
|
+
"#<Valvat #{[raw, iso_country_code].compact.join(' ')}>"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def Valvat(vat) # rubocop:disable Naming/MethodName
|
61
|
+
vat.is_a?(Valvat) ? vat : Valvat.new(vat)
|
16
62
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,12 +8,13 @@ rescue LoadError
|
|
8
8
|
end
|
9
9
|
|
10
10
|
require "#{File.dirname(__FILE__)}/../lib/valvat.rb"
|
11
|
+
require 'webmock/rspec'
|
12
|
+
WebMock.allow_net_connect!
|
11
13
|
|
12
14
|
RSpec.configure do |config|
|
13
15
|
config.mock_with :rspec
|
14
16
|
config.filter_run focus: true
|
15
17
|
config.run_all_when_everything_filtered = true
|
16
|
-
config.backtrace_exclusion_patterns = [%r{rspec/(core|expectations)}]
|
17
18
|
end
|
18
19
|
|
19
20
|
I18n.enforce_available_locales = false if defined?(I18n)
|
@@ -4,7 +4,7 @@ require 'spec_helper'
|
|
4
4
|
|
5
5
|
describe Valvat::Checksum::ES do
|
6
6
|
%w[ESA13585625 ESB83871236 ESE54507058 ES25139013J ESQ1518001A ESQ5018001G ESX4942978W ESX7676464F ESB10317980
|
7
|
-
ESY3860557K ESY2207765D].each do |valid_vat|
|
7
|
+
ESY3860557K ESY2207765D ES28350472M ES41961720Z ESM1171170X ESK0928769Y].each do |valid_vat|
|
8
8
|
it "returns true on valid VAT #{valid_vat}" do
|
9
9
|
expect(Valvat::Checksum.validate(valid_vat)).to be(true)
|
10
10
|
end
|
@@ -15,4 +15,51 @@ describe Valvat::Checksum::ES do
|
|
15
15
|
expect(Valvat::Checksum.validate(invalid_vat)).to be(false)
|
16
16
|
end
|
17
17
|
end
|
18
|
+
|
19
|
+
describe 'if starts with [KLMXYZ\\d], is always a natural person' do
|
20
|
+
invalid_vat = 'ESX65474207'
|
21
|
+
it "returns false on invalid VAT #{invalid_vat}" do
|
22
|
+
expect(Valvat::Checksum.validate(invalid_vat)).to be(false)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'some CIF categories (NPQRSW) require control-digit to be a letter' do
|
27
|
+
invalid_vat = 'ESP65474207'
|
28
|
+
valid_vat = 'ESP6547420G'
|
29
|
+
|
30
|
+
it "returns false on invalid VAT #{invalid_vat}" do
|
31
|
+
expect(Valvat::Checksum.validate(invalid_vat)).to be(false)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "returns true on valid VAT #{valid_vat}" do
|
35
|
+
expect(Valvat::Checksum.validate(valid_vat)).to be(true)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'some CIF categories (CDFGJNUV) allow both a numeric check digit and a letter' do
|
40
|
+
%w[ESC65474207 ESC6547420G].each do |valid_vat|
|
41
|
+
it "returns true on valid VAT #{valid_vat}" do
|
42
|
+
expect(Valvat::Checksum.validate(valid_vat)).to be(true)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'applies special rules to validation' do
|
48
|
+
describe 'special NIF categories (KLM) require CD to be a letter and first two digits '\
|
49
|
+
'to be between 01 and 56 (inclusive)' do
|
50
|
+
%w[ESK8201230M ESK0001230B].each do |invalid_vat|
|
51
|
+
it "returns false on invalid VAT #{invalid_vat}" do
|
52
|
+
expect(Valvat::Checksum.validate(invalid_vat)).to be(false)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'arbitrarily invalid VATs' do
|
58
|
+
%w[ESX0000000T ES00000001R ES00000000T ES99999999R].each do |invalid_vat|
|
59
|
+
it "returns false on invalid VAT #{invalid_vat}" do
|
60
|
+
expect(Valvat::Checksum.validate(invalid_vat)).to be(false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
18
65
|
end
|
@@ -21,6 +21,7 @@ describe Valvat::Checksum::GB do
|
|
21
21
|
|
22
22
|
it 'is true for a new format valid vat' do
|
23
23
|
expect(Valvat::Checksum.validate('GB434031439')).to be true
|
24
|
+
expect(Valvat::Checksum.validate('GB727255821')).to be true
|
24
25
|
end
|
25
26
|
|
26
27
|
it 'is false for an old format VAT in forbidden group' do
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Valvat::Lookup::HMRC do
|
6
|
+
before do
|
7
|
+
stub_const('Valvat::Lookup::HMRC::ENDPOINT_URL', 'https://test-api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns hash with valid: true on success' do
|
11
|
+
response = described_class.new('GB553557881', { uk: true }).perform
|
12
|
+
|
13
|
+
expect(response).to match({
|
14
|
+
valid: true,
|
15
|
+
address: "131B Barton Hamlet\nSW97 5CK\nGB",
|
16
|
+
country_code: 'GB',
|
17
|
+
vat_number: '553557881',
|
18
|
+
name: 'Credite Sberger Donal Inc.',
|
19
|
+
request_date: kind_of(Time)
|
20
|
+
})
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'returns hash with valid: false on invalid input' do
|
24
|
+
response = described_class.new('GB123456789', { uk: true }).perform
|
25
|
+
expect(response).to match({ valid: false })
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns hash with valid: false on valid input with :uk option not set' do
|
29
|
+
response = described_class.new('GB553557881', {}).perform
|
30
|
+
expect(response).to match({ valid: false })
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe Valvat::Lookup::VIES do
|
6
|
+
it 'returns hash with valid: true on success' do
|
7
|
+
response = described_class.new('IE6388047V', {}).perform
|
8
|
+
|
9
|
+
expect(response).to match({
|
10
|
+
valid: true,
|
11
|
+
address: '3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4',
|
12
|
+
country_code: 'IE',
|
13
|
+
vat_number: '6388047V',
|
14
|
+
name: 'GOOGLE IRELAND LIMITED',
|
15
|
+
request_date: kind_of(Date)
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'returns hash with valid: false on invalid input' do
|
20
|
+
response = described_class.new('XC123123', {}).perform
|
21
|
+
expect(response.to_hash).to match({ valid: false, faultstring: 'INVALID_INPUT', faultcode: 'env:Server' })
|
22
|
+
end
|
23
|
+
end
|