synapse_fi 0.0.1

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