revolut-connect 0.1.4 → 0.1.6

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: 2ab4ede6e2971a49dea7cccc0a36ec45af6b87e9d9e4b239b7870eab34b7a278
4
- data.tar.gz: d253ad5ca5b9b40fb8a16989fa787a93fd199a6f01621ed626c8a18dda95eaef
3
+ metadata.gz: 6f6c231899d0e5bfd5e675e2b911ccead17aa1df7c8f8e7e7475ba07ebfd4800
4
+ data.tar.gz: b15045344651b7a013b15dba27ac2ad19b868029fb85f642129fde6be5c5dd1e
5
5
  SHA512:
6
- metadata.gz: 7d023304718166aadb50b682fa557c0844932c8ffb0a0757666e8e9c8e9135fa1aeee5ddf774504348641d12099d7ec274fa29db100f66fc88b898b88dd4b340
7
- data.tar.gz: 16fb000e2863c830732d031353895425fd1bde52faad6f04ebf1f60f57c8af4534221442ce12a4eb0ae209a135f36da7f6cd7a522dced82cfac35487ac3b3099
6
+ metadata.gz: 6c0de989febb683fff5dc5d823dd89285dac611192e4053222eccb304df0d8d9ae4b3fc0589a4e74bcf6c7e6180736f4712ac45e036a4f1262fead830c53fc07
7
+ data.tar.gz: 4d77b497a69a1936ffb28fe30b1f64534c8ea3b5cf94776f3b449d925d3b986b8c7c29ad8ebc9e30e964d64baa01456930a7dc274128a09c73709d0ae4e7ae47
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- revolut-connect (0.1.4)
4
+ revolut-connect (0.1.6)
5
5
  faraday (>= 2)
6
6
  faraday-retry (>= 2)
7
7
  jwt (>= 1)
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  |:-:|:-:|
6
6
  | [![Tests](https://github.com/moraki-finance/revolut-connect/actions/workflows/main.yml/badge.svg?branch=main)](https://github.com/moraki-finance/revolut-connect/actions/workflows/main.yml) | [![Codecov Coverage](https://codecov.io/github/moraki-finance/revolut-connect/graph/badge.svg?token=SKTT14JJGV)](https://codecov.io/github/moraki-finance/revolut-connect) |
7
7
 
8
- A lightweight API client for Revolut featuring authentication, permission scopes, token expiration and automatic renewal.
8
+ A lightweight API client for Revolut featuring authentication, permission scopes, token expiration and automatic renewal, webhooks, webhook events, and much more!
9
9
 
10
10
  Revolut docs: <https://developer.revolut.com/>
11
11
 
@@ -22,6 +22,8 @@ _:warning: For now this connector only supports the [Business API](https://devel
22
22
  - `Payment`
23
23
  - `Transaction`
24
24
  - `TransferReason`
25
+ - `Webhook`
26
+ - `Simulation`
25
27
 
26
28
  ## :construction: Roadmap
27
29
 
@@ -31,10 +33,8 @@ _:warning: For now this connector only supports the [Business API](https://devel
31
33
  - `ForeignExchange` resource
32
34
  - `PaymentDraft` resource
33
35
  - `PayoutLink` resource
34
- - `Simulation` resource
35
36
  - `TeamMember` resource
36
37
  - `Transfer` resource
37
- - `Webhooks` resource
38
38
 
39
39
  ### Merchants API
40
40
 
@@ -160,9 +160,25 @@ Revolut.configure do |config|
160
160
  # Optional: The JWT for an already exchanged token.
161
161
  # Used to preload an existing auth token so that you don't have to exchange / renew it again.
162
162
  config.auth_json = ENV["REVOLUT_AUTH_JSON"]
163
+
164
+ # Optional: The revolut api version used. Generally used to hit the webhooks API as it requires api_version 2.0.
165
+ # Default: "1.0".
166
+ config.api_version = ENV["REVOLUT_API_VERSION"]
163
167
  end
164
168
  ```
165
169
 
170
+ If you're setting the `auth_json` config, rembember to call `Revolut::Auth.load_from_env` right after the configuration is set so that the gem loads this JSON you just set:
171
+
172
+ ```rb
173
+ Revolute.configure do |config|
174
+ ...
175
+ config.auth_json = ENV.fetch("REVOLUT_AUTH_JSON", nil)
176
+ end
177
+
178
+ # Load the `auth_json` value.
179
+ Revolut::Auth.load_from_env
180
+ ```
181
+
166
182
  ### Resources
167
183
 
168
184
  #### Accounts
@@ -231,6 +247,56 @@ transaction = Revolut::Payment.retrieve(payment.id)
231
247
  deleted = Revolut::Payment.delete(transaction.id)
232
248
  ```
233
249
 
250
+ #### Simulations
251
+
252
+ <https://developer.revolut.com/docs/business/simulations>
253
+
254
+ ```rb
255
+ # Update a transaction
256
+ transaction = Revolut::Simulation.update_transaction("a6ea39d7-62c9-481c-8ba6-8a887a44c486", action: :complete)
257
+
258
+ # Top up an account
259
+ transaction = Revolut::Simulation.top_up_account("e042f1fe-f721-49cc-af82-db7a6c46944f",
260
+ amount: 100,
261
+ currency: "GBP",
262
+ reference: "Test Top-up",
263
+ state: "completed"
264
+ )
265
+ ```
266
+
267
+ #### Webhooks
268
+
269
+ <https://developer.revolut.com/docs/business/webhooks-v-2>
270
+
271
+ ```rb
272
+ # Create a webhook
273
+ webhook = Revolut::Webhook.create(
274
+ url: "https://www.example.com",
275
+ events: [
276
+ "TransactionCreated",
277
+ "PayoutLinkCreated"
278
+ ]
279
+ )
280
+
281
+ # List webhooks
282
+ webhooks = Revolut::Webhook.list
283
+
284
+ # Retrieve a webhook
285
+ webhook = Revolut::Webhook.retrieve(webhook.id)
286
+
287
+ # Update a webhook
288
+ webhook = Revolut::Webhook.update(webhook.id, url: "https://www.example.com/")
289
+
290
+ # Delete a webhook
291
+ deleted = Revolut::Webhook.delete(webhook.id)
292
+
293
+ # Rotate webhook secret
294
+ rotated = Revolut::Webhook.rotate_signing_secret(webhook.id)
295
+
296
+ # Retrieve list of failing events
297
+ failed_events = Revolut::Webhook.failed_events(webhook.id)
298
+ ```
299
+
234
300
  ## Development
235
301
 
236
302
  You can use `bin/console` to access an interactive console. This will preload environment variables from a `.env` file.
@@ -9,26 +9,27 @@ module Revolut
9
9
  token_duration
10
10
  scope
11
11
  authorize_redirect_uri
12
- base_uri
12
+ api_version
13
13
  environment
14
14
  request_timeout
15
15
  global_headers
16
16
  ].freeze
17
17
 
18
- attr_reader(*CONFIG_KEYS)
18
+ attr_reader(*CONFIG_KEYS, :base_uri)
19
19
 
20
- def self.instance
21
- @instance ||= new
22
- end
23
-
24
- private
25
-
26
- def initialize
20
+ def initialize(**attrs)
27
21
  CONFIG_KEYS.each do |key|
28
22
  # Set instance variables like api_type & access_token. Fall back to global config
29
23
  # if not present.
30
- instance_variable_set(:"@#{key}", Revolut.config.send(key))
24
+ instance_variable_set(:"@#{key}", attrs[key] || Revolut.config.send(key))
31
25
  end
26
+
27
+ @base_uri = (environment == :sandbox) ? "https://sandbox-b2b.revolut.com/api/#{api_version}/" : "https://b2b.revolut.com/api/#{api_version}/"
28
+ end
29
+
30
+ # Instance with all the defaults
31
+ def self.instance
32
+ @instance ||= new
32
33
  end
33
34
  end
34
35
  end
data/lib/revolut/http.rb CHANGED
@@ -73,8 +73,8 @@ module Revolut
73
73
  # Request middlewares
74
74
  f.request content_type
75
75
  f.request :retry, { # Retries a request after refreshing the token if we get an UnauthorizedError
76
- max: 1,
77
76
  exceptions: [Faraday::UnauthorizedError],
77
+ methods: Faraday::Retry::Middleware::IDEMPOTENT_METHODS + %i[post patch],
78
78
  retry_block: ->(env:, options:, retry_count:, exception:, will_retry_in:) {
79
79
  Revolut::Auth.refresh(force: true)
80
80
  env.request_headers = env.request_headers.merge("Authorization" => "Bearer #{Revolut::Auth.access_token}")
@@ -7,6 +7,10 @@ module Revolut
7
7
  "accounts"
8
8
  end
9
9
 
10
+ # Retrieves the bank details for a specific account.
11
+ #
12
+ # @param id [String] The ID of the account.
13
+ # @return [Array<Revolut::BankAccount>] An array of bank account objects.
10
14
  def self.bank_details(id)
11
15
  response = http_client.get("/#{resources_name}/#{id}/bank-details")
12
16
 
@@ -57,12 +57,12 @@ module Revolut
57
57
  @coerce_with ||= attrs
58
58
  end
59
59
 
60
- protected
61
-
62
60
  def http_client
63
61
  @http_client ||= Revolut::Client.instance
64
62
  end
65
63
 
64
+ protected
65
+
66
66
  def resource_name
67
67
  resources_name
68
68
  end
@@ -88,16 +88,10 @@ module Revolut
88
88
 
89
89
  def check_not_allowed
90
90
  method = caller(1..1).first.match(/`(\w+)'/)[1].to_sym
91
- raise Revolut::Error, "`#{method}` is not allowed on this resource" if not_allowed_to.include?(method) || only.any? && !only.include?(method)
91
+ raise Revolut::UnsupportedOperationError, "`#{method}` operation is not allowed on this resource" if not_allowed_to.include?(method) || only.any? && !only.include?(method)
92
92
  end
93
93
  end
94
94
 
95
- def to_json
96
- @_raw.to_json
97
- end
98
-
99
- protected
100
-
101
95
  def initialize(attrs = {})
102
96
  @_raw = attrs
103
97
 
@@ -121,5 +115,9 @@ module Revolut
121
115
 
122
116
  instance_variables.each { |iv| self.class.send(:attr_accessor, iv.to_s[1..].to_sym) }
123
117
  end
118
+
119
+ def to_json
120
+ @_raw.to_json
121
+ end
124
122
  end
125
123
  end
@@ -0,0 +1,41 @@
1
+ module Revolut
2
+ # Reference: https://developer.revolut.com/docs/business/counterparties
3
+ class Simulation < Resource
4
+ shallow
5
+
6
+ def self.resources_name
7
+ "sandbox"
8
+ end
9
+
10
+ # Updates a transaction in the sandbox environment.
11
+ #
12
+ # @param id [String] The ID of the transaction to update.
13
+ # @param action [Symbol] The action to perform on the transaction.
14
+ # @return [Revolut::Transaction] The updated transaction object.
15
+ # @raise [Revolut::UnsupportedOperationError] If the method is called in a non-sandbox environment or if the action is not supported.
16
+ def self.update_transaction(id, action:)
17
+ raise Revolut::UnsupportedOperationError, "#update_transaction is meant to be run only in sandbox environments" unless Revolut.sandbox?
18
+ raise Revolut::UnsupportedOperationError, "The action `#{action}` is not supported" unless %i[complete revert declined fail].include?(action)
19
+
20
+ response = http_client.post("/#{resources_name}/transactions/#{id}/#{action}")
21
+
22
+ Revolut::Transaction.new(response.body)
23
+ end
24
+
25
+ # Adds funds to the specified account in the sandbox environment.
26
+ #
27
+ # @param id [String] The ID of the account to top up.
28
+ # @param data [Hash] Additional data for the top-up request.
29
+ # @option data [Float] :amount The amount to top up the account by.
30
+ # @option data [String] :currency The currency of the top-up amount.
31
+ # @return [Revolut::Transaction] The transaction object representing the top-up.
32
+ # @raise [Revolut::UnsupportedOperationError] If the method is called outside of the sandbox environment.
33
+ def self.top_up_account(id, **data)
34
+ raise Revolut::UnsupportedOperationError, "#top_up_account is meant to be run only in sandbox environments" unless Revolut.sandbox?
35
+
36
+ response = http_client.post("/#{resources_name}/topup", data: data.merge(account_id: id))
37
+
38
+ Revolut::Transaction.new(response.body)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,26 @@
1
+ module Revolut
2
+ # Reference: https://developer.revolut.com/docs/business/counterparties
3
+ class Webhook < Resource
4
+ class << self
5
+ def resources_name
6
+ "webhooks"
7
+ end
8
+
9
+ def http_client
10
+ @http_client ||= Revolut::Client.new(api_version: "2.0")
11
+ end
12
+
13
+ def rotate_signing_secret(id, **data)
14
+ response = http_client.post("/#{resources_name}/#{id}/rotate-signing-secret", data:)
15
+
16
+ new(response.body)
17
+ end
18
+
19
+ def failed_events(id)
20
+ response = http_client.get("/#{resources_name}/#{id}/failed-events")
21
+
22
+ response.body.map(&Revolut::WebhookEvent)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ require "openssl"
2
+ require "digest"
3
+
4
+ module Revolut
5
+ class WebhookEvent < Resource
6
+ shallow # do not allow any resource operations on this resource
7
+
8
+ # Constructs a new instance of the WebhookEvent class from a request object and a signing secret.
9
+ #
10
+ # @param request [ActionDispatch::Request] The request object containing the webhook event data.
11
+ # @param signing_secret [String] The signing secret used to verify the signature of the webhook event.
12
+ # @return [WebhookEvent] A new instance of the WebhookEvent class.
13
+ # @raise [Revolut::SignatureVerificationError] If the signature verification fails.
14
+ def self.construct_from(request, signing_secret)
15
+ json = request.body.read
16
+ timestamp = request.headers["Revolut-Request-Timestamp"]
17
+ header_signatures = request.headers["Revolut-Signature"].split(",")
18
+ payload_to_sign = "v1.#{timestamp}.#{json}"
19
+ digest = OpenSSL::Digest.new("sha256")
20
+ signature_digest = "v1=" + OpenSSL::HMAC.hexdigest(digest, signing_secret, payload_to_sign)
21
+
22
+ if header_signatures.include? signature_digest
23
+ new(JSON.parse(json))
24
+ else
25
+ raise Revolut::SignatureVerificationError, "Signature verification failed"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Revolut
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
data/lib/revolut.rb CHANGED
@@ -17,12 +17,15 @@ module Revolut
17
17
 
18
18
  class NotImplementedError < Error; end
19
19
 
20
+ class SignatureVerificationError < Error; end
21
+
22
+ class UnsupportedOperationError < Error; end
23
+
20
24
  class Configuration
21
- attr_accessor :request_timeout, :global_headers, :environment, :token_duration, :scope, :auth_json
25
+ attr_accessor :request_timeout, :global_headers, :environment, :token_duration, :scope, :auth_json, :api_version
22
26
  attr_writer :client_id, :signing_key, :iss, :authorize_redirect_uri
23
- attr_reader :base_uri
24
27
 
25
- DEFAULT_BASE_URI = "https://sandbox-b2b.revolut.com/api/1.0/"
28
+ DEFAULT_API_VERSION = "1.0"
26
29
  DEFAULT_ENVIRONMENT = "sandbox"
27
30
  DEFAULT_REQUEST_TIMEOUT = 120
28
31
  DEFAULT_TOKEN_DURATION = 120 # 2 minutes
@@ -37,8 +40,8 @@ module Revolut
37
40
  @token_duration = ENV.fetch("REVOLUT_TOKEN_DURATION", DEFAULT_TOKEN_DURATION)
38
41
  @auth_json = ENV["REVOLUT_AUTH_JSON"]
39
42
  @scope = ENV["REVOLUT_SCOPE"]
43
+ @api_version = ENV.fetch("REVOLUT_API_VERSION", DEFAULT_API_VERSION)
40
44
  @environment = ENV.fetch("REVOLUT_ENVIRONMENT", DEFAULT_ENVIRONMENT).to_sym
41
- @base_uri = (environment == :sandbox) ? "https://sandbox-b2b.revolut.com/api/1.0/" : "https://b2b.revolut.com/api/1.0/"
42
45
  end
43
46
 
44
47
  def client_id
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: revolut-connect
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Mochetti
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-03-07 00:00:00.000000000 Z
11
+ date: 2024-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jwt
@@ -84,8 +84,11 @@ files:
84
84
  - lib/revolut/resources/counterparty.rb
85
85
  - lib/revolut/resources/payment.rb
86
86
  - lib/revolut/resources/resource.rb
87
+ - lib/revolut/resources/simulation.rb
87
88
  - lib/revolut/resources/transaction.rb
88
89
  - lib/revolut/resources/transfer_reason.rb
90
+ - lib/revolut/resources/webhook.rb
91
+ - lib/revolut/resources/webhook_event.rb
89
92
  - lib/revolut/version.rb
90
93
  - sig/revolut/connect.rbs
91
94
  homepage: https://github.com/moraki-finance/revolut-connect