modulr-api 0.0.5 → 0.0.11

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_devengo.yml +18 -15
  3. data/Gemfile.lock +51 -4
  4. data/Guardfile +50 -0
  5. data/README.md +103 -12
  6. data/doc/modulr_requests/accounts/close.http +4 -0
  7. data/doc/modulr_requests/accounts/create.http +21 -0
  8. data/doc/modulr_requests/http-client.env.json +8 -0
  9. data/doc/modulr_requests/transactions/history.http +4 -0
  10. data/lib/modulr/api/accounts_service.rb +19 -3
  11. data/lib/modulr/api/customers_service.rb +12 -0
  12. data/lib/modulr/api/notifications_service.rb +32 -0
  13. data/lib/modulr/api/payments_service.rb +42 -17
  14. data/lib/modulr/api/service.rb +4 -0
  15. data/lib/modulr/api/services.rb +13 -0
  16. data/lib/modulr/api/transactions_service.rb +12 -0
  17. data/lib/modulr/client.rb +11 -15
  18. data/lib/modulr/error.rb +45 -0
  19. data/lib/modulr/resources/accounts/account.rb +4 -4
  20. data/lib/modulr/resources/accounts/identifier.rb +4 -4
  21. data/lib/modulr/resources/accounts/identifiers.rb +1 -1
  22. data/lib/modulr/resources/base.rb +14 -2
  23. data/lib/modulr/resources/{collection.rb → base_collection.rb} +2 -2
  24. data/lib/modulr/resources/customers/customer.rb +27 -0
  25. data/lib/modulr/resources/notifications/collection.rb +13 -0
  26. data/lib/modulr/resources/notifications/config.rb +13 -0
  27. data/lib/modulr/resources/notifications/notification.rb +22 -0
  28. data/lib/modulr/resources/payments/collection.rb +13 -0
  29. data/lib/modulr/resources/payments/destination.rb +13 -0
  30. data/lib/modulr/resources/payments/details.rb +21 -0
  31. data/lib/modulr/resources/payments/payment.rb +11 -1
  32. data/lib/modulr/resources/transactions/collection.rb +13 -0
  33. data/lib/modulr/resources/transactions/transaction.rb +30 -0
  34. data/lib/modulr/version.rb +1 -1
  35. data/lib/modulr.rb +11 -4
  36. data/modulr.gemspec +4 -1
  37. metadata +65 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da666a42d86736ca6e0931a62e30672d57a7ab332de197e0bc83bf9a8e5bc175
4
- data.tar.gz: 3b956f72365027d8990693d8e071d825e7a940f103c00eaacd0db68d4edaf433
3
+ metadata.gz: 3f5558adb23b165b565033e9a96d4dcf729e489fb0060abe0b56c22a1158135d
4
+ data.tar.gz: 99460f8fc2f1e2f8404e8e0bfa2cef8f9e9f66ae1dd359a11556b7de8497bcc2
5
5
  SHA512:
6
- metadata.gz: 37c00f76260c63cd0e2b6035161a61324c65b53497e60c580e46f253982435f9c0825480c3945a5e7ccf86fadc674cee17dae46ccb6dc4d5e9e1d5fd5316c06f
7
- data.tar.gz: 3adf0008186b638ddd479c4d0ec281f40393a492238c5eae022594c99f0d8c047ac87ce0c225deeb7db13fff893f0d1c983bf612679f3944c2469f47a6eb96ef
6
+ metadata.gz: 04735f27f41e13507ef0cd1834f76f5b2e60b656314ee1bb4bbd795cafdef1f2e4d515249f1a17877c42667f8c877d851f55184dc9ee48ac78b39f0191d8cdbb
7
+ data.tar.gz: 4298076a4b9957f7ecb6dcf67641f5a95f3ff1517a6d946e583bc3ea26a21a3da0fe937a9ac3c12cf29d81c072cdc21aa352a09bc6a8c82bc86939e4716aaa00
data/.rubocop_devengo.yml CHANGED
@@ -48,6 +48,9 @@ Metrics/BlockLength:
48
48
  - "**/*.rake"
49
49
  - "spec/**/*.rb"
50
50
 
51
+ Metrics/ClassLength:
52
+ Max: 500
53
+
51
54
  Metrics/MethodLength:
52
55
  Max: 50
53
56
 
@@ -159,25 +162,25 @@ Style/RedundantSelfAssignment:
159
162
  Style/SoleNestedConditional:
160
163
  Enabled: true
161
164
 
162
- Style/ArgumentsForwarding: # (new in 1.1)
165
+ Style/ArgumentsForwarding: # (new in 1.1)
163
166
  Enabled: true
164
167
 
165
- Style/DocumentDynamicEvalDefinition: # (new in 1.1)
168
+ Style/DocumentDynamicEvalDefinition: # (new in 1.1)
166
169
  Enabled: true
167
170
 
168
- Style/SwapValues: # (new in 1.1)
171
+ Style/SwapValues: # (new in 1.1)
169
172
  Enabled: true
170
173
 
171
- Style/CollectionCompact: # (new in 1.2)
174
+ Style/CollectionCompact: # (new in 1.2)
172
175
  Enabled: true
173
176
 
174
- Style/NegatedIfElseCondition: # (new in 1.2)
177
+ Style/NegatedIfElseCondition: # (new in 1.2)
175
178
  Enabled: true
176
179
 
177
- Style/NilLambda: # (new in 1.3)
180
+ Style/NilLambda: # (new in 1.3)
178
181
  Enabled: true
179
182
 
180
- Style/RedundantArgument: # (new in 1.4)
183
+ Style/RedundantArgument: # (new in 1.4)
181
184
  Enabled: true
182
185
 
183
186
  Style/FetchEnvVar:
@@ -252,28 +255,28 @@ Lint/UselessMethodDefinition:
252
255
  Lint/UselessTimes:
253
256
  Enabled: true
254
257
 
255
- Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1)
258
+ Lint/DuplicateRegexpCharacterClassElement: # (new in 1.1)
256
259
  Enabled: true
257
260
 
258
- Lint/EmptyBlock: # (new in 1.1)
261
+ Lint/EmptyBlock: # (new in 1.1)
259
262
  Enabled: true
260
263
 
261
- Lint/ToEnumArguments: # (new in 1.1)
264
+ Lint/ToEnumArguments: # (new in 1.1)
262
265
  Enabled: true
263
266
 
264
- Lint/UnmodifiedReduceAccumulator: # (new in 1.1)
267
+ Lint/UnmodifiedReduceAccumulator: # (new in 1.1)
265
268
  Enabled: true
266
269
 
267
- Lint/NoReturnInBeginEndBlocks: # (new in 1.2)
270
+ Lint/NoReturnInBeginEndBlocks: # (new in 1.2)
268
271
  Enabled: true
269
272
 
270
- Lint/DuplicateBranch: # (new in 1.3)
273
+ Lint/DuplicateBranch: # (new in 1.3)
271
274
  Enabled: true
272
275
 
273
- Lint/EmptyClass: # (new in 1.3)
276
+ Lint/EmptyClass: # (new in 1.3)
274
277
  Enabled: true
275
278
 
276
- Lint/UnexpectedBlockArity: # (new in 1.5)
279
+ Lint/UnexpectedBlockArity: # (new in 1.5)
277
280
  Enabled: true
278
281
 
279
282
  RSpec/AggregateExamples:
data/Gemfile.lock CHANGED
@@ -1,16 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- modulr-api (0.0.5)
4
+ modulr-api (0.0.11)
5
5
  faraday (~> 1.0)
6
6
  faraday_middleware (~> 1.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
+ addressable (2.8.1)
12
+ public_suffix (>= 2.0.2, < 6.0)
11
13
  ast (2.4.2)
14
+ coderay (1.1.3)
15
+ crack (0.4.5)
16
+ rexml
12
17
  diff-lcs (1.5.0)
13
- faraday (1.10.0)
18
+ faraday (1.10.3)
14
19
  faraday-em_http (~> 1.0)
15
20
  faraday-em_synchrony (~> 1.0)
16
21
  faraday-excon (~> 1.1)
@@ -35,13 +40,46 @@ GEM
35
40
  faraday-retry (1.0.3)
36
41
  faraday_middleware (1.2.0)
37
42
  faraday (~> 1.0)
43
+ ffi (1.15.5)
44
+ formatador (1.1.0)
45
+ guard (2.18.0)
46
+ formatador (>= 0.2.4)
47
+ listen (>= 2.7, < 4.0)
48
+ lumberjack (>= 1.0.12, < 2.0)
49
+ nenv (~> 0.1)
50
+ notiffany (~> 0.0)
51
+ pry (>= 0.13.0)
52
+ shellany (~> 0.0)
53
+ thor (>= 0.18.1)
54
+ guard-compat (1.2.1)
55
+ guard-rspec (4.7.3)
56
+ guard (~> 2.1)
57
+ guard-compat (~> 1.1)
58
+ rspec (>= 2.99.0, < 4.0)
59
+ hashdiff (1.0.1)
38
60
  json (2.6.2)
39
- multipart-post (2.2.3)
61
+ listen (3.7.1)
62
+ rb-fsevent (~> 0.10, >= 0.10.3)
63
+ rb-inotify (~> 0.9, >= 0.9.10)
64
+ lumberjack (1.2.8)
65
+ method_source (1.0.0)
66
+ multipart-post (2.3.0)
67
+ nenv (0.3.0)
68
+ notiffany (0.1.3)
69
+ nenv (~> 0.1)
70
+ shellany (~> 0.0)
40
71
  parallel (1.22.1)
41
72
  parser (3.1.2.0)
42
73
  ast (~> 2.4.1)
74
+ pry (0.14.1)
75
+ coderay (~> 1.1)
76
+ method_source (~> 1.0)
77
+ public_suffix (5.0.0)
43
78
  rainbow (3.1.1)
44
79
  rake (12.3.3)
80
+ rb-fsevent (0.11.1)
81
+ rb-inotify (0.10.1)
82
+ ffi (~> 1.0)
45
83
  regexp_parser (2.5.0)
46
84
  rexml (3.2.5)
47
85
  rspec (3.10.0)
@@ -78,8 +116,14 @@ GEM
78
116
  rubocop (~> 1.19)
79
117
  ruby-progressbar (1.11.0)
80
118
  ruby2_keywords (0.0.5)
119
+ shellany (0.0.1)
81
120
  test-prof (1.0.9)
121
+ thor (1.2.1)
82
122
  unicode-display_width (2.2.0)
123
+ webmock (2.3.2)
124
+ addressable (>= 2.3.6)
125
+ crack (>= 0.3.2)
126
+ hashdiff
83
127
 
84
128
  PLATFORMS
85
129
  arm64-darwin-20
@@ -87,6 +131,8 @@ PLATFORMS
87
131
  x86_64-linux
88
132
 
89
133
  DEPENDENCIES
134
+ guard (~> 2.0)
135
+ guard-rspec (~> 4.0)
90
136
  modulr-api!
91
137
  rake (~> 12.0)
92
138
  rspec (~> 3.0)
@@ -95,6 +141,7 @@ DEPENDENCIES
95
141
  rubocop-rake (~> 0.1)
96
142
  rubocop-rspec (~> 2.0)
97
143
  test-prof (~> 1.0)
144
+ webmock (~> 2.1)
98
145
 
99
146
  BUNDLED WITH
100
- 2.3.3
147
+ 2.3.15
data/Guardfile ADDED
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A sample Guardfile
4
+ # More info at https://github.com/guard/guard#readme
5
+
6
+ ## Uncomment and set this to only include directories you want to watch
7
+ # directories %w(app lib config test spec features) \
8
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
9
+
10
+ ## Note: if you are using the `directories` clause above and you are not
11
+ ## watching the project directory ('.'), then you will want to move
12
+ ## the Guardfile to a watched dir and symlink it back, e.g.
13
+ #
14
+ # $ mkdir config
15
+ # $ mv Guardfile config/
16
+ # $ ln -s config/Guardfile .
17
+ #
18
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
19
+
20
+ # NOTE: The cmd option is now required due to the increasing number of ways
21
+ # rspec may be run, below are examples of the most common uses.
22
+ # * bundler: 'bundle exec rspec'
23
+ # * bundler binstubs: 'bin/rspec'
24
+ # * spring: 'bin/rspec' (This will use spring if running and you have
25
+ # installed the spring binstubs per the docs)
26
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
27
+ # * 'just' rspec: 'rspec'
28
+
29
+ guard :rspec, cmd: "bundle exec rspec" do
30
+ require "guard/rspec/dsl"
31
+ dsl = Guard::RSpec::Dsl.new(self)
32
+
33
+ # Feel free to open issues for suggestions and improvements
34
+
35
+ # RSpec files
36
+ rspec = dsl.rspec
37
+ watch(rspec.spec_helper) { rspec.spec_dir }
38
+ watch(rspec.spec_support) { rspec.spec_dir }
39
+ watch(rspec.spec_files)
40
+
41
+ # Ruby files
42
+ ruby = dsl.ruby
43
+ dsl.watch_spec_files_for(ruby.lib_files)
44
+
45
+ # Turnip features and steps
46
+ watch(%r{^spec/acceptance/(.+)\.feature$})
47
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
48
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
49
+ end
50
+ end
data/README.md CHANGED
@@ -1,15 +1,13 @@
1
- # Modulr
1
+ # Modulr FINAC API Ruby client
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/modulr`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Ruby client for Modulr (cf. <https://modulr.readme.io/docs>)
6
4
 
7
5
  ## Installation
8
6
 
9
7
  Add this line to your application's Gemfile:
10
8
 
11
9
  ```ruby
12
- gem 'modulr-api'
10
+ gem "modulr-api"
13
11
  ```
14
12
 
15
13
  And then execute:
@@ -26,22 +24,115 @@ gem install modulr-api
26
24
 
27
25
  ## Usage
28
26
 
29
- TODO: Write usage instructions here
27
+ Run `bin/console` for an interactive prompt to experiment with the code.
28
+
29
+ ### Customers
30
+
31
+ ```rb
32
+ # Find a customer
33
+ client.customers.find(id: "C2188C26")
34
+ ```
35
+
36
+ ### Accounts
37
+
38
+ ```rb
39
+ # Find an account
40
+ client.accounts.find(id: "A2188C26")
41
+
42
+ # Create an account
43
+ client.accounts.create(customer_id: "C2188C26", currency: "EUR", product_code: "YOUR_PRODUCT_CODE", external_reference: "My new account")
44
+ ```
45
+
46
+ ### Payments
47
+
48
+ ```rb
49
+ # Find a payment
50
+ client.payments.find(id: "P210FFT5AW")
51
+
52
+ # List payments
53
+ client.payments.list(from: DateTime.now - 1, to: DateTime.now)
54
+
55
+ # Create a payment
56
+ client.payments.create(account_id: "A2188C26", currency: "EUR", amount: 0.01, destination: { type: "IBAN", iban: "ES8601280011390100072676", name: "Aitor García Rey" }, reference: "The reference")
57
+ ```
58
+
59
+ ### Notifications
60
+
61
+ ### Supported event types per channel
62
+
63
+ Not all notifications can be sent to any channel. Check the following list for a quick view and the [original](https://modulr.readme.io/docs/notifications-1) reference for an up-to-date list
64
+
65
+ Supported via webhook:
66
+
67
+ - ACCOUNT_SWITCH_UPDATE
68
+ - DDMANDATE
69
+ - DD_FAILED_CLAIM
70
+ - DD_FUNDS_RETURNED
71
+ - DD_INCOMING_DEBIT
72
+ - DD_COLLECTION_STATUS
73
+ - PAYIN
74
+ - PAYOUT
75
+ - UPCOMING_CREDIT
76
+ - UPCOMING_COLLECTION_CREDIT
77
+ - UPCOMING_COLLECTION_DEBIT
78
+ - PAYMENT_COMPLIANCE_STATUS
79
+ - PAYMENT_FILE_UPLOAD
30
80
 
31
- ## Development
81
+ Supported via email:
32
82
 
33
- 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.
83
+ - ACCOUNT_STATEMENT
84
+ - PENDING_PAYMENTS
85
+ - BALANCE
86
+ - CUSTVSTAT
87
+
88
+ ## Release
89
+
90
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
91
+
92
+ 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 tag and push the new version:
93
+
94
+ ```git
95
+ git tag vx.x.x main
96
+ git push origin vx.x.x
97
+ ```
98
+
99
+ The tagging will trigger the GitHub action defined in `release.yml`, pushing the gem to [rubygems.org](https://rubygems.org).
100
+
101
+ ## Tooling for manual API testing
102
+
103
+ We have a tooling system based on HTTP request files which allow developers to run pre-format Modulr API requests easily. These HTTP request files are stored inside `doc/modulr_requests` directory. Find the service you want to run and run the specific HTTP file to execute the request locally.
104
+
105
+ We run these requests locally from our favorite IDEs, in our case, we use ([IntelliJ](https://www.jetbrains.com/es-es/idea/) or [VS Code](https://code.visualstudio.com/)). [IntelliJ](https://www.jetbrains.com/es-es/idea/) has a native feature that allows you to run these requests easily but if you are using [VS Code](https://code.visualstudio.com/) you will need to install the following extension [httpYac](https://marketplace.visualstudio.com/items?itemName=anweber.vscode-httpyac).
106
+
107
+ To configure the HTTP files system create a new `modulr-ruby/doc/modulr_requests/http-client.private.env.json` from `modulr-ruby/doc/modulr_requests/http-client.env.json` file.
108
+ Configure all variables by environment and you are ready to use your HTTP files.
109
+
110
+ ## Testing
111
+
112
+ Any change should be tested. Builds with failures would not be allowed to merge.
113
+ To run your test suite locally using Rspec:
114
+
115
+ ```rb
116
+ bundle exec rspec
117
+ ```
118
+
119
+ To prepare your environment to listen for your local code changes use Guard instead:
120
+
121
+ ```rb
122
+ bundle exec guard
123
+ ```
34
124
 
35
- 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).
125
+ To test services, we have a spec system that uses the [Webmock](https://github.com/bblimke/webmock) library to stub requests and checks them against service response HTTP format files.
126
+ These HTTP files are stored in the `spec/fixtures` directory.
36
127
 
37
128
  ## Contributing
38
129
 
39
- Bug reports and pull requests are welcome on GitHub at <https://github.com/devengo/modulr-ruby>. 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/devengo/modulr-ruby/blob/master/CODE_OF_CONDUCT.md).
130
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/devengoapp/modulr-ruby>. 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/devengoapp/modulr-ruby/blob/main/CODE_OF_CONDUCT.md).
40
131
 
41
132
  ## License
42
133
 
43
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
134
+ The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
44
135
 
45
136
  ## Code of Conduct
46
137
 
47
- Everyone interacting in the Modulr project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/devengo/modulr-ruby/blob/master/CODE_OF_CONDUCT.md).
138
+ Everyone interacting in the Modulr project's codebases, issue trackers, chat rooms, and mailing lists is expected to follow the [code of conduct](https://github.com/devengoapp/modulr-ruby/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,4 @@
1
+ ### Accounts close
2
+ POST {{base_url}}/accounts/{{account_id}}/close
3
+ Content-Type: application/json
4
+ Authorization: {{api_key}}
@@ -0,0 +1,21 @@
1
+ ### Create Account -- (201) Created
2
+ POST {{base_url}}/customers/{{customer_id}}/accounts
3
+ Content-Type: application/json
4
+ Authorization: {{api_key}}
5
+
6
+ {
7
+ "externalReference": "my_reference -1-",
8
+ "currency": "EUR",
9
+ "productCode": "O1200001"
10
+ }
11
+
12
+ ### Create Account -- (400) Bad Request
13
+ POST {{base_url}}/customers/{{customer_id}}/accounts
14
+ Content-Type: application/json
15
+ Authorization: {{api_key}}
16
+
17
+ {
18
+ "externalReference": "my_reference -1-",
19
+ "currency": "EUR",
20
+ "productCode": null
21
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "sandbox": {
3
+ "base_url": "https://api-sandbox.modulrfinance.com/api-sandbox-token",
4
+ "account_id": "account_id",
5
+ "api_key": "api_key",
6
+ "customer_id": "customer_id"
7
+ }
8
+ }
@@ -0,0 +1,4 @@
1
+ ### Transactions History
2
+ GET {{base_url}}/accounts/{{account_id}}/transactions
3
+ Content-Type: application/json
4
+ Authorization: {{api_key}}
@@ -3,11 +3,27 @@
3
3
  module Modulr
4
4
  module API
5
5
  class AccountsService < Service
6
- def info(account_id:)
7
- response = client.get("/accounts/#{account_id}")
8
- puts response.body
6
+ def find(id:)
7
+ response = client.get("/accounts/#{id}")
9
8
  Resources::Accounts::Account.new(response, response.body)
10
9
  end
10
+
11
+ def create(customer_id:, currency:, product_code:, **opts)
12
+ payload = {
13
+ currency: currency,
14
+ productCode: product_code,
15
+ }
16
+ payload[:externalReference] = opts[:external_reference] if opts[:external_reference]
17
+
18
+ response = client.post("/customers/#{customer_id}/accounts", payload)
19
+ Resources::Accounts::Account.new(response, response.body)
20
+ end
21
+
22
+ def close(account_id:)
23
+ client.post("/accounts/#{account_id}/close")
24
+
25
+ nil
26
+ end
11
27
  end
12
28
  end
13
29
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module API
5
+ class CustomersService < Service
6
+ def find(id:)
7
+ response = client.get("/customers/#{id}")
8
+ Resources::Customers::Customer.new(response, response.body)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module API
5
+ class NotificationsService < Service
6
+ def find(id:, **opts)
7
+ response = client.get("#{base_notification_url(opts)}/notifications/#{id}")
8
+ Resources::Notifications::Notification.new(response, response.body)
9
+ end
10
+
11
+ def list(**opts)
12
+ response = client.get("#{base_notification_url(opts)}/notifications")
13
+ Resources::Notifications::Collection.new(response, response.body)
14
+ end
15
+
16
+ def create(type:, channel:, destinations:, config:, **opts)
17
+ payload = {
18
+ type: type,
19
+ channel: channel,
20
+ destinations: destinations,
21
+ config: config,
22
+ }
23
+ response = client.post("#{base_notification_url(opts)}/notifications", payload)
24
+ Resources::Notifications::Notification.new(response, response.body)
25
+ end
26
+
27
+ protected def base_notification_url(opts)
28
+ opts[:partner_id] ? "/partners/#{opts[:partner_id]}" : "/customers/#{opts[:customer_id]}"
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,30 +3,55 @@
3
3
  module Modulr
4
4
  module API
5
5
  class PaymentsService < Service
6
- def create( # rubocop:disable Metrics/ParameterLists
7
- account_id:,
8
- currency:,
9
- amount:,
10
- destination:,
11
- reference:,
12
- options: {}
13
- )
14
- data = {
6
+ def find(id:)
7
+ response = client.get("/payments", { id: id })
8
+ payment_attributes = response.body[:content]&.first
9
+ raise NotFoundError, "Payment #{id} not found" unless payment_attributes
10
+
11
+ Resources::Payments::Payment.new(response, payment_attributes)
12
+ end
13
+
14
+ def list(**opts)
15
+ return find(id: opts[:id]) if opts[:id]
16
+
17
+ response = client.get("/payments", build_query_params(opts))
18
+ Resources::Payments::Collection.new(response, response.body[:content])
19
+ end
20
+
21
+ # rubocop:disable Metrics/ParameterLists
22
+ def create(account_id:, destination:, reference:, currency:, amount:, **opts)
23
+ payload = {
15
24
  sourceAccountId: account_id,
25
+ destination: destination,
26
+ reference: reference,
16
27
  currency: currency,
17
28
  amount: amount,
18
- reference: reference,
19
- destination: {
20
- type: destination[:type],
21
- iban: destination[:iban],
22
- name: destination[:name],
23
- },
24
29
  }
25
- data[:externalReference] = options[:external_reference] if options[:external_reference]
26
30
 
27
- response = client.post("/payments", data, options)
31
+ payload[:externalReference] = opts[:external_reference] if opts[:external_reference]
32
+ payload[:endToEndReference] = opts[:e2e_reference] if opts[:e2e_reference]
33
+
34
+ response = client.post("/payments", payload)
28
35
  Resources::Payments::Payment.new(response, response.body)
29
36
  end
37
+ # rubocop:enable Metrics/ParameterLists
38
+
39
+ # rubocop:disable Metrics/AbcSize
40
+ private def build_query_params(opts)
41
+ same_name_params = [:type, :status]
42
+ date_params = { to: :toCreatedDate, from: :fromCreatedDate, updated_since: :modifiedSince }
43
+ mapped_params = {
44
+ external_reference: :externalReference,
45
+ has_external_reference: :hasExternalReference,
46
+ account_id: :sourceAccountId,
47
+ }
48
+ {}.tap do |params|
49
+ same_name_params.each { |param| params[param] = opts[param] if opts[param] }
50
+ date_params.each { |original, mapped| params[mapped] = format_datetime(opts[original]) if opts[original] }
51
+ mapped_params.each { |original, mapped| params[mapped] = opts[original] if opts[original] }
52
+ end
53
+ end
54
+ # rubocop:enable Metrics/AbcSize
30
55
  end
31
56
  end
32
57
  end
@@ -8,6 +8,10 @@ module Modulr
8
8
  def initialize(client)
9
9
  @client = client
10
10
  end
11
+
12
+ def format_datetime(datetime)
13
+ datetime.strftime("%Y-%m-%dT%l:%M:%S%z")
14
+ end
11
15
  end
12
16
  end
13
17
  end
@@ -3,6 +3,7 @@
3
3
  require_relative "service"
4
4
  require_relative "accounts_service"
5
5
  require_relative "payments_service"
6
+ require_relative "transactions_service"
6
7
 
7
8
  module Modulr
8
9
  module API
@@ -11,9 +12,21 @@ module Modulr
11
12
  @services[:accounts] ||= API::AccountsService.new(self)
12
13
  end
13
14
 
15
+ def customers
16
+ @services[:customers] ||= API::CustomersService.new(self)
17
+ end
18
+
14
19
  def payments
15
20
  @services[:payments] ||= API::PaymentsService.new(self)
16
21
  end
22
+
23
+ def notifications
24
+ @services[:notifications] ||= API::NotificationsService.new(self)
25
+ end
26
+
27
+ def transactions
28
+ @services[:transactions] ||= API::TransactionsService.new(self)
29
+ end
17
30
  end
18
31
  end
19
32
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module API
5
+ class TransactionsService < Service
6
+ def history(account_id:)
7
+ response = client.get("/accounts/#{account_id}/transactions")
8
+ Resources::Transactions::Transactions.new(response, response.body[:content])
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/modulr/client.rb CHANGED
@@ -60,7 +60,9 @@ module Modulr
60
60
  uri = "#{base_url}#{path}"
61
61
 
62
62
  begin
63
- connection.run_request(method, uri, request_options[:body], request_options[:headers])
63
+ connection.run_request(method, uri, request_options[:body], request_options[:headers]) do |request|
64
+ request.params.update(options) if options
65
+ end
64
66
  rescue StandardError => e
65
67
  handle_request_error(e)
66
68
  end
@@ -91,28 +93,22 @@ module Modulr
91
93
  end
92
94
 
93
95
  def handle_request_error(error)
96
+ response = error.response
94
97
  case error
95
98
  when Faraday::ClientError
96
- if error.response
97
- handle_error_response(error)
99
+ case response[:status]
100
+ when 403
101
+ raise ForbiddenError, response
102
+ when 404
103
+ raise NotFoundError, response
98
104
  else
99
- handle_network_error(error)
105
+ raise RequestError, response
100
106
  end
101
107
  else
102
- raise error
108
+ raise Error, response
103
109
  end
104
110
  end
105
111
 
106
- def handle_error_response(error)
107
- puts "Client Error: #{error.response}"
108
- raise error
109
- end
110
-
111
- def handle_network_error(error)
112
- puts "Network error: #{error.response}"
113
- raise error
114
- end
115
-
116
112
  def default_origin
117
113
  "Modulr/#{Modulr::VERSION} Ruby Client (Faraday/#{Faraday::VERSION})"
118
114
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ class Error < StandardError
5
+ end
6
+
7
+ class RequestError < Error
8
+ attr_reader :response, :errors
9
+
10
+ def initialize(response)
11
+ @response = response
12
+ @errors = extract_errors
13
+ super(message_from(response))
14
+ end
15
+
16
+ private def extract_errors
17
+ return unless json?
18
+
19
+ response[:body]
20
+ end
21
+
22
+ private def message_from(response)
23
+ return response if response.is_a?(String)
24
+
25
+ if errors
26
+ errors.map { |error| "#{error[:field]} #{error[:code]} #{error[:message]}" }.join(", ")
27
+ else
28
+ "#{response[:status]} #{response[:body]}"
29
+ end
30
+ end
31
+
32
+ private def json?
33
+ return unless response.is_a?(Hash)
34
+
35
+ content_type = response[:headers]["content-type"]
36
+ content_type&.start_with?("application/json")
37
+ end
38
+ end
39
+
40
+ class NotFoundError < RequestError
41
+ end
42
+
43
+ class ForbiddenError < RequestError
44
+ end
45
+ end
@@ -12,11 +12,11 @@ module Modulr
12
12
  closed: "CLOSED",
13
13
  client_blocked: "CLIENT_BLOCKED",
14
14
  }.freeze
15
- map :id, :id
16
- map :balance, :balance
15
+ map :id
16
+ map :balance
17
+ map :currency
18
+ map :status
17
19
  map :availableBalance, :available_balance
18
- map :currency, :currency
19
- map :status, :status
20
20
  map :customerId, :customer_id
21
21
  map :customerName, :customer_name
22
22
  map :externalReference, :external_reference
@@ -4,12 +4,12 @@ module Modulr
4
4
  module Resources
5
5
  module Accounts
6
6
  class Identifier < Base
7
- map :type, :type
7
+ map :type
8
+ map :iban
9
+ map :bic
10
+ map :currency
8
11
  map :accountNumber, :account_number
9
12
  map :sortCode, :sort_code
10
- map :iban, :sort_code
11
- map :bic, :sort_code
12
- map :currency, :sort_code
13
13
  map :countrySpecificDetails, :country_details
14
14
  map :providerExtraInfo, :provider_extra_info
15
15
  end
@@ -3,7 +3,7 @@
3
3
  module Modulr
4
4
  module Resources
5
5
  module Accounts
6
- class Identifiers < Collection
6
+ class Identifiers < BaseCollection
7
7
  def initialize(response, attributes_collection)
8
8
  super(response, Identifier, attributes_collection)
9
9
  end
@@ -13,8 +13,9 @@ module Modulr
13
13
  end
14
14
  end
15
15
 
16
- def self.map(original_attribute, mapped_attributes)
16
+ def self.map(original_attribute, mapped_attributes = nil)
17
17
  class_eval { attr_writer original_attribute.to_sym }
18
+ mapped_attributes ||= original_attribute
18
19
  mapped_attributes = [mapped_attributes].flatten
19
20
  mapped_attributes.each do |mapped_attribute|
20
21
  define_method(mapped_attribute) { instance_variable_get("@#{original_attribute}") }
@@ -24,6 +25,17 @@ module Modulr
24
25
  end
25
26
  end
26
27
 
27
- require_relative "collection"
28
+ require_relative "base_collection"
28
29
  require_relative "accounts/account"
30
+ require_relative "accounts/identifier"
31
+ require_relative "accounts/identifiers"
32
+ require_relative "customers/customer"
33
+ require_relative "notifications/notification"
34
+ require_relative "notifications/config"
35
+ require_relative "notifications/collection"
29
36
  require_relative "payments/payment"
37
+ require_relative "payments/details"
38
+ require_relative "payments/destination"
39
+ require_relative "payments/collection"
40
+ require_relative "transactions/transaction"
41
+ require_relative "transactions/collection"
@@ -2,14 +2,14 @@
2
2
 
3
3
  module Modulr
4
4
  module Resources
5
- class Collection
5
+ class BaseCollection
6
6
  include Enumerable
7
7
  attr_reader :response
8
8
 
9
9
  def initialize(response, item_klass, attributes_collection = [])
10
10
  @response = response
11
11
  @attributes_collection = attributes_collection
12
- @items = attributes_collection.map { |attributes_item| item_klass.new(nil, attributes_item) }
12
+ @items = attributes_collection.map { |attributes_item| item_klass.new(response, attributes_item) }
13
13
  end
14
14
 
15
15
  def each(&block)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Customers
6
+ class Customer < Base
7
+ map :id
8
+ map :name
9
+ map :type
10
+ map :status
11
+ map :verificationStatus, :verification_status
12
+ map :companyRegNumber, :taxid
13
+ map :expectedMonthlySpend, :expected_monthly_spend
14
+ map :partnerId, :partner_id
15
+ map :industryCode, :industry_code
16
+ map :tcsVersion, :tcs_version
17
+ map :externalReference, :external_reference
18
+ map :createdDate, :created_at
19
+ map :holdPaymentsForFunds, :hold_payments_for_funds
20
+ map :cardConstraintsBid, :card_constraints_bid
21
+ map :needAddressVerification, :need_address_verification
22
+ map :accessGroupsVisible, :access_groups_visible
23
+ map :legalEntity, :legal_entity
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Notifications
6
+ class Collection < BaseCollection
7
+ def initialize(response, attributes_collection)
8
+ super(response, Notification, attributes_collection)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Notifications
6
+ class Config < Base
7
+ map :retry
8
+ map :secret
9
+ map :hmacAlgorithm, :hmac_algorithm
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Notifications
6
+ class Notification < Base
7
+ attr_reader :config
8
+
9
+ map :id
10
+ map :type
11
+ map :channel
12
+ map :status
13
+ map :destinations
14
+
15
+ def initialize(response, attributes = {})
16
+ super(response, attributes)
17
+ @config = Config.new(response, attributes[:config])
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Payments
6
+ class Collection < BaseCollection
7
+ def initialize(response, attributes_collection)
8
+ super(response, Payment, attributes_collection)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Payments
6
+ class Destination < Base
7
+ map :type
8
+ map :iban
9
+ map :name
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Payments
6
+ class Details < Base
7
+ attr_reader :destination
8
+
9
+ map :sourceAccountId, :source_account_id
10
+ map :currency
11
+ map :amount
12
+ map :reference
13
+
14
+ def initialize(response, attributes = {})
15
+ super(response, attributes)
16
+ @destination = Destination.new(response, attributes[:destination])
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,9 +4,19 @@ module Modulr
4
4
  module Resources
5
5
  module Payments
6
6
  class Payment < Base
7
+ attr_reader :details
8
+
7
9
  map :id, [:id, :payment_reference_id]
8
- map :status, :status
10
+ map :status
11
+ map :reference
9
12
  map :externalReference, :external_reference
13
+ map :createdDate, :created_at
14
+ map :approvalStatus, :approval_status
15
+
16
+ def initialize(response, attributes = {})
17
+ super(response, attributes)
18
+ @details = Details.new(response, attributes[:details])
19
+ end
10
20
  end
11
21
  end
12
22
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Transactions
6
+ class Transactions < BaseCollection
7
+ def initialize(response, attributes_collection)
8
+ super(response, Transaction, attributes_collection)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modulr
4
+ module Resources
5
+ module Transactions
6
+ class Transaction < Base
7
+ attr_reader :balance, :available_balance
8
+
9
+ map :id
10
+ map :amount
11
+ map :currency
12
+ map :description
13
+ map :credit
14
+ map :type
15
+ map :transactionDate, :created_at
16
+ map :postedDate, :posted_date
17
+ map :sourceId, :source_id
18
+ map :sourceExternalReference, :external_reference
19
+ map :additionalInfo, :additional_info
20
+
21
+ def initialize(response, attributes = {})
22
+ super(response, attributes)
23
+
24
+ @balance = attributes[:account][:balance]
25
+ @available_balance = attributes[:account][:availableBalance]
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modulr
4
- VERSION = "0.0.5"
4
+ VERSION = "0.0.11"
5
5
  end
data/lib/modulr.rb CHANGED
@@ -3,8 +3,15 @@
3
3
  require_relative "modulr/version"
4
4
 
5
5
  require_relative "modulr/auth/signature"
6
- require_relative "modulr/client"
7
6
 
8
- module Modulr
9
- class Error < StandardError; end
10
- end
7
+ require_relative "modulr/api/service"
8
+ require_relative "modulr/api/services"
9
+ require_relative "modulr/api/accounts_service"
10
+ require_relative "modulr/api/customers_service"
11
+ require_relative "modulr/api/payments_service"
12
+ require_relative "modulr/api/notifications_service"
13
+
14
+ require_relative "modulr/resources/base"
15
+
16
+ require_relative "modulr/error"
17
+ require_relative "modulr/client"
data/modulr.gemspec CHANGED
@@ -27,12 +27,14 @@ Gem::Specification.new do |spec|
27
27
  (f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
28
  end
29
29
  end
30
- # spec.bindir = "exe"
30
+ spec.bindir = "exe"
31
31
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
32
  spec.require_paths = ["lib"]
33
33
 
34
34
  spec.add_dependency "faraday", "~> 1.0"
35
35
  spec.add_dependency "faraday_middleware", "~> 1.0"
36
+ spec.add_development_dependency "guard", "~> 2.0"
37
+ spec.add_development_dependency "guard-rspec", "~> 4.0"
36
38
  spec.add_development_dependency "rake", "~> 12.0"
37
39
  spec.add_development_dependency "rspec", "~> 3.0"
38
40
  spec.add_development_dependency "rubocop", "~> 1.0"
@@ -40,4 +42,5 @@ Gem::Specification.new do |spec|
40
42
  spec.add_development_dependency "rubocop-rake", "~> 0.1"
41
43
  spec.add_development_dependency "rubocop-rspec", "~> 2.0"
42
44
  spec.add_development_dependency "test-prof", "~> 1.0"
45
+ spec.add_development_dependency "webmock", "~> 2.1"
43
46
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modulr-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aitor García Rey
8
8
  autorequire:
9
- bindir: bin
9
+ bindir: exe
10
10
  cert_chain: []
11
- date: 2022-07-29 00:00:00.000000000 Z
11
+ date: 2023-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -38,6 +38,34 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: guard
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard-rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
41
69
  - !ruby/object:Gem::Dependency
42
70
  name: rake
43
71
  requirement: !ruby/object:Gem::Requirement
@@ -136,6 +164,20 @@ dependencies:
136
164
  - - "~>"
137
165
  - !ruby/object:Gem::Version
138
166
  version: '1.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: webmock
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.1'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.1'
139
181
  description: Ruby client for Modulr Finance API.
140
182
  email:
141
183
  - aitor@devengo.com
@@ -150,24 +192,42 @@ files:
150
192
  - CODE_OF_CONDUCT.md
151
193
  - Gemfile
152
194
  - Gemfile.lock
195
+ - Guardfile
153
196
  - LICENSE.txt
154
197
  - README.md
155
198
  - Rakefile
156
199
  - bin/console
157
200
  - bin/setup
201
+ - doc/modulr_requests/accounts/close.http
202
+ - doc/modulr_requests/accounts/create.http
203
+ - doc/modulr_requests/http-client.env.json
204
+ - doc/modulr_requests/transactions/history.http
158
205
  - lib/modulr.rb
159
206
  - lib/modulr/api/accounts_service.rb
207
+ - lib/modulr/api/customers_service.rb
208
+ - lib/modulr/api/notifications_service.rb
160
209
  - lib/modulr/api/payments_service.rb
161
210
  - lib/modulr/api/service.rb
162
211
  - lib/modulr/api/services.rb
212
+ - lib/modulr/api/transactions_service.rb
163
213
  - lib/modulr/auth/signature.rb
164
214
  - lib/modulr/client.rb
215
+ - lib/modulr/error.rb
165
216
  - lib/modulr/resources/accounts/account.rb
166
217
  - lib/modulr/resources/accounts/identifier.rb
167
218
  - lib/modulr/resources/accounts/identifiers.rb
168
219
  - lib/modulr/resources/base.rb
169
- - lib/modulr/resources/collection.rb
220
+ - lib/modulr/resources/base_collection.rb
221
+ - lib/modulr/resources/customers/customer.rb
222
+ - lib/modulr/resources/notifications/collection.rb
223
+ - lib/modulr/resources/notifications/config.rb
224
+ - lib/modulr/resources/notifications/notification.rb
225
+ - lib/modulr/resources/payments/collection.rb
226
+ - lib/modulr/resources/payments/destination.rb
227
+ - lib/modulr/resources/payments/details.rb
170
228
  - lib/modulr/resources/payments/payment.rb
229
+ - lib/modulr/resources/transactions/collection.rb
230
+ - lib/modulr/resources/transactions/transaction.rb
171
231
  - lib/modulr/version.rb
172
232
  - modulr.gemspec
173
233
  - sig/modulr.rbs
@@ -194,7 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
194
254
  - !ruby/object:Gem::Version
195
255
  version: '0'
196
256
  requirements: []
197
- rubygems_version: 3.3.7
257
+ rubygems_version: 3.4.6
198
258
  signing_key:
199
259
  specification_version: 4
200
260
  summary: Ruby client for Modulr Finance API.