revolut-connect 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.sample +6 -0
- data/.env.test +5 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.standard.yml +3 -0
- data/.vscode/settings.json +11 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +25 -0
- data/Gemfile.lock +143 -0
- data/LICENSE.txt +21 -0
- data/README.md +236 -0
- data/Rakefile +10 -0
- data/lib/revolut/client.rb +32 -0
- data/lib/revolut/http.rb +117 -0
- data/lib/revolut/middlewares/catch_error.rb +17 -0
- data/lib/revolut/resources/account.rb +16 -0
- data/lib/revolut/resources/auth.rb +119 -0
- data/lib/revolut/resources/bank_account.rb +6 -0
- data/lib/revolut/resources/counterparty.rb +15 -0
- data/lib/revolut/resources/payment.rb +22 -0
- data/lib/revolut/resources/resource.rb +125 -0
- data/lib/revolut/resources/transaction.rb +13 -0
- data/lib/revolut/resources/transfer_reason.rb +9 -0
- data/lib/revolut/version.rb +5 -0
- data/lib/revolut.rb +79 -0
- data/sig/revolut/connect.rbs +6 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9267f44243fdcd4eb8506475f0766c8696e86c1ca1eff5d015b0023faed2ea10
|
4
|
+
data.tar.gz: b310c700d99c101e6f923a2066264a15b0b143d769b963d92df5e626c6021188
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3d3bf7d3d056bcf68e3199fd0d9f6264ba81301ae5dc9c503aa83d46f1de2172d74cb58fbded6c3c116479c6438ebf9b14488f76a7a2ddf8ab6173c4e870ad01
|
7
|
+
data.tar.gz: 6130b8b132106023f1f8aac3e924829e443119c4ec2b104403b9a2b38751142bd269997e103c3d635e72f32229f69b96c2490c0e03ca5197f2ac824bed003690
|
data/.env.sample
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
REVOLUT_CLIENT_ID={YOUR APP CLIENT ID}
|
2
|
+
REVOLUT_SIGNING_KEY="{YOUR APP SIGNING KEY IN ONE LINE JOINED BY NEW LINES (e.g: -----BEGIN PRIVATE KEY-----\n....)}"
|
3
|
+
REVOLUT_AUTHORIZE_REDIRECT_URI=https://example.com
|
4
|
+
REVOLUT_ISS=example.com
|
5
|
+
REVOLUT_ENVIRONMENT=sandbox
|
6
|
+
REVOLUT_AUTH_JSON="{PRELOADED AUTH SO THAT YOU CAN SKIP THE FIRST TIME AUTHORIZATION FLOW}"
|
data/.env.test
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.2.2
|
data/.standard.yml
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"rubyLsp.formatter": "none", // Use standard rbfmt instead
|
3
|
+
"editor.formatOnSave": true,
|
4
|
+
"editor.codeActionsOnSave": {
|
5
|
+
"source.organizeImports": "explicit"
|
6
|
+
},
|
7
|
+
"[ruby]": {
|
8
|
+
"editor.defaultFormatter": "testdouble.vscode-standard-ruby"
|
9
|
+
},
|
10
|
+
"standardRuby.autofix": true
|
11
|
+
}
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at martin.mochetti@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/Gemfile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in revolut-connect.gemspec
|
6
|
+
gemspec
|
7
|
+
|
8
|
+
gem "rake", "~> 13.0"
|
9
|
+
|
10
|
+
group :development, :test do
|
11
|
+
gem "byebug", "~> 11.1"
|
12
|
+
gem "pry", "~> 0.14.2"
|
13
|
+
gem "pry-byebug", "~> 3.10"
|
14
|
+
gem "pry-rescue", "~> 1.5"
|
15
|
+
gem "pry-stack_explorer", "~> 0.6.1"
|
16
|
+
gem "dotenv"
|
17
|
+
gem "standard", "~> 1.3"
|
18
|
+
end
|
19
|
+
|
20
|
+
group :test do
|
21
|
+
gem "rspec", "~> 3.0"
|
22
|
+
gem "webmock", "~> 3.23.0"
|
23
|
+
gem "simplecov"
|
24
|
+
gem "simplecov-cobertura"
|
25
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
revolut-connect (0.1.0)
|
5
|
+
faraday (>= 1)
|
6
|
+
faraday-retry (>= 1)
|
7
|
+
jwt (>= 1)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
addressable (2.8.6)
|
13
|
+
public_suffix (>= 2.0.2, < 6.0)
|
14
|
+
ast (2.4.2)
|
15
|
+
base64 (0.2.0)
|
16
|
+
bigdecimal (3.1.6)
|
17
|
+
binding_of_caller (1.0.0)
|
18
|
+
debug_inspector (>= 0.0.1)
|
19
|
+
byebug (11.1.3)
|
20
|
+
coderay (1.1.3)
|
21
|
+
crack (1.0.0)
|
22
|
+
bigdecimal
|
23
|
+
rexml
|
24
|
+
debug_inspector (1.2.0)
|
25
|
+
diff-lcs (1.5.1)
|
26
|
+
docile (1.4.0)
|
27
|
+
dotenv (3.1.0)
|
28
|
+
faraday (2.9.0)
|
29
|
+
faraday-net_http (>= 2.0, < 3.2)
|
30
|
+
faraday-net_http (3.1.0)
|
31
|
+
net-http
|
32
|
+
faraday-retry (2.2.0)
|
33
|
+
faraday (~> 2.0)
|
34
|
+
hashdiff (1.1.0)
|
35
|
+
interception (0.5)
|
36
|
+
json (2.7.1)
|
37
|
+
jwt (2.8.1)
|
38
|
+
base64
|
39
|
+
language_server-protocol (3.17.0.3)
|
40
|
+
lint_roller (1.1.0)
|
41
|
+
method_source (1.0.0)
|
42
|
+
net-http (0.4.1)
|
43
|
+
uri
|
44
|
+
parallel (1.24.0)
|
45
|
+
parser (3.3.0.5)
|
46
|
+
ast (~> 2.4.1)
|
47
|
+
racc
|
48
|
+
pry (0.14.2)
|
49
|
+
coderay (~> 1.1)
|
50
|
+
method_source (~> 1.0)
|
51
|
+
pry-byebug (3.10.1)
|
52
|
+
byebug (~> 11.0)
|
53
|
+
pry (>= 0.13, < 0.15)
|
54
|
+
pry-rescue (1.6.0)
|
55
|
+
interception (>= 0.5)
|
56
|
+
pry (>= 0.12.0)
|
57
|
+
pry-stack_explorer (0.6.1)
|
58
|
+
binding_of_caller (~> 1.0)
|
59
|
+
pry (~> 0.13)
|
60
|
+
public_suffix (5.0.4)
|
61
|
+
racc (1.7.3)
|
62
|
+
rainbow (3.1.1)
|
63
|
+
rake (13.1.0)
|
64
|
+
regexp_parser (2.9.0)
|
65
|
+
rexml (3.2.6)
|
66
|
+
rspec (3.13.0)
|
67
|
+
rspec-core (~> 3.13.0)
|
68
|
+
rspec-expectations (~> 3.13.0)
|
69
|
+
rspec-mocks (~> 3.13.0)
|
70
|
+
rspec-core (3.13.0)
|
71
|
+
rspec-support (~> 3.13.0)
|
72
|
+
rspec-expectations (3.13.0)
|
73
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
74
|
+
rspec-support (~> 3.13.0)
|
75
|
+
rspec-mocks (3.13.0)
|
76
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
77
|
+
rspec-support (~> 3.13.0)
|
78
|
+
rspec-support (3.13.1)
|
79
|
+
rubocop (1.61.0)
|
80
|
+
json (~> 2.3)
|
81
|
+
language_server-protocol (>= 3.17.0)
|
82
|
+
parallel (~> 1.10)
|
83
|
+
parser (>= 3.3.0.2)
|
84
|
+
rainbow (>= 2.2.2, < 4.0)
|
85
|
+
regexp_parser (>= 1.8, < 3.0)
|
86
|
+
rexml (>= 3.2.5, < 4.0)
|
87
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
88
|
+
ruby-progressbar (~> 1.7)
|
89
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
90
|
+
rubocop-ast (1.31.1)
|
91
|
+
parser (>= 3.3.0.4)
|
92
|
+
rubocop-performance (1.20.2)
|
93
|
+
rubocop (>= 1.48.1, < 2.0)
|
94
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
95
|
+
ruby-progressbar (1.13.0)
|
96
|
+
simplecov (0.22.0)
|
97
|
+
docile (~> 1.1)
|
98
|
+
simplecov-html (~> 0.11)
|
99
|
+
simplecov_json_formatter (~> 0.1)
|
100
|
+
simplecov-cobertura (2.1.0)
|
101
|
+
rexml
|
102
|
+
simplecov (~> 0.19)
|
103
|
+
simplecov-html (0.12.3)
|
104
|
+
simplecov_json_formatter (0.1.4)
|
105
|
+
standard (1.34.0)
|
106
|
+
language_server-protocol (~> 3.17.0.2)
|
107
|
+
lint_roller (~> 1.0)
|
108
|
+
rubocop (~> 1.60)
|
109
|
+
standard-custom (~> 1.0.0)
|
110
|
+
standard-performance (~> 1.3)
|
111
|
+
standard-custom (1.0.2)
|
112
|
+
lint_roller (~> 1.0)
|
113
|
+
rubocop (~> 1.50)
|
114
|
+
standard-performance (1.3.1)
|
115
|
+
lint_roller (~> 1.1)
|
116
|
+
rubocop-performance (~> 1.20.2)
|
117
|
+
unicode-display_width (2.5.0)
|
118
|
+
uri (0.13.0)
|
119
|
+
webmock (3.23.0)
|
120
|
+
addressable (>= 2.8.0)
|
121
|
+
crack (>= 0.3.2)
|
122
|
+
hashdiff (>= 0.4.0, < 2.0.0)
|
123
|
+
|
124
|
+
PLATFORMS
|
125
|
+
x86_64-darwin-23
|
126
|
+
|
127
|
+
DEPENDENCIES
|
128
|
+
byebug (~> 11.1)
|
129
|
+
dotenv
|
130
|
+
pry (~> 0.14.2)
|
131
|
+
pry-byebug (~> 3.10)
|
132
|
+
pry-rescue (~> 1.5)
|
133
|
+
pry-stack_explorer (~> 0.6.1)
|
134
|
+
rake (~> 13.0)
|
135
|
+
revolut-connect!
|
136
|
+
rspec (~> 3.0)
|
137
|
+
simplecov
|
138
|
+
simplecov-cobertura
|
139
|
+
standard (~> 1.3)
|
140
|
+
webmock (~> 3.23.0)
|
141
|
+
|
142
|
+
BUNDLED WITH
|
143
|
+
2.4.22
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Martin Mochetti
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,236 @@
|
|
1
|
+
# Revolut Connect
|
2
|
+
|
3
|
+
<a href="https://codecov.io/github/moraki-finance/revolut-connect" >
|
4
|
+
<img src="https://codecov.io/github/moraki-finance/revolut-connect/graph/badge.svg?token=SKTT14JJGV"/>
|
5
|
+
</a>
|
6
|
+
|
7
|
+
[![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)
|
8
|
+
|
9
|
+
A lightweight API connector for Revolut. Revolut docs: <https://developer.revolut.com/>
|
10
|
+
|
11
|
+
_:warning: The extracted API objects don't do input parameters validations. It's a simple faraday wrapper that allows you to send as many inputs as you want. The Revolut API might fail when passing a wrong set of parameters._
|
12
|
+
|
13
|
+
_:warning: For now this connector only supports the [Business API](https://developer.revolut.com/docs/business/business-api). Pull requests are welcomed to support other APIs._
|
14
|
+
|
15
|
+
## Supported APIs & Resources
|
16
|
+
|
17
|
+
### Business API
|
18
|
+
|
19
|
+
- `Account`
|
20
|
+
- `Counterparty`
|
21
|
+
- `Payment`
|
22
|
+
- `Transaction`
|
23
|
+
- `TransferReason`
|
24
|
+
|
25
|
+
## :construction: Roadmap
|
26
|
+
|
27
|
+
### Business API
|
28
|
+
|
29
|
+
- `Card` resource
|
30
|
+
- `ForeignExchange` resource
|
31
|
+
- `PaymentDraft` resource
|
32
|
+
- `PayoutLink` resource
|
33
|
+
- `Simulation` resource
|
34
|
+
- `TeamMember` resource
|
35
|
+
- `Transfer` resource
|
36
|
+
- `Webhooks` resource
|
37
|
+
|
38
|
+
### Merchants API
|
39
|
+
|
40
|
+
- Authentication
|
41
|
+
- Resources
|
42
|
+
|
43
|
+
### Open Banking API
|
44
|
+
|
45
|
+
- Authentication
|
46
|
+
- Resources
|
47
|
+
|
48
|
+
## Installation
|
49
|
+
|
50
|
+
Install the gem and add to the application's Gemfile by executing:
|
51
|
+
|
52
|
+
```bash
|
53
|
+
bundle add revolut-connect
|
54
|
+
```
|
55
|
+
|
56
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
57
|
+
|
58
|
+
```bash
|
59
|
+
gem install revolut-connect
|
60
|
+
```
|
61
|
+
|
62
|
+
## Usage
|
63
|
+
|
64
|
+
### First Time Authorization
|
65
|
+
|
66
|
+
1. Generate a certificate for your API integration and register the API into your Revolut business account by following [this tutorial](https://developer.revolut.com/docs/guides/manage-accounts/get-started/make-your-first-api-request#1-add-your-certificate).
|
67
|
+
|
68
|
+
2. From the step above, you'll need to copy the `client id`, the `private key` and the `iss` values that Revolut asks you to generate. We'll set these as environment variables as follows:
|
69
|
+
|
70
|
+
```text
|
71
|
+
REVOLUT_CLIENT_ID={YOUR APP CLIENT ID}
|
72
|
+
REVOLUT_SIGNING_KEY="{YOUR APP SIGNING KEY IN ONE LINE JOINED BY NEW LINES (e.g: -----BEGIN PRIVATE KEY-----\n....)}"
|
73
|
+
REVOLUT_ISS={YOUR ISS}
|
74
|
+
```
|
75
|
+
|
76
|
+
3. Set the Revolut authorization redirect URI. This URI is what Revolut will use to redirect to after the user has authorized the API to access the account. Revolut will redirect to this URL adding a `code` query param that we'll need to exchange for the first access token ([reference](https://developer.revolut.com/docs/guides/manage-accounts/get-started/make-your-first-api-request#3-consent-to-the-application)):
|
77
|
+
|
78
|
+
```text
|
79
|
+
REVOLUT_AUTHORIZE_REDIRECT_URI={YOUR REVOLUT AUTH HANDLING DOMAIN}
|
80
|
+
```
|
81
|
+
|
82
|
+
4. In revolut, after adding all the API details (uploading the certificate, iss, etc), enable the API. This will take you to the APP authorization consent form. After you authorize the app, you should be redirected to the domain you set in the configuration with the authorization `code` in query params. Copy this code.
|
83
|
+
|
84
|
+
![Screenshot 2024-03-01 at 6 45 45 AM](https://github.com/moraki-finance/revolut-connect/assets/3678598/94f3e3c0-143d-40e7-9f14-69d1ea4f68f5)
|
85
|
+
|
86
|
+
![Screenshot 2024-03-01 at 6 46 11 AM](https://github.com/moraki-finance/revolut-connect/assets/3678598/a9c2a55d-3e7d-420c-8d9d-c9617438856f)
|
87
|
+
|
88
|
+
5. Exchange the code for your first time access token:
|
89
|
+
|
90
|
+
```rb
|
91
|
+
revolut_auth = Revolut::Auth.exchange(authorization_code: "{CODE YOU COPIED IN PREVIOUS STEP}")
|
92
|
+
```
|
93
|
+
|
94
|
+
6. This will return a `Revolut::Auth` object with the access token in it. It's highly recommended that you persist this information somewhere so that it can later be loaded without needing to go through this code exchange process again:
|
95
|
+
|
96
|
+
```rb
|
97
|
+
auth_to_persist = revolut_auth.to_json # Persist this somewhere (database, redis, etc.). Remember to encrypt it if you persist it.
|
98
|
+
```
|
99
|
+
|
100
|
+
And then, when you need to load the auth again:
|
101
|
+
|
102
|
+
```rb
|
103
|
+
Revolut::Auth.load(auth_to_persist)
|
104
|
+
```
|
105
|
+
|
106
|
+
You can also store this json in an environment variable and the gem will auto load it:
|
107
|
+
|
108
|
+
```text
|
109
|
+
REVOLUT_AUTH_JSON={auth_to_persist}
|
110
|
+
```
|
111
|
+
|
112
|
+
:tada: You're all set to start using the API.
|
113
|
+
|
114
|
+
### Configuration
|
115
|
+
|
116
|
+
In rails applications, it's standard to provide an initializer (e.g `config/initializers/revolut.rb`) to load all the configuration settings of the gem. If you follow the previous step (First Time Authorization), you can do the following:
|
117
|
+
|
118
|
+
```rb
|
119
|
+
Revolut.configure do |config|
|
120
|
+
# Your app client id. Typically stored in the environment variables as it's a sensitive secret.
|
121
|
+
config.client_id = ENV["REVOLUT_CLIENT_ID"]
|
122
|
+
|
123
|
+
# Your app private key. Typically stored in the environment variables as it's a sensitive secret.
|
124
|
+
config.signing_key = ENV["REVOLUT_SIGNING_KEY"]
|
125
|
+
|
126
|
+
# The URI that Revolut will redirect to upon a successful authorization.
|
127
|
+
# Used to get the authorization code and exchange it for an access_token.
|
128
|
+
config.authorize_redirect_uri = ENV["REVOLUT_AUTHORIZE_REDIRECT_URI"]
|
129
|
+
|
130
|
+
# Optional: JWT issuer domain. Typically your app domain.
|
131
|
+
# Default: example.com
|
132
|
+
config.iss = ENV["REVOLUT_ISS"]
|
133
|
+
|
134
|
+
# Optional: Timeout of the underlying faraday requests.
|
135
|
+
# Default: 120
|
136
|
+
config.request_timeout = 120
|
137
|
+
|
138
|
+
# Optional: Set extra headers that will get attached to every revolut api request.
|
139
|
+
# Useful for observability tools like Helicone: https://www.helicone.ai/
|
140
|
+
# Default: {}
|
141
|
+
config.global_headers = {
|
142
|
+
"Helicone-Auth": "Bearer {HELICONE_API_KEY}"
|
143
|
+
"helicone-stream-force-format" => "true",
|
144
|
+
}
|
145
|
+
|
146
|
+
# Optional: Set the environment to be production or sandbox.
|
147
|
+
# Default: sandbox
|
148
|
+
config.environment = ENV["REVOLUT_ENVIRONMENT"]
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### Resources
|
153
|
+
|
154
|
+
#### Accounts
|
155
|
+
|
156
|
+
<https://developer.revolut.com/docs/business/accounts>
|
157
|
+
|
158
|
+
```rb
|
159
|
+
# List revolut accounts
|
160
|
+
accounts = Revolut::Account.list
|
161
|
+
|
162
|
+
# Retrieve a single account
|
163
|
+
account = Revolut::Account.retrieve(accounts.last.id)
|
164
|
+
|
165
|
+
# List bank accounts
|
166
|
+
bank_details = Revolut::Account.bank_details(accounts.last.id)
|
167
|
+
```
|
168
|
+
|
169
|
+
#### Counterparties
|
170
|
+
|
171
|
+
<https://developer.revolut.com/docs/business/counterparties>
|
172
|
+
|
173
|
+
```rb
|
174
|
+
# List counterparties
|
175
|
+
counterparties = Revolut::Counterparty.list
|
176
|
+
|
177
|
+
# Create a counterparty
|
178
|
+
created_counterparty = Revolut::Counterparty.create(
|
179
|
+
profile_type: "personal",
|
180
|
+
name: "John Smith",
|
181
|
+
revtag: "johnsmith"
|
182
|
+
)
|
183
|
+
|
184
|
+
# Retrieve a counterparty
|
185
|
+
retrieved_counterparty = Revolut::Counterparty.retrieve(created_counterparty.id)
|
186
|
+
|
187
|
+
# Delete a counterparty
|
188
|
+
deleted = Revolut::Counterparty.delete(retrieved_counterparty.id)
|
189
|
+
```
|
190
|
+
|
191
|
+
#### Payments
|
192
|
+
|
193
|
+
<https://developer.revolut.com/docs/business/create-payment>
|
194
|
+
|
195
|
+
```rb
|
196
|
+
# Create a payment transaction
|
197
|
+
payment = Revolut::Payment.create(
|
198
|
+
request_id: "49c6a48b-6b58-40a0-b974-0b8c4888c8a7", # Your app's own payment ID.
|
199
|
+
account_id: "af98333c-ea53-482b-93c2-1fa5e4eae671",
|
200
|
+
receiver: {
|
201
|
+
counterparty_id: "49c6a48b-6b58-40a0-b974-0b8c4888c8a7",
|
202
|
+
account_id: "9116f03a-c074-4585-b261-18a706b3768b"
|
203
|
+
},
|
204
|
+
amount: 1000.99,
|
205
|
+
charge_bearer: "debtor",
|
206
|
+
currency: "EUR",
|
207
|
+
reference: "To John Doe"
|
208
|
+
)
|
209
|
+
|
210
|
+
# List payment transactions
|
211
|
+
transactions = Revolut::Payment.list
|
212
|
+
|
213
|
+
# Retrieve a payment transaction
|
214
|
+
transaction = Revolut::Payment.retrieve(payment.id)
|
215
|
+
|
216
|
+
# Delete a payment transaction
|
217
|
+
deleted = Revolut::Payment.delete(transaction.id)
|
218
|
+
```
|
219
|
+
|
220
|
+
## Development
|
221
|
+
|
222
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
223
|
+
|
224
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
225
|
+
|
226
|
+
## Contributing
|
227
|
+
|
228
|
+
Bug reports and pull requests are welcome on GitHub at <https://github.com/moraki-finance/revolut-connect>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/moraki-finance/revolut-connect/blob/main/CODE_OF_CONDUCT.md).
|
229
|
+
|
230
|
+
## License
|
231
|
+
|
232
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
233
|
+
|
234
|
+
## Code of Conduct
|
235
|
+
|
236
|
+
Everyone interacting in the Revolut::Connect project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/revolut-connect/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Revolut
|
2
|
+
class Client
|
3
|
+
include Revolut::HTTP
|
4
|
+
|
5
|
+
CONFIG_KEYS = %i[
|
6
|
+
client_id
|
7
|
+
signing_key
|
8
|
+
iss
|
9
|
+
authorize_redirect_uri
|
10
|
+
base_uri
|
11
|
+
environment
|
12
|
+
request_timeout
|
13
|
+
global_headers
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
attr_reader(*CONFIG_KEYS)
|
17
|
+
|
18
|
+
def self.instance
|
19
|
+
@instance ||= new
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
CONFIG_KEYS.each do |key|
|
26
|
+
# Set instance variables like api_type & access_token. Fall back to global config
|
27
|
+
# if not present.
|
28
|
+
instance_variable_set(:"@#{key}", Revolut.config.send(key))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/revolut/http.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
module Revolut
|
4
|
+
module HTTP
|
5
|
+
def get(path, headers: {}, **query)
|
6
|
+
full_uri = uri(path:, query:)
|
7
|
+
|
8
|
+
conn.get(full_uri) do |req|
|
9
|
+
req.headers = all_headers(headers)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(path, data: {}, headers: {}, **query)
|
14
|
+
full_uri = uri(path:, query:)
|
15
|
+
|
16
|
+
conn.post(full_uri) do |req|
|
17
|
+
req.body = data.to_json if data.any?
|
18
|
+
req.headers = all_headers(headers)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def patch(path, data: {}, headers: {}, **query)
|
23
|
+
full_uri = uri(path:, query:)
|
24
|
+
|
25
|
+
conn.patch(full_uri) do |req|
|
26
|
+
req.body = data.to_json if data.any?
|
27
|
+
req.headers = all_headers(headers)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def delete(path, headers: {}, **query)
|
32
|
+
full_uri = uri(path:, query:)
|
33
|
+
|
34
|
+
conn.delete(full_uri) do |req|
|
35
|
+
req.headers = all_headers(headers)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_access_token(authorization_code:)
|
40
|
+
full_uri = uri(path: "auth/token")
|
41
|
+
|
42
|
+
conn(content_type: :url_encoded, authed: false).post(full_uri) do |req|
|
43
|
+
req.headers = global_headers
|
44
|
+
req.body = URI.encode_www_form({
|
45
|
+
grant_type: "authorization_code",
|
46
|
+
code: authorization_code,
|
47
|
+
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
48
|
+
client_assertion:
|
49
|
+
})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def refresh_access_token(refresh_token:)
|
54
|
+
full_uri = uri(path: "auth/token")
|
55
|
+
|
56
|
+
conn(content_type: :url_encoded, authed: false).post(full_uri) do |req|
|
57
|
+
req.headers = global_headers
|
58
|
+
req.body = URI.encode_www_form({
|
59
|
+
grant_type: "refresh_token",
|
60
|
+
refresh_token:,
|
61
|
+
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
|
62
|
+
client_assertion:
|
63
|
+
})
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def conn(content_type: :json, authed: true)
|
70
|
+
Faraday.new do |f|
|
71
|
+
f.options[:timeout] = request_timeout
|
72
|
+
|
73
|
+
# Request middlewares
|
74
|
+
f.request content_type
|
75
|
+
f.request :retry, { # Retries a request after refreshing the token if we get an UnauthorizedError
|
76
|
+
max: 1,
|
77
|
+
exceptions: [Faraday::UnauthorizedError],
|
78
|
+
retry_block: ->(env:, options:, retry_count:, exception:, will_retry_in:) {
|
79
|
+
Revolut::Auth.refresh(force: true)
|
80
|
+
env.request_headers = env.request_headers.merge("Authorization" => "Bearer #{Revolut::Auth.access_token}")
|
81
|
+
}
|
82
|
+
}
|
83
|
+
f.request :authorization, "Bearer", -> { Revolut::Auth.access_token } if authed
|
84
|
+
|
85
|
+
# Response middlewares
|
86
|
+
f.response :json
|
87
|
+
f.response ENV["CONSOLE"] ? :catch_error : :raise_error
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def uri(path:, query: {})
|
92
|
+
File.join(base_uri, path) + "?#{URI.encode_www_form(query)}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def all_headers(request_headers = {})
|
96
|
+
global_headers.merge(request_headers)
|
97
|
+
end
|
98
|
+
|
99
|
+
def client_assertion
|
100
|
+
private_key = OpenSSL::PKey::RSA.new(signing_key)
|
101
|
+
|
102
|
+
payload = {
|
103
|
+
iss:,
|
104
|
+
sub: client_id,
|
105
|
+
aud: "https://revolut.com",
|
106
|
+
exp: Time.now.to_i + 120 # Expires in 2 minutes
|
107
|
+
}
|
108
|
+
|
109
|
+
header = {
|
110
|
+
alg: "RS256",
|
111
|
+
typ: "JWT"
|
112
|
+
}
|
113
|
+
|
114
|
+
JWT.encode(payload, private_key, "RS256", header)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# Helper middleware only intended to be used in the console.
|
2
|
+
# The idea is to have a fast extraction of the API error message from the response body.
|
3
|
+
class CatchError < Faraday::Middleware
|
4
|
+
def on_complete(env)
|
5
|
+
raise_error_middleware.on_complete(env)
|
6
|
+
rescue Faraday::Error => e
|
7
|
+
raise e, JSON.parse(e.response[:body])["message"]
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def raise_error_middleware
|
13
|
+
@raise_error_middleware ||= Faraday::Response::RaiseError.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Faraday::Response.register_middleware catch_error: CatchError
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Revolut
|
2
|
+
# Reference: https://developer.revolut.com/docs/business/counterparties
|
3
|
+
class Account < Resource
|
4
|
+
not_allowed_to :create, :update, :delete
|
5
|
+
|
6
|
+
def self.resources_name
|
7
|
+
"accounts"
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.bank_details(id)
|
11
|
+
response = http_client.get("/#{resources_name}/#{id}/bank-details")
|
12
|
+
|
13
|
+
response.body.map(&Revolut::BankAccount)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require "jwt"
|
2
|
+
|
3
|
+
module Revolut
|
4
|
+
class Auth < Resource
|
5
|
+
class NotAuthorizedError < StandardError
|
6
|
+
def initialize
|
7
|
+
super(
|
8
|
+
"You need to authorize your app to access the Revolut business account of the user\n" \
|
9
|
+
"Please visit #{Revolut::Auth.authorize_url} to get an authorization code that you can then use with the Revolut::Auth.retrieve method to get an access token."
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :token_type, :refresh_token
|
16
|
+
attr_writer :access_token, :expires_at
|
17
|
+
|
18
|
+
# Generates the authorization URL for the Revolut API.
|
19
|
+
# Use this URI to redirect the user to Revolut's authorization page
|
20
|
+
# to authorize your app to access her business account.
|
21
|
+
#
|
22
|
+
# @return [String] The authorization URL.
|
23
|
+
def authorize_url
|
24
|
+
"#{authorize_base_uri}?client_id=#{Revolut.config.client_id}&redirect_uri=#{Revolut.config.authorize_redirect_uri}&response_type=code#authorise"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Exchanges the authorization code for an access token.
|
28
|
+
#
|
29
|
+
# @param authorization_code [String] The authorization code to retrieve the access token.
|
30
|
+
# @return [Auth] The newly created Revolut::Auth object.
|
31
|
+
def exchange(authorization_code:)
|
32
|
+
auth_json = http_client.get_access_token(authorization_code:).body
|
33
|
+
load(auth_json)
|
34
|
+
new(auth_json)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Loads authentication data from a JSON object.
|
38
|
+
#
|
39
|
+
# @param auth_json [Hash] The JSON object containing authentication data.
|
40
|
+
# @return [void]
|
41
|
+
def load(auth_json)
|
42
|
+
@access_token = auth_json["access_token"]
|
43
|
+
@token_type = auth_json["token_type"]
|
44
|
+
@expires_at = Time.now.to_i + auth_json["expires_in"]
|
45
|
+
@refresh_token = auth_json["refresh_token"]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the access token too access the Revolut API.
|
49
|
+
# Raises Revolut::Auth::NotAuthorizedError if the access token is not set.
|
50
|
+
# Refreshes the access token if it has expired.
|
51
|
+
#
|
52
|
+
# @return [String] The access token.
|
53
|
+
def access_token
|
54
|
+
# If there's no token set in the authorization class, it means that we're trying to
|
55
|
+
# access the API without having gone through the authorization process.
|
56
|
+
raise Revolut::Auth::NotAuthorizedError if @access_token.nil?
|
57
|
+
|
58
|
+
refresh if expired?
|
59
|
+
|
60
|
+
@access_token
|
61
|
+
end
|
62
|
+
|
63
|
+
# Refreshes the access token if it has expired or if force is set to true.
|
64
|
+
#
|
65
|
+
# @param force [Boolean] Whether to force refresh the access token.
|
66
|
+
# @return [Revolut::Resources::Auth] The refreshed authentication object.
|
67
|
+
def refresh(force: false)
|
68
|
+
return unless expired? || force
|
69
|
+
|
70
|
+
new(http_client.refresh_access_token(refresh_token:).body).tap do |refreshed_auth|
|
71
|
+
@access_token = refreshed_auth.access_token
|
72
|
+
@token_type = refreshed_auth.token_type
|
73
|
+
@expires_at = Time.now.to_i + refreshed_auth.expires_in
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the expiration date and time of the authentication token.
|
78
|
+
#
|
79
|
+
# @return [DateTime] The expiration date and time.
|
80
|
+
def expires_at
|
81
|
+
Time.at(@expires_at).utc.to_datetime
|
82
|
+
end
|
83
|
+
|
84
|
+
# Checks if the access_token has expired.
|
85
|
+
#
|
86
|
+
# Returns:
|
87
|
+
# - true if the access_token has expired
|
88
|
+
# - false otherwise
|
89
|
+
def expired?
|
90
|
+
expires_at && Time.now.to_i >= @expires_at
|
91
|
+
end
|
92
|
+
|
93
|
+
# Loads authentication information from environment variable REVOLUT_AUTH_JSON.
|
94
|
+
#
|
95
|
+
# If the access token is not already set and the environment variable REVOLUT_AUTH_JSON is present,
|
96
|
+
# this method loads the JSON data from the environment variable and calls the load method to set the authentication information.
|
97
|
+
#
|
98
|
+
# Example:
|
99
|
+
# auth.load_from_env
|
100
|
+
#
|
101
|
+
# @return [void]
|
102
|
+
def load_from_env
|
103
|
+
env_json = ENV["REVOLUT_AUTH_JSON"]
|
104
|
+
|
105
|
+
return unless @access_token.nil? && env_json
|
106
|
+
|
107
|
+
load(JSON.parse(env_json))
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def authorize_base_uri
|
113
|
+
Revolut.sandbox? ?
|
114
|
+
"https://sandbox-business.revolut.com/app-confirm" :
|
115
|
+
"https://business.revolut.com/app-confirm"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Revolut
|
2
|
+
# Reference: https://developer.revolut.com/docs/business/counterparties
|
3
|
+
class Counterparty < Resource
|
4
|
+
not_allowed_to :update
|
5
|
+
coerce_with accounts: Revolut::BankAccount
|
6
|
+
|
7
|
+
def self.resource_name
|
8
|
+
"counterparty"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.resources_name
|
12
|
+
"counterparties"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Revolut
|
4
|
+
class Payment < Resource
|
5
|
+
only :create
|
6
|
+
|
7
|
+
class << self
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
# Delegate list, retrieve, and delete to the transactions resource
|
11
|
+
def_delegators :transactions, :list, :retrieve, :delete
|
12
|
+
|
13
|
+
def resource_name
|
14
|
+
"pay"
|
15
|
+
end
|
16
|
+
|
17
|
+
def transactions
|
18
|
+
Revolut::Transaction
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Revolut
|
2
|
+
class Resource
|
3
|
+
class << self
|
4
|
+
def create(**attrs)
|
5
|
+
check_not_allowed
|
6
|
+
|
7
|
+
response = http_client.post("/#{resource_name}", data: attrs)
|
8
|
+
|
9
|
+
body = response.body
|
10
|
+
|
11
|
+
return body.map(&self) if body.is_a?(Array)
|
12
|
+
|
13
|
+
new(body)
|
14
|
+
end
|
15
|
+
|
16
|
+
def retrieve(id)
|
17
|
+
check_not_allowed
|
18
|
+
|
19
|
+
response = http_client.get("/#{resource_name}/#{id}")
|
20
|
+
|
21
|
+
new(response.body)
|
22
|
+
end
|
23
|
+
|
24
|
+
def update(id, **attrs)
|
25
|
+
check_not_allowed
|
26
|
+
|
27
|
+
response = http_client.patch("/#{resource_name}/#{id}", data: attrs)
|
28
|
+
|
29
|
+
new(response.body)
|
30
|
+
end
|
31
|
+
|
32
|
+
def list(**)
|
33
|
+
check_not_allowed
|
34
|
+
|
35
|
+
response = http_client.get("/#{resources_name}", **)
|
36
|
+
|
37
|
+
response.body.map(&self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(id)
|
41
|
+
check_not_allowed
|
42
|
+
|
43
|
+
http_client.delete("/#{resource_name}/#{id}")
|
44
|
+
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_proc
|
49
|
+
->(attrs) { new(attrs) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def skip_coertion_for(*attrs)
|
53
|
+
@skip_coertion_for ||= attrs
|
54
|
+
end
|
55
|
+
|
56
|
+
def coerce_with(**attrs)
|
57
|
+
@coerce_with ||= attrs
|
58
|
+
end
|
59
|
+
|
60
|
+
protected
|
61
|
+
|
62
|
+
def http_client
|
63
|
+
@http_client ||= Revolut::Client.instance
|
64
|
+
end
|
65
|
+
|
66
|
+
def resource_name
|
67
|
+
resources_name
|
68
|
+
end
|
69
|
+
|
70
|
+
def resources_name
|
71
|
+
raise Revolut::NotImplementedError, "Implement #resources_name in subclass"
|
72
|
+
end
|
73
|
+
|
74
|
+
def not_allowed_to(*attrs)
|
75
|
+
@not_allowed_to ||= attrs
|
76
|
+
end
|
77
|
+
|
78
|
+
def shallow
|
79
|
+
# Adding :shallow will make all other resource methods fail.
|
80
|
+
only :shallow
|
81
|
+
end
|
82
|
+
|
83
|
+
def only(*attrs)
|
84
|
+
@only ||= attrs
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def check_not_allowed
|
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)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def to_json
|
96
|
+
@_raw.to_json
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
|
101
|
+
def initialize(attrs = {})
|
102
|
+
@_raw = attrs
|
103
|
+
|
104
|
+
attrs.each do |key, value|
|
105
|
+
if self.class.skip_coertion_for.include?(key.to_sym)
|
106
|
+
instance_variable_set(:"@#{key}", value)
|
107
|
+
else
|
108
|
+
coerce_class = self.class.coerce_with[key.to_sym] || Revolut::Resource
|
109
|
+
coerced_value = if value.is_a?(Hash)
|
110
|
+
coerce_class.new(value)
|
111
|
+
elsif value.is_a?(Array)
|
112
|
+
value.map do |v|
|
113
|
+
v.is_a?(Hash) ? coerce_class.new(v) : v
|
114
|
+
end
|
115
|
+
else
|
116
|
+
value
|
117
|
+
end
|
118
|
+
instance_variable_set(:"@#{key}", coerced_value)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
instance_variables.each { |iv| self.class.send(:attr_accessor, iv.to_s[1..].to_sym) }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/revolut.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "faraday/retry"
|
5
|
+
|
6
|
+
require_relative "revolut/middlewares/catch_error"
|
7
|
+
require_relative "revolut/version"
|
8
|
+
require_relative "revolut/http"
|
9
|
+
require_relative "revolut/client"
|
10
|
+
require_relative "revolut/resources/resource"
|
11
|
+
Dir[File.join(__dir__, "revolut", "resources", "*.rb")].each { |file| require file }
|
12
|
+
|
13
|
+
# Load the authentication information from the environment variable REVOLUT_AUTH_JSON right away if possible.
|
14
|
+
Revolut::Auth.load_from_env
|
15
|
+
|
16
|
+
module Revolut
|
17
|
+
class Error < StandardError; end
|
18
|
+
|
19
|
+
class ConfigurationError < Error; end
|
20
|
+
|
21
|
+
class NotImplementedError < Error; end
|
22
|
+
|
23
|
+
class Configuration
|
24
|
+
attr_accessor :request_timeout, :global_headers, :environment
|
25
|
+
attr_writer :client_id, :signing_key, :iss, :authorize_redirect_uri
|
26
|
+
attr_reader :base_uri
|
27
|
+
|
28
|
+
DEFAULT_BASE_URI = "https://sandbox-b2b.revolut.com/api/1.0/"
|
29
|
+
DEFAULT_ENVIRONMENT = "sandbox"
|
30
|
+
DEFAULT_REQUEST_TIMEOUT = 120
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
@request_timeout = DEFAULT_REQUEST_TIMEOUT
|
34
|
+
@global_headers = {}
|
35
|
+
@client_id = ENV["REVOLUT_CLIENT_ID"]
|
36
|
+
@signing_key = ENV["REVOLUT_SIGNING_KEY"]&.gsub("\\n", "\n")
|
37
|
+
@iss = ENV.fetch("REVOLUT_ISS", "example.com")
|
38
|
+
@authorize_redirect_uri = ENV["REVOLUT_AUTHORIZE_REDIRECT_URI"]
|
39
|
+
@environment = ENV.fetch("REVOLUT_ENVIRONMENT", DEFAULT_ENVIRONMENT).to_sym
|
40
|
+
@base_uri = (environment == :sandbox) ? "https://sandbox-b2b.revolut.com/api/1.0/" : "https://b2b.revolut.com/api/1.0/"
|
41
|
+
end
|
42
|
+
|
43
|
+
def client_id
|
44
|
+
@client_id || (raise ConfigurationError, "Revolut client_id missing!")
|
45
|
+
end
|
46
|
+
|
47
|
+
def signing_key
|
48
|
+
@signing_key || (raise ConfigurationError, "Revolut signing_key missing!")
|
49
|
+
end
|
50
|
+
|
51
|
+
def iss
|
52
|
+
@iss || (raise ConfigurationError, "Revolut iss missing!")
|
53
|
+
end
|
54
|
+
|
55
|
+
def authorize_redirect_uri
|
56
|
+
@authorize_redirect_uri || (raise ConfigurationError, "Revolut authorize_redirect_uri missing!")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class << self
|
61
|
+
attr_writer :config
|
62
|
+
|
63
|
+
def config
|
64
|
+
@config ||= Revolut::Configuration.new
|
65
|
+
end
|
66
|
+
|
67
|
+
def env
|
68
|
+
config.environment
|
69
|
+
end
|
70
|
+
|
71
|
+
def sandbox?
|
72
|
+
env == :sandbox
|
73
|
+
end
|
74
|
+
|
75
|
+
def configure
|
76
|
+
yield(config)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: revolut-connect
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Mochetti
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: jwt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faraday-retry
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1'
|
55
|
+
description: Revolut API connector for Ruby. This gem is not official and is not supported
|
56
|
+
by Revolut. Use at your own risk.
|
57
|
+
email:
|
58
|
+
- martin.mochetti@gmail.com
|
59
|
+
executables: []
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".env.sample"
|
64
|
+
- ".env.test"
|
65
|
+
- ".rspec"
|
66
|
+
- ".ruby-version"
|
67
|
+
- ".standard.yml"
|
68
|
+
- ".vscode/settings.json"
|
69
|
+
- CHANGELOG.md
|
70
|
+
- CODE_OF_CONDUCT.md
|
71
|
+
- Gemfile
|
72
|
+
- Gemfile.lock
|
73
|
+
- LICENSE.txt
|
74
|
+
- README.md
|
75
|
+
- Rakefile
|
76
|
+
- lib/revolut.rb
|
77
|
+
- lib/revolut/client.rb
|
78
|
+
- lib/revolut/http.rb
|
79
|
+
- lib/revolut/middlewares/catch_error.rb
|
80
|
+
- lib/revolut/resources/account.rb
|
81
|
+
- lib/revolut/resources/auth.rb
|
82
|
+
- lib/revolut/resources/bank_account.rb
|
83
|
+
- lib/revolut/resources/counterparty.rb
|
84
|
+
- lib/revolut/resources/payment.rb
|
85
|
+
- lib/revolut/resources/resource.rb
|
86
|
+
- lib/revolut/resources/transaction.rb
|
87
|
+
- lib/revolut/resources/transfer_reason.rb
|
88
|
+
- lib/revolut/version.rb
|
89
|
+
- sig/revolut/connect.rbs
|
90
|
+
homepage: https://github.com/moraki-finance/revolut-connect
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata:
|
94
|
+
homepage_uri: https://github.com/moraki-finance/revolut-connect
|
95
|
+
source_code_uri: https://github.com/moraki-finance/revolut-connect
|
96
|
+
changelog_uri: https://github.com/moraki-finance/revolut-connect/blob/main/CHANGELOG.md
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 3.0.0
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubygems_version: 3.2.3
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Revolut non-official API connector
|
116
|
+
test_files: []
|