valvat 1.4.4 → 2.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/valvat/configuration.rb +15 -3
- data/lib/valvat/error.rb +2 -0
- data/lib/valvat/hmrc/access_token.rb +102 -0
- data/lib/valvat/locales/it.yml +1 -0
- data/lib/valvat/lookup/base.rb +3 -2
- data/lib/valvat/lookup/hmrc.rb +20 -6
- data/lib/valvat/options.rb +15 -0
- data/lib/valvat/utils.rb +5 -0
- data/lib/valvat/version.rb +1 -1
- data.tar.gz.sig +0 -0
- metadata +5 -4
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 07de71a83be21df648e4865c840e533ef9e4c1a88ba874cb8840737226df37b9
|
4
|
+
data.tar.gz: 29580e05eeb6e02ad91ea9854bb3c5e6df74092e2381732da98d77ea4912cc25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 822844919a568bad7b2e576f101d123347455efc8b0fa5813edebf612e3160385923e016285d19cffe9d98fc6e6bd22620b2c8be245ef77cb98bf9810365bad3
|
7
|
+
data.tar.gz: 8b54c2f90276b904358187037d239798f8d0f404ed62cb75c74cfb6b28f8a49d91797912990510c9d232d08b7cd4929ea2f81a5d59de53f9596cb4284bed8e8b
|
checksums.yaml.gz.sig
CHANGED
Binary file
|
data/lib/valvat/configuration.rb
CHANGED
@@ -7,7 +7,7 @@ class Valvat
|
|
7
7
|
# Configuration options should be set by passing a hash:
|
8
8
|
#
|
9
9
|
# Valvat.configure(
|
10
|
-
# uk:
|
10
|
+
# uk: { client_id: '<client_id>', client_secret: '<client_secret>' }
|
11
11
|
# )
|
12
12
|
#
|
13
13
|
def self.configure(options)
|
@@ -42,7 +42,15 @@ class Valvat
|
|
42
42
|
|
43
43
|
# Use lookup via HMRC for VAT numbers from the UK
|
44
44
|
# if set to false lookup will always return false for UK VAT numbers
|
45
|
-
|
45
|
+
# HMRC options:
|
46
|
+
# :client_id and :client_secret API credentials for OAuth 2.0 Authentication
|
47
|
+
# :sandbox Use sandboxed instead of production API (defaults to false)
|
48
|
+
# See more details https://developer.service.hmrc.gov.uk/api-documentation/docs/development-practices
|
49
|
+
#
|
50
|
+
uk: false,
|
51
|
+
|
52
|
+
# Rate limit for Lookup and HMRC authentication requests
|
53
|
+
rate_limit: 5
|
46
54
|
}.freeze
|
47
55
|
|
48
56
|
def self.initialize
|
@@ -53,8 +61,12 @@ class Valvat
|
|
53
61
|
@data[key]
|
54
62
|
end
|
55
63
|
|
64
|
+
def dig(*keys)
|
65
|
+
@data.dig(*keys)
|
66
|
+
end
|
67
|
+
|
56
68
|
def configure(options)
|
57
|
-
@data = @data
|
69
|
+
@data = Utils.deep_merge(@data, Utils.deep_symbolize_keys(options))
|
58
70
|
end
|
59
71
|
|
60
72
|
def initialize
|
data/lib/valvat/error.rb
CHANGED
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class Valvat
|
7
|
+
module HMRC
|
8
|
+
class AccessToken
|
9
|
+
Error = Class.new(StandardError)
|
10
|
+
|
11
|
+
PRODUCTION_ENDPOINT_URL = 'https://api.service.hmrc.gov.uk/oauth/token'
|
12
|
+
SANDBOX_ENDPOINT_URL = 'https://test-api.service.hmrc.gov.uk/oauth/token'
|
13
|
+
SCOPE = 'read:vat'
|
14
|
+
GRANT_TYPE = 'client_credentials'
|
15
|
+
|
16
|
+
def initialize(options = {})
|
17
|
+
uk_options = options[:uk].is_a?(Hash) ? options[:uk] : {}
|
18
|
+
|
19
|
+
@client_id = uk_options[:client_id].to_s
|
20
|
+
@client_secret = uk_options[:client_secret].to_s
|
21
|
+
@rate_limit = options[:rate_limit]
|
22
|
+
@endpoint_uri = URI(uk_options[:sandbox] ? SANDBOX_ENDPOINT_URL : PRODUCTION_ENDPOINT_URL)
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.fetch(options = {})
|
26
|
+
new(options).fetch
|
27
|
+
end
|
28
|
+
|
29
|
+
def fetch(uri = @endpoint_uri, request_count = 0)
|
30
|
+
raise_if_invalid!
|
31
|
+
|
32
|
+
request = build_request(uri)
|
33
|
+
response = build_https.request(request)
|
34
|
+
handle_response!(response, request_count)
|
35
|
+
rescue Errno::ECONNRESET, IOError
|
36
|
+
raise if request_count > @rate_limit
|
37
|
+
|
38
|
+
fetch(uri, request_count + 1)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def raise_if_invalid!
|
44
|
+
raise Error, 'Client ID is missing' if @client_id.empty?
|
45
|
+
raise Error, 'Client secret is missing' if @client_secret.empty?
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_request(uri)
|
49
|
+
request = Net::HTTP::Post.new(uri)
|
50
|
+
request['Content-Type'] = 'application/x-www-form-urlencoded'
|
51
|
+
request.body = build_body
|
52
|
+
request
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_body
|
56
|
+
URI.encode_www_form(
|
57
|
+
scope: SCOPE,
|
58
|
+
grant_type: GRANT_TYPE,
|
59
|
+
client_id: @client_id,
|
60
|
+
client_secret: @client_secret
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def build_https
|
65
|
+
https = Net::HTTP.new(@endpoint_uri.host, @endpoint_uri.port)
|
66
|
+
https.use_ssl = true
|
67
|
+
https
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_response!(response)
|
71
|
+
JSON.parse(response.read_body)
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_response_location(response)
|
75
|
+
URI.parse(response['Location'])
|
76
|
+
end
|
77
|
+
|
78
|
+
# See https://developer.service.hmrc.gov.uk/api-documentation/docs/authorisation/application-restricted-endpoints
|
79
|
+
def handle_response!(response, request_count)
|
80
|
+
if response.is_a?(Net::HTTPRedirection) && request_count < @rate_limit
|
81
|
+
fetch(parse_response_location(response), request_count + 1)
|
82
|
+
elsif response.code == '200'
|
83
|
+
# {"access_token":"<access_token>","token_type":"bearer","expires_in":14400,"scope":"read:vat"}
|
84
|
+
parse_response!(response)['access_token']
|
85
|
+
else
|
86
|
+
raise Error, "Failed to fetch access token: #{handle_response_error(response, request_count)}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def handle_response_error(response, request_count)
|
91
|
+
if response.code.match?(/4\d\d/)
|
92
|
+
# For 4xx codes: {"error":"invalid_client","error_description":"invalid client id or secret"}
|
93
|
+
parse_response!(response)['error_description']
|
94
|
+
elsif request_count >= @rate_limit
|
95
|
+
'rate limit exceeded'
|
96
|
+
else
|
97
|
+
response.read_body
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/lib/valvat/locales/it.yml
CHANGED
data/lib/valvat/lookup/base.rb
CHANGED
@@ -10,6 +10,7 @@ class Valvat
|
|
10
10
|
@vat = Valvat(vat)
|
11
11
|
@options = Valvat::Options(options)
|
12
12
|
@requester = @options[:requester] && Valvat(@options[:requester])
|
13
|
+
@rate_limit = @options[:rate_limit]
|
13
14
|
end
|
14
15
|
|
15
16
|
def perform
|
@@ -40,13 +41,13 @@ class Valvat
|
|
40
41
|
def fetch(uri, limit = 0)
|
41
42
|
response = send_request(uri)
|
42
43
|
|
43
|
-
if response == Net::HTTPRedirection && limit <
|
44
|
+
if response == Net::HTTPRedirection && limit < @rate_limit
|
44
45
|
fetch(URI.parse(response['Location']), limit + 1)
|
45
46
|
else
|
46
47
|
response
|
47
48
|
end
|
48
49
|
rescue Errno::ECONNRESET, IOError
|
49
|
-
raise if limit >
|
50
|
+
raise if limit > @rate_limit
|
50
51
|
|
51
52
|
fetch(uri, limit + 1)
|
52
53
|
end
|
data/lib/valvat/lookup/hmrc.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'base'
|
4
|
+
require_relative '../hmrc/access_token'
|
4
5
|
require 'net/http'
|
5
6
|
require 'json'
|
6
7
|
require 'time'
|
@@ -8,16 +9,19 @@ require 'time'
|
|
8
9
|
class Valvat
|
9
10
|
class Lookup
|
10
11
|
class HMRC < Base
|
11
|
-
|
12
|
+
PRODUCTION_ENDPOINT_URL = 'https://api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup'
|
13
|
+
SANDBOX_ENDPOINT_URL = 'https://test-api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup'
|
12
14
|
HEADERS = {
|
13
15
|
# https://developer.service.hmrc.gov.uk/api-documentation/docs/reference-guide#versioning
|
14
|
-
'Accept' => 'application/vnd.hmrc.
|
16
|
+
'Accept' => 'application/vnd.hmrc.2.0+json'
|
15
17
|
}.freeze
|
16
18
|
|
17
19
|
def perform
|
18
|
-
return { valid: false } unless @options[:uk]
|
20
|
+
return { valid: false } unless @options[:uk].is_a?(Hash)
|
19
21
|
|
20
22
|
parse(fetch(endpoint_uri).body)
|
23
|
+
rescue Valvat::HMRC::AccessToken::Error => e
|
24
|
+
{ error: e }
|
21
25
|
end
|
22
26
|
|
23
27
|
private
|
@@ -25,11 +29,12 @@ class Valvat
|
|
25
29
|
def endpoint_uri
|
26
30
|
endpoint = "/#{@vat.to_s_wo_country}"
|
27
31
|
endpoint += "/#{@requester.to_s_wo_country}" if @requester
|
28
|
-
|
32
|
+
endpoint_url = @options.dig(:uk, :sandbox) ? SANDBOX_ENDPOINT_URL : PRODUCTION_ENDPOINT_URL
|
33
|
+
URI.parse(endpoint_url + endpoint)
|
29
34
|
end
|
30
35
|
|
31
36
|
def build_request(uri)
|
32
|
-
Net::HTTP::Get.new(uri.request_uri,
|
37
|
+
Net::HTTP::Get.new(uri.request_uri, build_headers!)
|
33
38
|
end
|
34
39
|
|
35
40
|
def parse(body)
|
@@ -72,7 +77,8 @@ class Valvat
|
|
72
77
|
'SCHEDULED_MAINTENANCE' => ServiceUnavailable,
|
73
78
|
'SERVER_ERROR' => ServiceUnavailable,
|
74
79
|
'INVALID_REQUEST' => InvalidRequester,
|
75
|
-
'GATEWAY_TIMEOUT' => Timeout
|
80
|
+
'GATEWAY_TIMEOUT' => Timeout,
|
81
|
+
'INVALID_CREDENTIALS' => AuthorizationError
|
76
82
|
}.freeze
|
77
83
|
|
78
84
|
def build_fault(raw)
|
@@ -82,6 +88,14 @@ class Valvat
|
|
82
88
|
exception = FAULTS[fault] || UnknownLookupError
|
83
89
|
{ error: exception.new("#{fault}#{raw['message'] ? " (#{raw['message']})" : ''}", self.class) }
|
84
90
|
end
|
91
|
+
|
92
|
+
def build_headers!
|
93
|
+
HEADERS.merge('Authorization' => "Bearer #{fetch_access_token!}")
|
94
|
+
end
|
95
|
+
|
96
|
+
def fetch_access_token!
|
97
|
+
@fetch_access_token ||= Valvat::HMRC::AccessToken.fetch(@options)
|
98
|
+
end
|
85
99
|
end
|
86
100
|
end
|
87
101
|
end
|
data/lib/valvat/options.rb
CHANGED
@@ -18,11 +18,26 @@ class Valvat
|
|
18
18
|
@options[key] ||= @options[deprecated]
|
19
19
|
end
|
20
20
|
end
|
21
|
+
|
22
|
+
check_uk_key(silence)
|
21
23
|
end
|
22
24
|
|
23
25
|
def [](key)
|
24
26
|
@options.key?(key) ? @options[key] : Valvat.config[key]
|
25
27
|
end
|
28
|
+
|
29
|
+
def dig(*keys)
|
30
|
+
@options.dig(*keys).nil? ? Valvat.config.dig(*keys) : @options.dig(*keys)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def check_uk_key(silence)
|
36
|
+
return if @options[:uk] != true || silence
|
37
|
+
|
38
|
+
puts 'DEPRECATED: The option :uk is not allowed to be set to `true` anymore. ' \
|
39
|
+
'Instead it needs to be set to your HMRC API authentication credentials.'
|
40
|
+
end
|
26
41
|
end
|
27
42
|
|
28
43
|
def self.Options(options)
|
data/lib/valvat/utils.rb
CHANGED
@@ -45,5 +45,10 @@ class Valvat
|
|
45
45
|
val.is_a?(Hash) ? deep_symbolize_keys(val) : val
|
46
46
|
end
|
47
47
|
end
|
48
|
+
|
49
|
+
def self.deep_merge(original_hash, hash_to_merge)
|
50
|
+
merger = proc { |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : v2 }
|
51
|
+
original_hash.merge(hash_to_merge, &merger)
|
52
|
+
end
|
48
53
|
end
|
49
54
|
end
|
data/lib/valvat/version.rb
CHANGED
data.tar.gz.sig
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: valvat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastian Munz
|
@@ -34,7 +34,7 @@ cert_chain:
|
|
34
34
|
4+Gn3/xNnr5M7CdCGOG96P+gnx508xxiuMas8SvHl0/WB4kFpSGTH3uKzFh9fF+I
|
35
35
|
36KG/gudYiTEZqWbjFIh4jJceFclxM/L
|
36
36
|
-----END CERTIFICATE-----
|
37
|
-
date: 2024-
|
37
|
+
date: 2024-12-19 00:00:00.000000000 Z
|
38
38
|
dependencies:
|
39
39
|
- !ruby/object:Gem::Dependency
|
40
40
|
name: rexml
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 3.
|
45
|
+
version: 3.3.6
|
46
46
|
- - "<"
|
47
47
|
- !ruby/object:Gem::Version
|
48
48
|
version: 4.0.0
|
@@ -52,7 +52,7 @@ dependencies:
|
|
52
52
|
requirements:
|
53
53
|
- - ">="
|
54
54
|
- !ruby/object:Gem::Version
|
55
|
-
version: 3.
|
55
|
+
version: 3.3.6
|
56
56
|
- - "<"
|
57
57
|
- !ruby/object:Gem::Version
|
58
58
|
version: 4.0.0
|
@@ -93,6 +93,7 @@ files:
|
|
93
93
|
- lib/valvat/checksum/si.rb
|
94
94
|
- lib/valvat/configuration.rb
|
95
95
|
- lib/valvat/error.rb
|
96
|
+
- lib/valvat/hmrc/access_token.rb
|
96
97
|
- lib/valvat/local.rb
|
97
98
|
- lib/valvat/locales/bg.yml
|
98
99
|
- lib/valvat/locales/ca.yml
|
metadata.gz.sig
CHANGED
Binary file
|