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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e8469dde623b4fba82a31eeb1e18b71d6def4524028246af63ac9818d298529b
4
- data.tar.gz: '07599d80cff37cfce5f52ae2652241c299e8e09f6ef8c16082fa8b09ca69cd8b'
3
+ metadata.gz: 07de71a83be21df648e4865c840e533ef9e4c1a88ba874cb8840737226df37b9
4
+ data.tar.gz: 29580e05eeb6e02ad91ea9854bb3c5e6df74092e2381732da98d77ea4912cc25
5
5
  SHA512:
6
- metadata.gz: fc3dd4cf4b265689807e50602dbb9b3d72f16db6c10ad644a84468d077aba1eed2c6dc95930a6ec48983232231ea4704a53679fe2f780eeb98184379ee304009
7
- data.tar.gz: 35d0b10616341c4174d766f6f18ba40f432789fb0f83601e72e7dad7b8527c51c60e88cf851f50c25bad5c346b05ab5b0f9a82853226ebb514f0654d8ddf01bf
6
+ metadata.gz: 822844919a568bad7b2e576f101d123347455efc8b0fa5813edebf612e3160385923e016285d19cffe9d98fc6e6bd22620b2c8be245ef77cb98bf9810365bad3
7
+ data.tar.gz: 8b54c2f90276b904358187037d239798f8d0f404ed62cb75c74cfb6b28f8a49d91797912990510c9d232d08b7cd4929ea2f81a5d59de53f9596cb4284bed8e8b
checksums.yaml.gz.sig CHANGED
Binary file
@@ -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: true
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
- uk: false
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.merge(Utils.deep_symbolize_keys(options))
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
@@ -31,4 +31,6 @@ class Valvat
31
31
  UnknownLookupError = Class.new(LookupError)
32
32
 
33
33
  HTTPError = Class.new(LookupError)
34
+
35
+ AuthorizationError = Class.new(LookupError)
34
36
  end
@@ -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
@@ -2,6 +2,7 @@ it:
2
2
  errors:
3
3
  messages:
4
4
  invalid_vat: IVA non valido per %{country_adjective}
5
+ vies_down: "Impossibile convalidare la tua partita IVA: Il servizio VIES non è disponibile, riprova più tardi."
5
6
  valvat:
6
7
  country_adjectives:
7
8
  eu: Europa
@@ -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 < 5
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 > 5
50
+ raise if limit > @rate_limit
50
51
 
51
52
  fetch(uri, limit + 1)
52
53
  end
@@ -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
- ENDPOINT_URL = 'https://api.service.hmrc.gov.uk/organisations/vat/check-vat-number/lookup'
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.1.0+json'
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
- URI.parse(ENDPOINT_URL + endpoint)
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, HEADERS)
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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Valvat
4
- VERSION = '1.4.4'
4
+ VERSION = '2.0.1'
5
5
  end
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: 1.4.4
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-07-11 00:00:00.000000000 Z
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.2.7
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.2.7
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