synapseruby 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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