synapseruby 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ebc99ee79db2dbcb7e91d2cab9e902593a9ab15e
4
+ data.tar.gz: 8a71c65ff8748d4c83e452f7b6bc09d7e90b625f
5
+ SHA512:
6
+ metadata.gz: 79bce83ab437fb49be69d94d311debed6ae6cd8c930c1e935069a3db8130ef79094c8eb42c16a836b5c49daecaf9a687c62867ea08f9c5f07bdfdc89a517f776
7
+ data.tar.gz: 87b7cc6334d114f46dfd4b334f3864c466ae1ca2376de9049335ac271302f8d091ac39bfd23ccd8e2d3866f64c4af519a0bb65a26c77f738f1253fcbab713498
data/.env.sample ADDED
@@ -0,0 +1,9 @@
1
+ # For development in a console
2
+ CLIENT_ID=your_sandbox_client_id
3
+ CLIENT_SECRET=your_sandbox_client_secret
4
+ FINGERPRINT=your_sandbox_fingerprint
5
+
6
+ # For running tests
7
+ TEST_CLIENT_ID=your_sandbox_client_id
8
+ TEST_CLIENT_SECRET=your_sandbox_client_secret
9
+
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ .env
2
+ .DS_Store
3
+
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+ source 'https://rubygems.org'
3
+
4
+
5
+ # Specify your gem's dependencies in your *.gemspec file
6
+ gemspec
7
+
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ synapse_fi (0.0.3)
5
+ rest-client (~> 2.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ansi (1.5.0)
11
+ builder (3.2.3)
12
+ domain_name (0.5.20180417)
13
+ unf (>= 0.0.5, < 1.0.0)
14
+ dotenv (2.1.2)
15
+ http-cookie (1.0.3)
16
+ domain_name (~> 0.5)
17
+ mime-types (3.2.2)
18
+ mime-types-data (~> 3.2015)
19
+ mime-types-data (3.2018.0812)
20
+ minitest (5.8.5)
21
+ minitest-reporters (1.1.19)
22
+ ansi
23
+ builder
24
+ minitest (>= 5.0)
25
+ ruby-progressbar
26
+ netrc (0.11.0)
27
+ rake (10.5.0)
28
+ rest-client (2.0.2)
29
+ http-cookie (>= 1.0.2, < 2.0)
30
+ mime-types (>= 1.16, < 4.0)
31
+ netrc (~> 0.8)
32
+ ruby-progressbar (1.10.0)
33
+ unf (0.1.4)
34
+ unf_ext
35
+ unf_ext (0.0.7.5)
36
+
37
+ PLATFORMS
38
+ ruby
39
+
40
+ DEPENDENCIES
41
+ bundler
42
+ dotenv (~> 2.1.1)
43
+ minitest (~> 5.8.2)
44
+ minitest-reporters (~> 1.1.5)
45
+ rake (~> 10.0)
46
+ synapse_fi!
47
+
48
+ BUNDLED WITH
49
+ 1.17.1
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # SynapseRuby
2
+
3
+ Native API library for SynapseFI REST v3.x
4
+
5
+ Not all API endpoints are supported.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'synapseruby'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ ```bash
18
+ $ bundle
19
+ ```
20
+
21
+ Or install it yourself by executing:
22
+
23
+ ```bash
24
+ $ gem install synapseruby
25
+ ```
26
+ ## Documentation
27
+
28
+ - [API docs](http://docs.synapsefi.com/v3.1)
29
+ - [synapse_fi gem docs](https://www.rubydoc.info/gems/synapse_fi)
30
+ - [Samples demonstrating common operations](samples.md)
31
+
32
+ ## Contributing
33
+
34
+ For minor issues, please open a pull request. For larger changes or features, please email hello@synapsepay.com. Please document and test any public constants/methods.
35
+
36
+ ## Running the Test Suite
37
+
38
+ If you haven't already, set the `TEST_CLIENT_ID` and `TEST_CLIENT_SECRET` environment variables in `.env` file .
39
+ Please read and update test files with your user own test id's
40
+
41
+ To run all tests, execute:
42
+
43
+ ```bash
44
+ rake test
45
+ ```
46
+
47
+ ## License
48
+
49
+ [MIT License](LICENSE)
50
+
51
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "lib"
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
Binary file
@@ -0,0 +1,359 @@
1
+ require_relative './http_request'
2
+ require_relative './user'
3
+ require_relative './users'
4
+ require_relative './transaction'
5
+ require_relative './transactions'
6
+ require_relative './node'
7
+ require_relative './nodes'
8
+ require_relative './subscription'
9
+ require_relative './subscriptions'
10
+ require_relative './subnet'
11
+ require_relative './subnets'
12
+ require 'pp'
13
+
14
+ module Synapse
15
+ # Initializes various wrapper settings such as development mode and request
16
+ # header values
17
+
18
+ class Client
19
+
20
+ VALID_QUERY_PARAMS = [:query, :page, :per_page, :full_dehydrate, :radius, :zip, :lat, :lon, :limit, :currency].freeze
21
+
22
+ attr_accessor :http_client
23
+
24
+ attr_reader :client_id
25
+
26
+ # Alias for #http_client
27
+ alias_method :client, :http_client
28
+
29
+ # @param client_id [String] should be stored in environment variable
30
+ # @param client_secret [String] should be stored in environment variable
31
+ # @param ip_address [String] user's IP address
32
+ # @param fingerprint [String] a hashed value, either unique to user or static
33
+ # @param development_mode [String] default true
34
+ # @param raise_for_202 [Boolean]
35
+ # @param logging [Boolean] (optional) logs to stdout when true
36
+ # @param log_to [String] (optional) file path to log to file (logging must be true)
37
+ def initialize(client_id:, client_secret:, ip_address:, fingerprint:nil,development_mode: true, raise_for_202:nil, **options)
38
+ base_url = if development_mode
39
+ 'https://uat-api.synapsefi.com/v3.1'
40
+ else
41
+ 'https://api.synapsefi.com/v3.1'
42
+ end
43
+ @client_id = client_id
44
+ @client_secret = client_secret
45
+ @http_client = HTTPClient.new(base_url: base_url,
46
+ client_id: client_id,
47
+ client_secret: client_secret,
48
+ fingerprint: fingerprint,
49
+ ip_address: ip_address,
50
+ raise_for_202: raise_for_202,
51
+ **options
52
+ )
53
+ end
54
+
55
+ # Queries Synapse API to create a new user
56
+ # @param payload [Hash]
57
+ # @param idempotency_key [String] (optional)
58
+ # @return [Synapse::User]
59
+ # @see https://docs.synapsepay.com/docs/create-a-user payload structure
60
+ def create_user(payload:, **options)
61
+ response = client.post(user_path,payload, options)
62
+
63
+ User.new(user_id: response['_id'],
64
+ refresh_token: response['refresh_token'],
65
+ client: client,
66
+ full_dehydrate: "no",
67
+ payload: response
68
+ )
69
+ end
70
+
71
+ # Update headers in HTTPClient class
72
+ # for API request headers
73
+ # @param fingerprint [Hash]
74
+ # @param idemopotency_key [Hash]
75
+ # @param ip_address [Hash]
76
+ def update_headers(fingerprint:nil, idemopotency_key:nil, ip_address:nil)
77
+ client.update_headers(fingerprint: fingerprint, idemopotency_key: idemopotency_key, ip_address: ip_address)
78
+ end
79
+
80
+ # Queries Synapse API for a user by user_id
81
+ # @param user_id [String] id of the user to find
82
+ # @param full_dehydrate [String] (optional) if true, returns all KYC on user
83
+ # @see https://docs.synapsefi.com/docs/get-user
84
+ # @return [Synapse::User]
85
+ def get_user(user_id:, **options)
86
+ raise ArgumentError, 'client must be a Synapse::Client' unless self.is_a?(Client)
87
+ raise ArgumentError, 'user_id must be a String' unless user_id.is_a?(String)
88
+
89
+ options[:full_dehydrate] = "yes" if options[:full_dehydrate] == true
90
+ options[:full_dehydrate] = "no" if options[:full_dehydrate] == false
91
+
92
+ path = user_path(user_id: user_id, full_dehydrate: options[:full_dehydrate])
93
+ response = client.get(path)
94
+
95
+ User.new(user_id: response['_id'],
96
+ refresh_token: response['refresh_token'],
97
+ client: client,
98
+ full_dehydrate: options[:full_dehydrate] == "yes" ? true : false,
99
+ payload: response
100
+ )
101
+ end
102
+
103
+ # Queries Synapse API for platform users
104
+ # @param query [String] (optional) response will be filtered to
105
+ # users with matching name/email
106
+ # @param page [Integer] (optional) response will default to 1
107
+ # @param per_page [Integer] (optional) response will default to 20
108
+ # @return [Array<Synapse::Users>]
109
+ def get_users(**options)
110
+ path = user_path(options)
111
+ response = client.get(path)
112
+ return [] if response["users"].empty?
113
+ users = response["users"].map { |user_data| User.new(user_id: user_data['_id'],
114
+ refresh_token: user_data['refresh_token'],
115
+ client: client,
116
+ full_dehydrate: "no",
117
+ payload: user_data
118
+ )}
119
+ Users.new(limit: response["limit"],
120
+ page: response["page"],
121
+ page_count: response["page_count"],
122
+ user_count: response["user_count"],
123
+ payload: users,
124
+ http_client: client
125
+ )
126
+ end
127
+
128
+ # Queries Synapse for all transactions on platform
129
+ # @param page [Integer] (optional) response will default to 1
130
+ # @param per_page [Integer] (optional) response will default to 20
131
+ # @return [Array<Synapse::Transactions>]
132
+ def get_all_transaction(**options)
133
+ path = '/trans'
134
+
135
+ params = VALID_QUERY_PARAMS.map do |p|
136
+ options[p] ? "#{p}=#{options[p]}" : nil
137
+ end.compact
138
+
139
+ path += '?' + params.join('&') if params.any?
140
+
141
+ trans = client.get(path)
142
+
143
+ return [] if trans["trans"].empty?
144
+ response = trans["trans"].map { |trans_data| Transaction.new(trans_id: trans_data['_id'], payload: trans_data)}
145
+ Transactions.new(limit: trans["limit"],
146
+ page: trans["page"],
147
+ page_count: trans["page_count"],
148
+ trans_count: trans["trans_count"],
149
+ payload: response
150
+ )
151
+ end
152
+
153
+ # Queries Synapse API for all nodes belonging to platform
154
+ # @param page [Integer] (optional) response will default to 1
155
+ # @param per_page [Integer] (optional) response will default to 20
156
+ # @return [Array<Synapse::Nodes>]
157
+ def get_all_nodes(**options)
158
+ [options[:page], options[:per_page]].each do |arg|
159
+ if arg && (!arg.is_a?(Integer) || arg < 1)
160
+ raise ArgumentError, "#{arg} must be nil or an Integer >= 1"
161
+ end
162
+ end
163
+ path = nodes_path(options: options)
164
+ nodes = client.get(path)
165
+
166
+ return [] if nodes["nodes"].empty?
167
+ response = nodes["nodes"].map { |node_data| Node.new(node_id: node_data['_id'],
168
+ user_id: node_data['user_id'],
169
+ payload: node_data,
170
+ full_dehydrate: "no"
171
+ )}
172
+ Nodes.new(limit: nodes["limit"],
173
+ page: nodes["page"],
174
+ page_count: nodes["page_count"],
175
+ nodes_count: nodes["node_count"],
176
+ payload: response
177
+ )
178
+ end
179
+
180
+ # Queries Synapse API for all institutions available for bank logins
181
+ # @param page [Integer] (optional) response will default to 1
182
+ # @param per_page [Integer] (optional) response will default to 20
183
+ # @return API response [Hash]
184
+ def get_all_institutions(**options)
185
+ client.get(institutions_path(options))
186
+ end
187
+
188
+ # Queries Synapse API to create a webhook subscriptions for platform
189
+ # @param scope [Hash]
190
+ # @param idempotency_key [String] (optional)
191
+ # @see https://docs.synapsefi.com/docs/create-subscription
192
+ # @return [Synapse::Subscription]
193
+ def create_subscriptions(scope:, **options)
194
+ response = client.post(subscriptions_path , scope, options)
195
+
196
+ Subscription.new(subscription_id: response["_id"], url: response["url"], payload: response)
197
+ end
198
+
199
+ # Queries Synapse API for all platform subscriptions
200
+ # @param page [Integer] (optional) response will default to 1
201
+ # @param per_page [Integer] (optional) response will default to 20
202
+ # @return [Array<Synapse::Subscriptions>]
203
+ def get_all_subscriptions(**options)
204
+ subscriptions = client.get(subscriptions_path(options))
205
+
206
+ return [] if subscriptions["subscriptions"].empty?
207
+ response = subscriptions["subscriptions"].map { |subscription_data| Subscription.new(subscription_id: subscription_data["_id"],
208
+ url: subscription_data["url"],
209
+ payload: subscription_data)}
210
+ Subscriptions.new(limit: subscriptions["limit"],
211
+ page: subscriptions["page"],
212
+ page_count: subscriptions["page_count"],
213
+ subscriptions_count: subscriptions["subscription_count"],
214
+ payload: response
215
+ )
216
+ end
217
+
218
+ # Queries Synapse API for a subscription by subscription_id
219
+ # @param subscription_id [String]
220
+ # @return [Synapse::Subscription]
221
+ def get_subscription(subscription_id:)
222
+ path = subscriptions_path + "/#{subscription_id}"
223
+ response = client.get(path)
224
+ Subscription.new(subscription_id: response["_id"], url: response["url"], payload: response)
225
+ end
226
+
227
+ # Updates subscription platform subscription
228
+ # @param subscription_id [String]
229
+ # @param body [Hash]
230
+ # see https://docs.synapsefi.com/docs/update-subscription
231
+ # @return [Synapse::Subscription]
232
+ def update_subscriptions(subscription_id:, body:)
233
+ path = subscriptions_path + "/#{subscription_id}"
234
+
235
+ response = client.patch(path, body)
236
+ Subscription.new(subscription_id: response["_id"], url: response["url"], payload: response)
237
+ end
238
+
239
+ # Returns all of the webhooks belonging to client
240
+ # @return [Hash]
241
+ def webhook_logs()
242
+ path = subscriptions_path + "/logs"
243
+ client.get(path)
244
+ end
245
+
246
+ # Issues public key for client
247
+ # @param scope [String]
248
+ # @see https://docs.synapsefi.com/docs/issuing-public-key
249
+ # @note valid scope "OAUTH|POST,USERS|POST,USERS|GET,USER|GET,USER|PATCH,SUBSCRIPTIONS|GET,SUBSCRIPTIONS|POST,SUBSCRIPTION|GET,SUBSCRIPTION|PATCH,CLIENT|REPORTS,CLIENT|CONTROLS"
250
+ def issue_public_key(scope:)
251
+ path = '/client?issue_public_key=YES'
252
+ path += "&scope=#{scope}"
253
+ response = client.get(path)
254
+ response[ "public_key_obj"]
255
+ end
256
+
257
+ # Queries Synapse API for ATMS nearby
258
+ # @param zip [String]
259
+ # @param radius [String]
260
+ # @param lat [String]
261
+ # @param lon [String]
262
+ # @see https://docs.synapsefi.com/docs/locate-atms
263
+ # @return [Hash]
264
+ def locate_atm(**options)
265
+ params = VALID_QUERY_PARAMS.map do |p|
266
+ options[p] ? "#{p}=#{options[p]}" : nil
267
+ end.compact
268
+
269
+ path = "/nodes/atms?"
270
+ path += params.join('&') if params.any?
271
+ atms = client.get(path)
272
+ atms
273
+ end
274
+
275
+ # Queries Synapse API for Crypto Currencies Quotes
276
+ # @return API response [Hash]
277
+ def get_crypto_quotes()
278
+ path = '/nodes/crypto-quotes'
279
+ params = VALID_QUERY_PARAMS.map do |p|
280
+ options[p] ? "#{p}=#{options[p]}" : nil
281
+ end.compact
282
+
283
+ path += '?' + params.join('&') if params.any?
284
+ quotes = client.get(path)
285
+ quotes
286
+ end
287
+
288
+ # Queries Synapse API for Crypto Currencies Market data
289
+ # @param limit [Integer]
290
+ # @param currency [String]
291
+ # @return API response [Hash]
292
+ def get_crypto_market_data(**options)
293
+ path = '/nodes/crypto-market-watch'
294
+
295
+ params = VALID_QUERY_PARAMS.map do |p|
296
+ options[p] ? "#{p}=#{options[p]}" : nil
297
+ end.compact
298
+
299
+ path += '?' + params.join('&') if params.any?
300
+
301
+ data = client.get(path)
302
+ data
303
+ end
304
+
305
+ private
306
+ def user_path(user_id: nil, **options)
307
+ path = "/users"
308
+ path += "/#{user_id}" if user_id
309
+
310
+ params = VALID_QUERY_PARAMS.map do |p|
311
+ options[p] ? "#{p}=#{options[p]}" : nil
312
+ end.compact
313
+
314
+ path += '?' + params.join('&') if params.any?
315
+ path
316
+ end
317
+
318
+ def transactions_path(user_id: nil, node_id: nil, **options)
319
+ path = "/users/#{user_id}/trans"
320
+ params = VALID_QUERY_PARAMS.map do |p|
321
+ options[p] ? "#{p}=#{options[p]}" : nil
322
+ end.compact
323
+
324
+ path += '?' + params.join('&') if params.any?
325
+ path
326
+ end
327
+
328
+ def nodes_path(**options)
329
+ path = "/nodes"
330
+ params = VALID_QUERY_PARAMS.map do |p|
331
+ options[p] ? "#{p}=#{options[p]}" : nil
332
+ end.compact
333
+
334
+ path += '?' + params.join('&') if params.any?
335
+ path
336
+ end
337
+
338
+ def institutions_path(**options)
339
+ path = "/institutions"
340
+ params = VALID_QUERY_PARAMS.map do |p|
341
+ options[p] ? "#{p}=#{options[p]}" : nil
342
+ end.compact
343
+
344
+ path += '?' + params.join('&') if params.any?
345
+ path
346
+ end
347
+
348
+ def subscriptions_path(**options)
349
+ path = "/subscriptions"
350
+ params = VALID_QUERY_PARAMS.map do |p|
351
+ options[p] ? "#{p}=#{options[p]}" : nil
352
+ end.compact
353
+
354
+ path += '?' + params.join('&') if params.any?
355
+ path
356
+ end
357
+ end
358
+ end
359
+
@@ -0,0 +1,111 @@
1
+ module Synapse
2
+ # Custom class for handling HTTP and API errors.
3
+ class Error < StandardError
4
+ # Raised on a 4xx HTTP status code
5
+ ClientError = Class.new(self)
6
+
7
+ # Raised on the HTTP status code 202
8
+ Accepted = Class.new(ClientError)
9
+
10
+ # Raised on the HTTP status code 400
11
+ BadRequest = Class.new(ClientError)
12
+
13
+ # Raised on the HTTP status code 401
14
+ Unauthorized = Class.new(ClientError)
15
+
16
+ # Raised on the HTTP status code 402
17
+ RequestDeclined = Class.new(ClientError)
18
+
19
+ # Raised on the HTTP status code 404
20
+ NotFound = Class.new(ClientError)
21
+
22
+ # Raised on the HTTP status code 409
23
+ Conflict = Class.new(ClientError)
24
+
25
+ # Raised on the HTTP status code 429
26
+ TooManyRequests = Class.new(ClientError)
27
+
28
+ # Raised on a 5xx HTTP status code
29
+ ServerError = Class.new(self)
30
+
31
+ # Raised on the HTTP status code 500
32
+ InternalServerError = Class.new(ServerError)
33
+
34
+ # Raised on the HTTP status code 503
35
+ ServiceUnavailable = Class.new(ServerError)
36
+
37
+ # HTTP status code to Error subclass mapping
38
+ ERRORS = {
39
+ '202' => Synapse::Error::Accepted,
40
+ '400' => Synapse::Error::BadRequest,
41
+ '401' => Synapse::Error::Unauthorized,
42
+ '402' => Synapse::Error::RequestDeclined,
43
+ '404' => Synapse::Error::NotFound,
44
+ '409' => Synapse::Error::Conflict,
45
+ '429' => Synapse::Error::TooManyRequests,
46
+ '500' => Synapse::Error::InternalServerError,
47
+ '503' => Synapse::Error::ServiceUnavailable,
48
+ }.freeze
49
+
50
+ # The SynapsePay API Error Code
51
+ #
52
+ # @return [Integer]
53
+ attr_reader :code, :http_code
54
+
55
+ # The JSON HTTP response in Hash form
56
+ # @return [Hash]
57
+ attr_reader :response, :message
58
+
59
+ class << self
60
+ # Create a new error from an HTTP response
61
+ # @param body [String]
62
+ # @param code [Integer]
63
+ # @param http_code [Integer]
64
+ # @return [Synapse::Error]
65
+ def from_response(body)
66
+ message, error_code, http_code = parse_error(body)
67
+ http_code = http_code.to_s
68
+ klass = ERRORS[http_code] || Synapse::Error
69
+ klass.new(message: message, code: error_code, response: body, http_code: http_code)
70
+ end
71
+
72
+ private
73
+
74
+ def parse_error(body)
75
+
76
+ if body.nil? || body.empty?
77
+ ['', nil, nil]
78
+
79
+ elsif body['mfa'] && body.is_a?(Hash)
80
+ ["#{body['mfa']["message"] } acces_token: #{body['mfa']["access_token"]}", body['error_code'], body['http_code']]
81
+ elsif body[:mfa] && body.is_a?(Hash)
82
+ ["#{body[:mfa][:message] } acces_token: #{body[:mfa][:access_token]}", body[:error_code], body[:http_code]]
83
+
84
+ elsif body['message'] && body.is_a?(Hash)
85
+ [body["message"]["en"], body['error_code'], body['http_code']]
86
+ elsif body[:message] && body.is_a?(Hash)
87
+ [body[:message][:en], body[:error_code], body[:http_code]]
88
+
89
+ elsif body.is_a?(Hash) && body['error'].is_a?(Hash)
90
+ [body['error']['en'], body['error_code'], body['http_code']]
91
+ elsif body.is_a?(Hash) && body[:error].is_a?(Hash)
92
+ [body[:error][:en], body[:error_code], body[:http_code]]
93
+
94
+ end
95
+ end
96
+ end
97
+
98
+ # Initializes a new Error object
99
+ # @param message [Exception, String]
100
+ # @param code [Integer]
101
+ # @param response [Hash]
102
+ # @return [Synapse::Error]
103
+ def initialize(message: '', code: nil, response: {}, http_code:)
104
+ super(message)
105
+ @code = code
106
+ @response = response
107
+ @message = message
108
+ @http_code = http_code
109
+ end
110
+ end
111
+ end