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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +40 -0
  3. data/.rubocop.yml +212 -0
  4. data/.ruby-version +1 -1
  5. data/CHANGELOG.md +4 -0
  6. data/Gemfile +2 -0
  7. data/README.md +2 -0
  8. data/Rakefile +7 -3
  9. data/bunq-client.gemspec +22 -20
  10. data/lib/bunq/attachment.rb +15 -0
  11. data/lib/bunq/attachment_public.rb +13 -0
  12. data/lib/bunq/attachment_public_content.rb +4 -2
  13. data/lib/bunq/attachment_publics.rb +20 -0
  14. data/lib/bunq/attachments.rb +20 -0
  15. data/lib/bunq/avatar.rb +15 -0
  16. data/lib/bunq/avatars.rb +15 -0
  17. data/lib/bunq/bunq.rb +2 -0
  18. data/lib/bunq/bunqme_tab.rb +17 -0
  19. data/lib/bunq/bunqme_tabs.rb +21 -0
  20. data/lib/bunq/card.rb +2 -0
  21. data/lib/bunq/cards.rb +3 -1
  22. data/lib/bunq/certificate_pinned.rb +10 -6
  23. data/lib/bunq/client.rb +74 -51
  24. data/lib/bunq/device_servers.rb +5 -4
  25. data/lib/bunq/draft_share_invite_bank.rb +2 -0
  26. data/lib/bunq/draft_share_invite_banks.rb +3 -1
  27. data/lib/bunq/encryptor.rb +6 -7
  28. data/lib/bunq/errors.rb +9 -2
  29. data/lib/bunq/header.rb +20 -0
  30. data/lib/bunq/installation.rb +2 -0
  31. data/lib/bunq/installations.rb +4 -2
  32. data/lib/bunq/monetary_account.rb +17 -0
  33. data/lib/bunq/monetary_account_bank.rb +17 -0
  34. data/lib/bunq/monetary_account_banks.rb +21 -0
  35. data/lib/bunq/monetary_accounts.rb +3 -1
  36. data/lib/bunq/notification_filter_url.rb +4 -2
  37. data/lib/bunq/paginated.rb +14 -3
  38. data/lib/bunq/payment.rb +2 -0
  39. data/lib/bunq/payments.rb +3 -1
  40. data/lib/bunq/qr_code_content.rb +5 -3
  41. data/lib/bunq/resource.rb +25 -11
  42. data/lib/bunq/session_servers.rb +5 -2
  43. data/lib/bunq/signature.rb +9 -7
  44. data/lib/bunq/user.rb +17 -0
  45. data/lib/bunq/user_company.rb +2 -0
  46. data/lib/bunq/user_person.rb +2 -0
  47. data/lib/bunq/users.rb +17 -0
  48. data/lib/bunq/version.rb +3 -1
  49. metadata +53 -20
  50. data/.travis.yml +0 -5
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bunq
2
4
  class Installation
3
5
  def initialize(client, id)
@@ -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, "/v1/installation")
6
+ @resource = Bunq::Resource.new(client, '/v1/installation')
5
7
  end
6
8
 
7
9
  def create(public_key)
8
- fail ArgumentError.new('public_key is required') unless public_key
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("/monetary-account")
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("/notification-filter-url")
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
@@ -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.new('Cant pass both older_id and newer_id') if older_id && newer_id
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
- raise StopIteration if last_page
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
- next_params = params.merge(:"#{paging_id(params)}" => param(paging_id(params), pagination[paging_url(params)])) unless last_page
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bunq
2
4
  # https://doc.bunq.com/api/1/call/payment
3
5
  class Payment
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("/payment")
9
+ @resource = parent_resource.append('/payment')
8
10
  end
9
11
 
10
12
  # https://doc.bunq.com/api/1/call/payment/method/list
@@ -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("/qr-code-content")
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 { |response| response.body }
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
- attr_reader :resource
8
- NO_PARAMS = {}
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
- def post(payload, skip_verify: false, encrypt: false, &block)
25
- body = JSON.generate(payload)
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['X-Bunq-Client-Request-Id'] = SecureRandom.uuid
86
+ headers[Bunq::Header::CLIENT_REQUEST_ID] = SecureRandom.uuid
83
87
 
84
88
  unless @path.end_with?('/installation') && verb == 'POST'
85
- headers['X-Bunq-Client-Signature'] = sign_request(verb, params, headers, payload)
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(verb, params, headers, payload = nil)
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, &block)
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
@@ -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, "/v1/session-server")
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
- @resource.post(secret: @api_key)['Response']
16
+
17
+ @resource.post({secret: @api_key})['Response']
15
18
  end
16
19
  end
17
20
  end
@@ -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.new('private_key is mandatory') unless private_key
11
- fail ArgumentError.new('server_public_key is mandatory') unless server_public_key
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
- _header_name = header_name.to_s.downcase
52
- _header_name.start_with?(BUNQ_HEADER_PREFIX) && _header_name != BUNQ_SERVER_SIGNATURE_RESPONSE_HEADER
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(responseCode)
56
- (Bunq::configuration.sandbox && responseCode == 409) || responseCode == 429
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, %Q{#{response.code}\n#{sorted_bunq_headers.join("\n")}\n\n#{response.body}})
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'resource'
2
4
  require_relative 'draft_share_invite_bank'
3
5