bunq-client 0.7.2 → 1.0.0
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 +4 -4
- data/.github/workflows/rspec.yml +40 -0
- data/.rubocop.yml +212 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +4 -0
- data/Gemfile +2 -0
- data/README.md +2 -0
- data/Rakefile +7 -3
- data/bunq-client.gemspec +22 -20
- data/lib/bunq/attachment.rb +15 -0
- data/lib/bunq/attachment_public.rb +13 -0
- data/lib/bunq/attachment_public_content.rb +4 -2
- data/lib/bunq/attachment_publics.rb +20 -0
- data/lib/bunq/attachments.rb +20 -0
- data/lib/bunq/avatar.rb +15 -0
- data/lib/bunq/avatars.rb +15 -0
- data/lib/bunq/bunq.rb +2 -0
- data/lib/bunq/bunqme_tab.rb +17 -0
- data/lib/bunq/bunqme_tabs.rb +21 -0
- data/lib/bunq/card.rb +2 -0
- data/lib/bunq/cards.rb +3 -1
- data/lib/bunq/certificate_pinned.rb +10 -6
- data/lib/bunq/client.rb +74 -51
- data/lib/bunq/device_servers.rb +5 -4
- data/lib/bunq/draft_share_invite_bank.rb +2 -0
- data/lib/bunq/draft_share_invite_banks.rb +3 -1
- data/lib/bunq/encryptor.rb +6 -7
- data/lib/bunq/errors.rb +9 -2
- data/lib/bunq/header.rb +20 -0
- data/lib/bunq/installation.rb +2 -0
- data/lib/bunq/installations.rb +4 -2
- data/lib/bunq/monetary_account.rb +17 -0
- data/lib/bunq/monetary_account_bank.rb +17 -0
- data/lib/bunq/monetary_account_banks.rb +21 -0
- data/lib/bunq/monetary_accounts.rb +3 -1
- data/lib/bunq/notification_filter_url.rb +4 -2
- data/lib/bunq/paginated.rb +14 -3
- data/lib/bunq/payment.rb +2 -0
- data/lib/bunq/payments.rb +3 -1
- data/lib/bunq/qr_code_content.rb +5 -3
- data/lib/bunq/resource.rb +25 -11
- data/lib/bunq/session_servers.rb +5 -2
- data/lib/bunq/signature.rb +9 -7
- data/lib/bunq/user.rb +17 -0
- data/lib/bunq/user_company.rb +2 -0
- data/lib/bunq/user_person.rb +2 -0
- data/lib/bunq/users.rb +17 -0
- data/lib/bunq/version.rb +3 -1
- metadata +53 -20
- data/.travis.yml +0 -5
data/lib/bunq/header.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bunq
|
4
|
+
module Header
|
5
|
+
ACCEPT = 'Accept'
|
6
|
+
ATTACHMENT_DESCRIPTION = 'X-Bunq-Attachment-Description'
|
7
|
+
CACHE_CONTROL = 'Cache-Control'
|
8
|
+
CONTENT_TYPE = 'Content-Type'
|
9
|
+
CLIENT_AUTH = 'X-Bunq-Client-Authentication'
|
10
|
+
CLIENT_ENCRYPTION_HMAC = 'X-Bunq-Client-Encryption-Hmac'
|
11
|
+
CLIENT_ENCRYPTION_IV = 'X-Bunq-Client-Encryption-Iv'
|
12
|
+
CLIENT_ENCRYPTION_KEY = 'X-Bunq-Client-Encryption-Key'
|
13
|
+
CLIENT_REQUEST_ID = 'X-Bunq-Client-Request-Id'
|
14
|
+
CLIENT_SIGNATURE = 'X-Bunq-Client-Signature'
|
15
|
+
GEOLOCATION = 'X-Bunq-Geolocation'
|
16
|
+
LANGUAGE = 'X-Bunq-Language'
|
17
|
+
REGION = 'X-Bunq-Region'
|
18
|
+
USER_AGENT = 'User-Agent'
|
19
|
+
end
|
20
|
+
end
|
data/lib/bunq/installation.rb
CHANGED
data/lib/bunq/installations.rb
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bunq
|
2
4
|
class Installations
|
3
5
|
def initialize(client)
|
4
|
-
@resource = Bunq::Resource.new(client,
|
6
|
+
@resource = Bunq::Resource.new(client, '/v1/installation')
|
5
7
|
end
|
6
8
|
|
7
9
|
def create(public_key)
|
8
|
-
fail ArgumentError
|
10
|
+
fail ArgumentError, 'public_key is required' unless public_key
|
9
11
|
|
10
12
|
@resource.post({client_public_key: public_key}, skip_verify: true)['Response']
|
11
13
|
end
|
@@ -1,4 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'attachments'
|
1
4
|
require_relative 'notification_filter_url'
|
5
|
+
require_relative 'bunqme_tab'
|
6
|
+
require_relative 'bunqme_tabs'
|
2
7
|
|
3
8
|
module Bunq
|
4
9
|
##
|
@@ -8,6 +13,18 @@ module Bunq
|
|
8
13
|
@resource = parent_resource.append("/monetary-account/#{id}")
|
9
14
|
end
|
10
15
|
|
16
|
+
def attachments
|
17
|
+
Bunq::Attachments.new(@resource)
|
18
|
+
end
|
19
|
+
|
20
|
+
def bunqme_tab(id)
|
21
|
+
Bunq::BunqmeTab.new(@resource, id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def bunqme_tabs
|
25
|
+
Bunq::BunqmeTabs.new(@resource)
|
26
|
+
end
|
27
|
+
|
11
28
|
def payment(id)
|
12
29
|
Bunq::Payment.new(@resource, id)
|
13
30
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bunq
|
4
|
+
class MonetaryAccountBank
|
5
|
+
def initialize(parent_resource, id)
|
6
|
+
@resource = parent_resource.append("/monetary-account-bank/#{id}")
|
7
|
+
end
|
8
|
+
|
9
|
+
def show
|
10
|
+
@resource.with_session { @resource.get }['Response']
|
11
|
+
end
|
12
|
+
|
13
|
+
def update(attributes)
|
14
|
+
@resource.with_session { @resource.put(attributes) }['Response']
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'paginated'
|
4
|
+
|
5
|
+
module Bunq
|
6
|
+
class MonetaryAccountBanks
|
7
|
+
def initialize(parent_resource)
|
8
|
+
@resource = parent_resource.append('/monetary-account-bank')
|
9
|
+
end
|
10
|
+
|
11
|
+
def index(count: 200, older_id: nil, newer_id: nil)
|
12
|
+
Bunq::Paginated
|
13
|
+
.new(@resource)
|
14
|
+
.paginate(count: count, older_id: older_id, newer_id: newer_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create(attributes)
|
18
|
+
@resource.with_session { @resource.post(attributes) }['Response']
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bunq
|
2
4
|
##
|
3
5
|
# https://doc.bunq.com/api/1/call/monetary-account
|
4
6
|
class MonetaryAccounts
|
5
7
|
def initialize(parent_resource)
|
6
|
-
@resource = parent_resource.append(
|
8
|
+
@resource = parent_resource.append('/monetary-account')
|
7
9
|
end
|
8
10
|
|
9
11
|
# https://doc.bunq.com/api/1/call/monetary-account-bank/method/list
|
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bunq
|
2
4
|
# https://doc.bunq.com/#/notification-filter-url
|
3
5
|
class NotificationFilterUrl
|
4
6
|
def initialize(parent_resource)
|
5
|
-
@resource = parent_resource.append(
|
7
|
+
@resource = parent_resource.append('/notification-filter-url')
|
6
8
|
end
|
7
9
|
|
8
10
|
def create(notification_filters)
|
9
|
-
@resource.with_session { @resource.post({notification_filters: notification_filters})}['Response']
|
11
|
+
@resource.with_session { @resource.post({notification_filters: notification_filters}) }['Response']
|
10
12
|
end
|
11
13
|
|
12
14
|
def show
|
data/lib/bunq/paginated.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'errors'
|
2
4
|
|
3
5
|
module Bunq
|
@@ -16,7 +18,7 @@ module Bunq
|
|
16
18
|
private
|
17
19
|
|
18
20
|
def setup_params(count, older_id, newer_id)
|
19
|
-
fail ArgumentError
|
21
|
+
fail ArgumentError, 'Cant pass both older_id and newer_id' if older_id && newer_id
|
20
22
|
|
21
23
|
params = {count: count}
|
22
24
|
params[:older_id] = older_id if older_id
|
@@ -30,7 +32,7 @@ module Bunq
|
|
30
32
|
|
31
33
|
Enumerator.new do |yielder|
|
32
34
|
loop do
|
33
|
-
|
35
|
+
fail StopIteration if last_page
|
34
36
|
|
35
37
|
result = @resource.with_session { @resource.get(next_params) }
|
36
38
|
result['Response'].each do |item|
|
@@ -41,18 +43,27 @@ module Bunq
|
|
41
43
|
fail MissingPaginationObject unless pagination
|
42
44
|
|
43
45
|
last_page = !pagination[paging_url(params)]
|
44
|
-
|
46
|
+
next if last_page
|
47
|
+
|
48
|
+
next_params = params.merge(
|
49
|
+
"#{paging_id(params)}": param(
|
50
|
+
paging_id(params),
|
51
|
+
pagination[paging_url(params)],
|
52
|
+
),
|
53
|
+
)
|
45
54
|
end
|
46
55
|
end
|
47
56
|
end
|
48
57
|
|
49
58
|
def paging_url(params)
|
50
59
|
return 'newer_url' if params[:newer_id]
|
60
|
+
|
51
61
|
'older_url'
|
52
62
|
end
|
53
63
|
|
54
64
|
def paging_id(params)
|
55
65
|
return 'newer_id' if params[:newer_id]
|
66
|
+
|
56
67
|
'older_id'
|
57
68
|
end
|
58
69
|
|
data/lib/bunq/payment.rb
CHANGED
data/lib/bunq/payments.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'paginated'
|
2
4
|
|
3
5
|
module Bunq
|
4
6
|
# https://doc.bunq.com/api/1/call/payment
|
5
7
|
class Payments
|
6
8
|
def initialize(parent_resource)
|
7
|
-
@resource = parent_resource.append(
|
9
|
+
@resource = parent_resource.append('/payment')
|
8
10
|
end
|
9
11
|
|
10
12
|
# https://doc.bunq.com/api/1/call/payment/method/list
|
data/lib/bunq/qr_code_content.rb
CHANGED
@@ -1,12 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bunq
|
2
4
|
class QrCodeContent
|
3
5
|
def initialize(parent_resource)
|
4
|
-
@resource = parent_resource.append(
|
6
|
+
@resource = parent_resource.append('/qr-code-content')
|
5
7
|
end
|
6
8
|
|
7
9
|
def show
|
8
|
-
@resource.with_session do
|
9
|
-
@resource.get
|
10
|
+
@resource.with_session do
|
11
|
+
@resource.get(&:body)
|
10
12
|
end
|
11
13
|
end
|
12
14
|
end
|
data/lib/bunq/resource.rb
CHANGED
@@ -1,11 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'errors'
|
2
4
|
require 'restclient'
|
3
5
|
require 'json'
|
4
6
|
|
5
7
|
module Bunq
|
6
8
|
class Resource
|
7
|
-
|
8
|
-
|
9
|
+
APPLICATION_JSON = 'application/json'
|
10
|
+
|
11
|
+
NO_PARAMS = {}.freeze
|
9
12
|
|
10
13
|
def initialize(client, path)
|
11
14
|
@client = client
|
@@ -20,12 +23,13 @@ module Bunq
|
|
20
23
|
raise Bunq::Timeout
|
21
24
|
end
|
22
25
|
|
23
|
-
|
24
|
-
|
25
|
-
body =
|
26
|
+
def post(payload, skip_verify: false, encrypt: false, custom_headers: {}, &block)
|
27
|
+
custom_headers = JSON.parse(custom_headers.to_json)
|
28
|
+
body = post_body(payload, custom_headers)
|
26
29
|
body, headers = client.encryptor.encrypt(body) if encrypt
|
30
|
+
headers = headers.to_h.merge(custom_headers)
|
27
31
|
|
28
|
-
headers = bunq_request_headers('POST', NO_PARAMS, body, headers
|
32
|
+
headers = bunq_request_headers('POST', NO_PARAMS, body, headers)
|
29
33
|
|
30
34
|
resource.post(body, headers) do |response, request, result|
|
31
35
|
if skip_verify
|
@@ -74,26 +78,27 @@ module Bunq
|
|
74
78
|
x[:user] = client.configuration.sandbox_user
|
75
79
|
x[:password] = client.configuration.sandbox_password
|
76
80
|
end
|
77
|
-
end
|
81
|
+
end,
|
78
82
|
)
|
79
83
|
end
|
80
84
|
|
81
85
|
def bunq_request_headers(verb, params, payload = nil, headers = {})
|
82
|
-
headers[
|
86
|
+
headers[Bunq::Header::CLIENT_REQUEST_ID] = SecureRandom.uuid
|
83
87
|
|
84
88
|
unless @path.end_with?('/installation') && verb == 'POST'
|
85
|
-
headers[
|
89
|
+
headers[Bunq::Header::CLIENT_SIGNATURE] = sign_request(verb, params, headers, payload)
|
86
90
|
end
|
87
91
|
|
88
92
|
headers
|
89
93
|
end
|
90
94
|
|
91
|
-
def sign_request(
|
95
|
+
def sign_request(_verb, _params, _headers, payload = nil)
|
92
96
|
client.signature.create(payload)
|
93
97
|
end
|
94
98
|
|
95
99
|
def encode_params(path, params)
|
96
100
|
return path if params.empty?
|
101
|
+
|
97
102
|
"#{path}?#{URI.escape(params.collect { |k, v| "#{k}=#{v}" }.join('&'))}"
|
98
103
|
end
|
99
104
|
|
@@ -109,7 +114,7 @@ module Bunq
|
|
109
114
|
(100..499).include?(response.code)
|
110
115
|
end
|
111
116
|
|
112
|
-
def handle_response(response, _request, _result
|
117
|
+
def handle_response(response, _request, _result)
|
113
118
|
if response.code == 200 || response.code == 201
|
114
119
|
if block_given?
|
115
120
|
yield(response)
|
@@ -128,5 +133,14 @@ module Bunq
|
|
128
133
|
fail UnexpectedResponse.new(code: response.code, headers: response.raw_headers, body: response.body)
|
129
134
|
end
|
130
135
|
end
|
136
|
+
|
137
|
+
def post_body(payload, custom_headers)
|
138
|
+
if custom_headers.key?(Bunq::Header::CONTENT_TYPE) &&
|
139
|
+
custom_headers[Bunq::Header::CONTENT_TYPE] != APPLICATION_JSON
|
140
|
+
payload
|
141
|
+
else
|
142
|
+
JSON.generate(payload)
|
143
|
+
end
|
144
|
+
end
|
131
145
|
end
|
132
146
|
end
|
data/lib/bunq/session_servers.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Bunq
|
2
4
|
##
|
3
5
|
# https://doc.bunq.com/api/1/call/session-server
|
4
6
|
class SessionServers
|
5
7
|
def initialize(client)
|
6
|
-
@resource = Bunq::Resource.new(client,
|
8
|
+
@resource = Bunq::Resource.new(client, '/v1/session-server')
|
7
9
|
@api_key = client.configuration.api_key
|
8
10
|
end
|
9
11
|
|
@@ -11,7 +13,8 @@ module Bunq
|
|
11
13
|
# https://doc.bunq.com/api/1/call/session-server/method/post
|
12
14
|
def create
|
13
15
|
fail 'Cannot create session, please add the api_key to your configuration' unless @api_key
|
14
|
-
|
16
|
+
|
17
|
+
@resource.post({secret: @api_key})['Response']
|
15
18
|
end
|
16
19
|
end
|
17
20
|
end
|
data/lib/bunq/signature.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'errors'
|
2
4
|
|
3
5
|
module Bunq
|
@@ -7,8 +9,8 @@ module Bunq
|
|
7
9
|
BUNQ_SERVER_SIGNATURE_RESPONSE_HEADER = 'X-Bunq-Server-Signature'.downcase
|
8
10
|
|
9
11
|
def initialize(private_key, server_public_key)
|
10
|
-
fail ArgumentError
|
11
|
-
fail ArgumentError
|
12
|
+
fail ArgumentError, 'private_key is mandatory' unless private_key
|
13
|
+
fail ArgumentError, 'server_public_key is mandatory' unless server_public_key
|
12
14
|
|
13
15
|
@private_key = OpenSSL::PKey::RSA.new(private_key)
|
14
16
|
@server_public_key = OpenSSL::PKey::RSA.new(server_public_key)
|
@@ -48,12 +50,12 @@ module Bunq
|
|
48
50
|
end
|
49
51
|
|
50
52
|
def verifiable_header?(header_name, _)
|
51
|
-
|
52
|
-
|
53
|
+
the_header_name = header_name.to_s.downcase
|
54
|
+
the_header_name.start_with?(BUNQ_HEADER_PREFIX) && the_header_name != BUNQ_SERVER_SIGNATURE_RESPONSE_HEADER
|
53
55
|
end
|
54
56
|
|
55
|
-
def skip_signature_check(
|
56
|
-
(Bunq
|
57
|
+
def skip_signature_check(response_code)
|
58
|
+
(Bunq.configuration.sandbox && response_code == 409) || response_code == 429
|
57
59
|
end
|
58
60
|
|
59
61
|
def verify_legacy(signature, response)
|
@@ -66,7 +68,7 @@ module Bunq
|
|
66
68
|
"#{k.to_s.split('-').map(&:capitalize).join('-')}: #{v.first}"
|
67
69
|
end
|
68
70
|
|
69
|
-
verify(signature, %
|
71
|
+
verify(signature, %(#{response.code}\n#{sorted_bunq_headers.join("\n")}\n\n#{response.body}))
|
70
72
|
end
|
71
73
|
|
72
74
|
def verify_modern(signature, response)
|
data/lib/bunq/user.rb
CHANGED
@@ -1,12 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative 'resource'
|
2
4
|
require_relative 'monetary_account'
|
3
5
|
require_relative 'monetary_accounts'
|
6
|
+
require_relative 'monetary_account_bank'
|
7
|
+
require_relative 'monetary_account_banks'
|
4
8
|
require_relative 'draft_share_invite_bank'
|
5
9
|
require_relative 'draft_share_invite_banks'
|
6
10
|
require_relative 'certificate_pinned'
|
7
11
|
require_relative 'card'
|
8
12
|
require_relative 'cards'
|
9
13
|
require_relative 'notification_filter_url'
|
14
|
+
require_relative 'attachment'
|
10
15
|
|
11
16
|
module Bunq
|
12
17
|
class User
|
@@ -14,6 +19,10 @@ module Bunq
|
|
14
19
|
@resource = Bunq::Resource.new(client, "/v1/user/#{id}")
|
15
20
|
end
|
16
21
|
|
22
|
+
def attachment(id)
|
23
|
+
Bunq::Attachment.new(@resource, id)
|
24
|
+
end
|
25
|
+
|
17
26
|
def monetary_account(id)
|
18
27
|
Bunq::MonetaryAccount.new(@resource, id)
|
19
28
|
end
|
@@ -22,6 +31,14 @@ module Bunq
|
|
22
31
|
Bunq::MonetaryAccounts.new(@resource)
|
23
32
|
end
|
24
33
|
|
34
|
+
def monetary_account_bank(id)
|
35
|
+
Bunq::MonetaryAccountBank.new(@resource, id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def monetary_account_banks
|
39
|
+
Bunq::MonetaryAccountBanks.new(@resource)
|
40
|
+
end
|
41
|
+
|
25
42
|
def draft_share_invite_bank(id)
|
26
43
|
Bunq::DraftShareInviteBank.new(@resource, id)
|
27
44
|
end
|