valvat 1.4.4 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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