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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +69 -3
- data/lib/revolut/client.rb +11 -10
- data/lib/revolut/http.rb +1 -1
- data/lib/revolut/resources/account.rb +4 -0
- data/lib/revolut/resources/resource.rb +7 -9
- data/lib/revolut/resources/simulation.rb +41 -0
- data/lib/revolut/resources/webhook.rb +26 -0
- data/lib/revolut/resources/webhook_event.rb +29 -0
- data/lib/revolut/version.rb +1 -1
- data/lib/revolut.rb +7 -4
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6f6c231899d0e5bfd5e675e2b911ccead17aa1df7c8f8e7e7475ba07ebfd4800
|
4
|
+
data.tar.gz: b15045344651b7a013b15dba27ac2ad19b868029fb85f642129fde6be5c5dd1e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c0de989febb683fff5dc5d823dd89285dac611192e4053222eccb304df0d8d9ae4b3fc0589a4e74bcf6c7e6180736f4712ac45e036a4f1262fead830c53fc07
|
7
|
+
data.tar.gz: 4d77b497a69a1936ffb28fe30b1f64534c8ea3b5cf94776f3b449d925d3b986b8c7c29ad8ebc9e30e964d64baa01456930a7dc274128a09c73709d0ae4e7ae47
|
data/Gemfile.lock
CHANGED
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.
|
data/lib/revolut/client.rb
CHANGED
@@ -9,26 +9,27 @@ module Revolut
|
|
9
9
|
token_duration
|
10
10
|
scope
|
11
11
|
authorize_redirect_uri
|
12
|
-
|
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
|
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::
|
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
|
data/lib/revolut/version.rb
CHANGED
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
|
-
|
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
|
+
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-
|
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
|