synapse_pay_rest 0.0.15 → 2.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 +4 -4
- data/.gitignore +7 -2
- data/Gemfile.lock +10 -6
- data/LICENSE +20 -0
- data/README.md +80 -22
- data/lib/synapse_pay_rest.rb +65 -21
- data/lib/synapse_pay_rest/api/nodes.rb +93 -19
- data/lib/synapse_pay_rest/api/transactions.rb +103 -0
- data/lib/synapse_pay_rest/api/users.rb +101 -41
- data/lib/synapse_pay_rest/client.rb +49 -0
- data/lib/synapse_pay_rest/error.rb +8 -2
- data/lib/synapse_pay_rest/http_client.rb +94 -27
- data/lib/synapse_pay_rest/models/node/ach_us_node.rb +111 -0
- data/lib/synapse_pay_rest/models/node/base_node.rb +192 -0
- data/lib/synapse_pay_rest/models/node/eft_ind_node.rb +19 -0
- data/lib/synapse_pay_rest/models/node/eft_node.rb +27 -0
- data/lib/synapse_pay_rest/models/node/eft_np_node.rb +19 -0
- data/lib/synapse_pay_rest/models/node/iou_node.rb +27 -0
- data/lib/synapse_pay_rest/models/node/node.rb +99 -0
- data/lib/synapse_pay_rest/models/node/reserve_us_node.rb +23 -0
- data/lib/synapse_pay_rest/models/node/synapse_ind_node.rb +22 -0
- data/lib/synapse_pay_rest/models/node/synapse_node.rb +25 -0
- data/lib/synapse_pay_rest/models/node/synapse_np_node.rb +22 -0
- data/lib/synapse_pay_rest/models/node/synapse_us_node.rb +23 -0
- data/lib/synapse_pay_rest/models/node/unverified_node.rb +73 -0
- data/lib/synapse_pay_rest/models/node/wire_int_node.rb +23 -0
- data/lib/synapse_pay_rest/models/node/wire_node.rb +38 -0
- data/lib/synapse_pay_rest/models/node/wire_us_node.rb +23 -0
- data/lib/synapse_pay_rest/models/transaction/transaction.rb +212 -0
- data/lib/synapse_pay_rest/models/user/base_document.rb +346 -0
- data/lib/synapse_pay_rest/models/user/document.rb +71 -0
- data/lib/synapse_pay_rest/models/user/physical_document.rb +29 -0
- data/lib/synapse_pay_rest/models/user/question.rb +45 -0
- data/lib/synapse_pay_rest/models/user/social_document.rb +7 -0
- data/lib/synapse_pay_rest/models/user/user.rb +593 -0
- data/lib/synapse_pay_rest/models/user/virtual_document.rb +77 -0
- data/lib/synapse_pay_rest/version.rb +2 -1
- data/samples.md +391 -219
- data/synapse_pay_rest.gemspec +13 -12
- metadata +78 -24
- data/lib/synapse_pay_rest/api/trans.rb +0 -38
@@ -3,62 +3,123 @@ require 'base64'
|
|
3
3
|
require 'open-uri'
|
4
4
|
|
5
5
|
module SynapsePayRest
|
6
|
-
#
|
6
|
+
# Wrapper class for /users endpoints
|
7
7
|
class Users
|
8
|
-
#
|
8
|
+
# Valid optional args for #get
|
9
|
+
# @todo Should refactor this to HTTPClient
|
9
10
|
VALID_QUERY_PARAMS = [:query, :page, :per_page].freeze
|
10
11
|
|
12
|
+
# @!attribute [rw] client
|
13
|
+
# @return [SynapsePayRest::HTTPClient]
|
11
14
|
attr_accessor :client
|
12
15
|
|
16
|
+
# @param [SynapsePayRest::HTTPClient]
|
13
17
|
def initialize(client)
|
14
18
|
@client = client
|
15
19
|
end
|
16
20
|
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
# Sends a GET request to /users endpoint and returns the response. Queries a
|
22
|
+
# specific user_id if user_id supplied, else queries all users.
|
23
|
+
#
|
24
|
+
# @param user_id [String,void] id of the user
|
25
|
+
# @param query [String] (optional) response will be filtered to
|
26
|
+
# users with matching name/email
|
27
|
+
# @param page [String,Integer] (optional) response will default to 1
|
28
|
+
# @param per_page [String,Integer] (optional) response will default to 20
|
29
|
+
#
|
30
|
+
# @raise [SynapsePayRest::Error] may return subclasses of error based on
|
31
|
+
# HTTP response from API
|
32
|
+
#
|
33
|
+
# @return [Hash] API response
|
34
|
+
#
|
35
|
+
# @todo Probably should use CGI or RestClient's param builder instead of
|
36
|
+
# rolling our own, probably error-prone and untested version
|
37
|
+
# https://github.com/rest-client/rest-client#usage-raw-url
|
25
38
|
def get(user_id: nil, **options)
|
26
39
|
path = create_user_path(user_id: user_id)
|
27
40
|
|
28
|
-
# factor single user and all users into separate methods
|
29
41
|
if user_id
|
30
42
|
response = client.get(path)
|
31
43
|
client.update_headers(user_id: response['_id']) if response['_id']
|
32
44
|
return response
|
33
45
|
end
|
34
46
|
|
35
|
-
# Should factor this out into HTTPClient and separate args for paginate/search(name/email)/per_page
|
36
47
|
params = VALID_QUERY_PARAMS.map do |p|
|
37
48
|
options[p] ? "#{p}=#{options[p]}" : nil
|
38
49
|
end.compact
|
39
50
|
|
40
|
-
# Probably should use CGI or RestClient's param builder instead of
|
41
|
-
# rolling our own, probably error-prone and untested version
|
42
|
-
# https://github.com/rest-client/rest-client#usage-raw-url
|
43
51
|
path += '?' + params.join('&') if params.any?
|
44
52
|
client.get(path)
|
45
53
|
end
|
46
54
|
|
47
|
-
|
48
|
-
|
49
|
-
|
55
|
+
# Sends a POST request to /users endpoint to create a new user, and returns
|
56
|
+
# the response.
|
57
|
+
#
|
58
|
+
# @param payload [Hash]
|
59
|
+
# @see https://docs.synapsepay.com/docs/create-a-user payload structure
|
60
|
+
#
|
61
|
+
# @raise [SynapsePayRest::Error] may return subclasses of error based on
|
62
|
+
# HTTP response from API
|
63
|
+
#
|
64
|
+
# @return [Hash] API response
|
65
|
+
def create(payload:)
|
66
|
+
path = create_user_path
|
67
|
+
response = client.post(path, payload)
|
50
68
|
client.update_headers(user_id: response['_id']) if response['_id']
|
51
69
|
response
|
52
70
|
end
|
53
71
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
72
|
+
# Sends a POST request to /oauth/:user_id endpoint to obtain a new oauth key
|
73
|
+
# and update the client's headers, and returns the response
|
74
|
+
#
|
75
|
+
# @param payload [Hash]
|
76
|
+
# @see https://docs.synapsepay.com/docs/get-oauth_key-refresh-token payload structure
|
77
|
+
#
|
78
|
+
# @raise [SynapsePayRest::Error] may return subclasses of error based on
|
79
|
+
# HTTP response from API
|
80
|
+
#
|
81
|
+
# @return [Hash] API response
|
82
|
+
def refresh(payload:)
|
83
|
+
path = "/oauth/#{@client.user_id}"
|
84
|
+
response = @client.post(path, payload)
|
85
|
+
client.update_headers(oauth_key: response['oauth_key']) if response['oauth_key']
|
58
86
|
response
|
59
87
|
end
|
60
88
|
|
61
|
-
|
89
|
+
# Sends a PATCH request to /users endpoint, updating the current user,
|
90
|
+
# which can also include adding/updating user CIP documents, and returns the response.
|
91
|
+
#
|
92
|
+
# @param payload [Hash]
|
93
|
+
# @see https://docs.synapsepay.com/docs/update-user payload structure for
|
94
|
+
# updating user
|
95
|
+
# @see https://docs.synapsepay.com/docs/adding-documents payload structure
|
96
|
+
# for adding documents to user
|
97
|
+
# @see https://docs.synapsepay.com/docs/updating-existing-document payload
|
98
|
+
# structure for updating user's existing documents
|
99
|
+
#
|
100
|
+
# @raise [SynapsePayRest::Error] may return subclasses of error based on
|
101
|
+
# HTTP response from API
|
102
|
+
#
|
103
|
+
# @return [Hash] API response
|
104
|
+
def update(payload:)
|
105
|
+
path = create_user_path(user_id: client.user_id)
|
106
|
+
response = client.patch(path, payload)
|
107
|
+
client.update_headers(user_id: response['_id']) if response['_id']
|
108
|
+
response
|
109
|
+
end
|
110
|
+
# Alias for #update (legacy name)
|
111
|
+
alias_method :answer_kba, :update
|
112
|
+
# Alias for #update (legacy name)
|
113
|
+
alias_method :add_doc, :update
|
114
|
+
|
115
|
+
# Converts a file to base64 for use in payloads for adding physical documents.
|
116
|
+
#
|
117
|
+
# @param file_path [String]
|
118
|
+
# @param file_type [String,void] (optional) MIME type of file (will attempt
|
119
|
+
# to autodetect if nil)
|
120
|
+
#
|
121
|
+
# @return [String] base64 encoded file
|
122
|
+
def encode_attachment(file_path:, file_type: nil)
|
62
123
|
# try to find file_type
|
63
124
|
if file_type.nil?
|
64
125
|
content_types = MIME::Types.type_for(file_path)
|
@@ -67,7 +128,7 @@ module SynapsePayRest
|
|
67
128
|
|
68
129
|
# if file_type not found in previous step
|
69
130
|
if file_type.nil?
|
70
|
-
raise(
|
131
|
+
raise('File type not found. Specify a file_type argument.')
|
71
132
|
end
|
72
133
|
|
73
134
|
file_contents = open(file_path) { |f| f.read }
|
@@ -76,33 +137,32 @@ module SynapsePayRest
|
|
76
137
|
mime_padding + encoded
|
77
138
|
end
|
78
139
|
|
79
|
-
#
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
update(payload: payload)
|
87
|
-
end
|
88
|
-
|
89
|
-
# deprecated
|
90
|
-
def attach_file(file_path: raise("file_path is required"))
|
91
|
-
warn caller.first + " DEPRECATION WARNING: the method #{self.class}##{__method__} is deprecated. Use SynapsePayRest::Users::update with encode_attachment instead."
|
140
|
+
# Detects the file type of the file and calls #attach_file_with_file_type
|
141
|
+
# on it.
|
142
|
+
#
|
143
|
+
# @param file_path [String]
|
144
|
+
# @deprecated Use #update with KYC 2.0 payload instead.
|
145
|
+
def attach_file(file_path:)
|
146
|
+
warn caller.first + " DEPRECATION WARNING: #{self.class}##{__method__} is deprecated. Use #update with encode_attachment instead."
|
92
147
|
|
93
148
|
file_contents = open(file_path) { |f| f.read }
|
94
149
|
content_types = MIME::Types.type_for(file_path)
|
95
150
|
file_type = content_types.first.content_type if content_types.any?
|
96
151
|
if file_type.nil?
|
97
|
-
raise(
|
152
|
+
raise('File type not found. Use attach_file_with_file_type(file_path: <file_path>, file_type: <file_type>)')
|
98
153
|
else
|
99
154
|
attach_file_with_file_type(file_path: file_path, file_type: file_type)
|
100
155
|
end
|
101
156
|
end
|
102
157
|
|
103
|
-
# deprecated
|
104
|
-
|
105
|
-
|
158
|
+
# Converts a file to base64 and sends it to the API using deprecated KYC 1.0
|
159
|
+
# call.
|
160
|
+
#
|
161
|
+
# @param file_path [String]
|
162
|
+
# @param file_type [String] MIME type
|
163
|
+
# @deprecated Use #update with KYC 2.0 payload instead.
|
164
|
+
def attach_file_with_file_type(file_path:, file_type:)
|
165
|
+
warn caller.first + " DEPRECATION WARNING: #{self.class}##{__method__} is deprecated. Use #update with encode_attachment instead."
|
106
166
|
|
107
167
|
path = create_user_path(user_id: @client.user_id)
|
108
168
|
file_contents = open(file_path) { |f| f.read }
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Initializes various wrapper settings such as development mode and request
|
3
|
+
# header values. Also stores and initializes endpoint class instances
|
4
|
+
# (Users, Nodes, Transactions) for making API calls.
|
5
|
+
class Client
|
6
|
+
# @!attribute [rw] http_client
|
7
|
+
# @return [SynapsePayRest::HTTPClient]
|
8
|
+
# @!attribute [rw] users
|
9
|
+
# @return [SynapsePayRest::Users]
|
10
|
+
# @!attribute [rw] nodes
|
11
|
+
# @return [SynapsePayRest::Nodes]
|
12
|
+
# @!attribute [rw] transactions
|
13
|
+
# @return [SynapsePayRest::Transactions]
|
14
|
+
attr_accessor :http_client, :users, :nodes, :transactions
|
15
|
+
|
16
|
+
# Alias for #transactions (legacy name)
|
17
|
+
alias_method :trans, :transactions
|
18
|
+
# Alias for #http_client (legacy name)
|
19
|
+
alias_method :client, :http_client
|
20
|
+
|
21
|
+
# @param client_id [String] should be stored in environment variable
|
22
|
+
# @param client_secret [String] should be stored in environment variable
|
23
|
+
# @param ip_address [String] user's IP address
|
24
|
+
# @param fingerprint [String] a hashed value, either unique to user or static
|
25
|
+
# @param user_id [String] (optional)
|
26
|
+
# @param development_mode [String] default true
|
27
|
+
# @param logging [Boolean] (optional) logs to stdout when true
|
28
|
+
# @param log_to [String] (optional) file path to log to file (logging must be true)
|
29
|
+
def initialize(client_id:, client_secret:, ip_address:, fingerprint: nil,
|
30
|
+
user_id: nil, development_mode: true, **options)
|
31
|
+
base_url = if development_mode
|
32
|
+
'https://sandbox.synapsepay.com/api/3'
|
33
|
+
else
|
34
|
+
'https://synapsepay.com/api/3'
|
35
|
+
end
|
36
|
+
|
37
|
+
@http_client = HTTPClient.new(base_url: base_url,
|
38
|
+
client_id: client_id,
|
39
|
+
client_secret: client_secret,
|
40
|
+
user_id: user_id,
|
41
|
+
fingerprint: fingerprint,
|
42
|
+
ip_address: ip_address,
|
43
|
+
**options)
|
44
|
+
@users = Users.new @http_client
|
45
|
+
@nodes = Nodes.new @http_client
|
46
|
+
@transactions = Transactions.new @http_client
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module SynapsePayRest
|
2
|
+
# Custom class for handling HTTP and API errors.
|
2
3
|
class Error < StandardError
|
3
4
|
# Raised on a 4xx HTTP status code
|
4
5
|
ClientError = Class.new(self)
|
@@ -48,6 +49,10 @@ module SynapsePayRest
|
|
48
49
|
# Raised on the HTTP status code 504
|
49
50
|
GatewayTimeout = Class.new(ServerError)
|
50
51
|
|
52
|
+
# HTTP status code to Error subclass mapping
|
53
|
+
#
|
54
|
+
# @todo need to add an error message for various 202 cases (fingerprint, mfa, etc)
|
55
|
+
# @todo doesn't do well when there's an html response from nginx for bad gateway/timeout
|
51
56
|
ERRORS = {
|
52
57
|
400 => SynapsePayRest::Error::BadRequest,
|
53
58
|
401 => SynapsePayRest::Error::Unauthorized,
|
@@ -62,8 +67,8 @@ module SynapsePayRest
|
|
62
67
|
500 => SynapsePayRest::Error::InternalServerError,
|
63
68
|
502 => SynapsePayRest::Error::BadGateway,
|
64
69
|
503 => SynapsePayRest::Error::ServiceUnavailable,
|
65
|
-
504 => SynapsePayRest::Error::GatewayTimeout
|
66
|
-
}
|
70
|
+
504 => SynapsePayRest::Error::GatewayTimeout
|
71
|
+
}.freeze
|
67
72
|
|
68
73
|
# The SynapsePay API Error Code
|
69
74
|
#
|
@@ -82,6 +87,7 @@ module SynapsePayRest
|
|
82
87
|
# @param code [Integer]
|
83
88
|
# @return [SynapsePayRest::Error]
|
84
89
|
def error_from_response(body, code)
|
90
|
+
code = code.to_i
|
85
91
|
klass = ERRORS[code] || SynapsePayRest::Error
|
86
92
|
message, error_code = parse_error(body)
|
87
93
|
klass.new(message: message, code: error_code, response: body)
|
@@ -2,57 +2,124 @@ require 'rest-client'
|
|
2
2
|
require 'json'
|
3
3
|
|
4
4
|
module SynapsePayRest
|
5
|
+
# Wrapper for HTTP requests using RestClient.
|
5
6
|
class HTTPClient
|
6
|
-
|
7
|
+
# @!attribute [rw] base_url
|
8
|
+
# @return [String] the base url of the API (production or sandbox)
|
9
|
+
# @!attribute [rw] config
|
10
|
+
# @return [Hash] various settings related to request headers
|
11
|
+
# @!attribute [rw] user_id
|
12
|
+
# @return [String] the user_id which is stored upon a call to Users#get or Users#create
|
13
|
+
attr_accessor :base_url, :config, :user_id
|
7
14
|
|
8
|
-
|
9
|
-
|
15
|
+
# @param base_url [String] the base url of the API (production or sandbox)
|
16
|
+
# @param client_id [String]
|
17
|
+
# @param client_secret [String]
|
18
|
+
# @param fingerprint [String]
|
19
|
+
# @param ip_address [String]
|
20
|
+
# @param user_id [String] (optional) automatically stored on call to Users#get or Users#create
|
21
|
+
# @param logging [Boolean] (optional) logs to stdout when true
|
22
|
+
# @param log_to [String] (optional) file path to log to file (logging must be true)
|
23
|
+
def initialize(base_url:, client_id:, fingerprint:, ip_address:, client_secret:,
|
24
|
+
user_id: nil, **options)
|
25
|
+
|
26
|
+
log_to = options[:log_to] || 'stdout'
|
27
|
+
RestClient.log = log_to if options[:logging]
|
28
|
+
|
29
|
+
@config = {
|
30
|
+
fingerprint: fingerprint,
|
31
|
+
client_id: client_id,
|
32
|
+
client_secret: client_secret
|
33
|
+
}
|
10
34
|
@base_url = base_url
|
11
|
-
|
12
|
-
@user_id = user_id
|
35
|
+
@user_id = user_id
|
13
36
|
end
|
14
37
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
38
|
+
# Returns headers for HTTP requests.
|
39
|
+
#
|
40
|
+
# @return [Hash]
|
41
|
+
def headers
|
42
|
+
user = "#{config[:oauth_key]}|#{config[:fingerprint]}"
|
43
|
+
gateway = "#{config[:client_id]}|#{config[:client_secret]}"
|
19
44
|
headers = {
|
20
|
-
:content_type
|
21
|
-
:accept
|
45
|
+
:content_type => :json,
|
46
|
+
:accept => :json,
|
22
47
|
'X-SP-GATEWAY' => gateway,
|
23
|
-
'X-SP-USER'
|
24
|
-
'X-SP-USER-IP' => config[
|
48
|
+
'X-SP-USER' => user,
|
49
|
+
'X-SP-USER-IP' => config[:ip_address]
|
25
50
|
}
|
26
51
|
end
|
52
|
+
# Alias for #headers (legacy name)
|
53
|
+
alias_method :get_headers, :headers
|
27
54
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
55
|
+
# Updates headers and/or user_id.
|
56
|
+
#
|
57
|
+
# @param user_id [String,void]
|
58
|
+
# @param oauth_key [String,void]
|
59
|
+
# @param fingerprint [String,void]
|
60
|
+
# @param client_id [String,void]
|
61
|
+
# @param client_secret [String,void]
|
62
|
+
# @param ip_address [String,void]
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
def update_headers(user_id: nil, oauth_key: nil, fingerprint: nil,
|
66
|
+
client_id: nil, client_secret: nil, ip_address: nil)
|
67
|
+
self.user_id = user_id if user_id
|
68
|
+
config[:fingerprint] = fingerprint if fingerprint
|
69
|
+
config[:oauth_key] = oauth_key if oauth_key
|
70
|
+
config[:client_id] = client_id if client_id
|
71
|
+
config[:client_secret] = client_secret if client_secret
|
72
|
+
config[:ip_address] = ip_address if ip_address
|
73
|
+
nil
|
37
74
|
end
|
38
75
|
|
76
|
+
# Sends a POST request to the given path with the given payload.
|
77
|
+
#
|
78
|
+
# @param path [String]
|
79
|
+
# @param payload [Hash]
|
80
|
+
#
|
81
|
+
# @raise [SynapsePayRest::Error] subclass depends on HTTP response
|
82
|
+
#
|
83
|
+
# @return [Hash] API response
|
39
84
|
def post(path, payload)
|
40
|
-
response = with_error_handling { RestClient.post(full_url(path), payload.to_json,
|
85
|
+
response = with_error_handling { RestClient.post(full_url(path), payload.to_json, headers) }
|
41
86
|
JSON.parse(response)
|
42
87
|
end
|
43
88
|
|
89
|
+
# Sends a PATCH request to the given path with the given payload.
|
90
|
+
#
|
91
|
+
# @param path [String]
|
92
|
+
# @param payload [Hash]
|
93
|
+
#
|
94
|
+
# @raise [SynapsePayRest::Error] subclass depends on HTTP response
|
95
|
+
#
|
96
|
+
# @return [Hash] API response
|
44
97
|
def patch(path, payload)
|
45
|
-
response = with_error_handling { RestClient.patch(full_url(path), payload.to_json,
|
98
|
+
response = with_error_handling { RestClient.patch(full_url(path), payload.to_json, headers) }
|
46
99
|
JSON.parse(response)
|
47
100
|
end
|
48
101
|
|
102
|
+
# Sends a GET request to the given path with the given payload.
|
103
|
+
#
|
104
|
+
# @param path [String]
|
105
|
+
#
|
106
|
+
# @raise [SynapsePayRest::Error] subclass depends on HTTP response
|
107
|
+
#
|
108
|
+
# @return [Hash] API response
|
49
109
|
def get(path)
|
50
|
-
response = with_error_handling { RestClient.get(full_url(path),
|
110
|
+
response = with_error_handling { RestClient.get(full_url(path), headers) }
|
51
111
|
JSON.parse(response)
|
52
112
|
end
|
53
113
|
|
114
|
+
# Sends a DELETE request to the given path with the given payload.
|
115
|
+
#
|
116
|
+
# @param path [String]
|
117
|
+
#
|
118
|
+
# @raise [SynapsePayRest::Error] subclass depends on HTTP response
|
119
|
+
#
|
120
|
+
# @return [Hash] API response
|
54
121
|
def delete(path)
|
55
|
-
response = with_error_handling { RestClient.delete(full_url(path),
|
122
|
+
response = with_error_handling { RestClient.delete(full_url(path), headers) }
|
56
123
|
JSON.parse(response)
|
57
124
|
end
|
58
125
|
|
@@ -66,7 +133,7 @@ module SynapsePayRest
|
|
66
133
|
yield
|
67
134
|
rescue RestClient::Exception => e
|
68
135
|
body = JSON.parse(e.response.body)
|
69
|
-
raise Error.error_from_response(body,
|
136
|
+
raise Error.error_from_response(body, body['error_code'])
|
70
137
|
end
|
71
138
|
end
|
72
139
|
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Represents a US bank account for processing ACH payments. Can be added by
|
3
|
+
# account/routing number or via bank login for selected banks (recommended).
|
4
|
+
#
|
5
|
+
# @see https://docs.synapsepay.com/docs/node-resources nodes documentation
|
6
|
+
# @see https://synapsepay.com/api/v3/institutions/show valid banks for login
|
7
|
+
class AchUsNode < BaseNode
|
8
|
+
class << self
|
9
|
+
# Creates an ACH-US node via bank login, belonging to user supplied.
|
10
|
+
#
|
11
|
+
# @param user [SynapsePayRest::User] the user to whom the node belongs
|
12
|
+
# @param bank_name [String]
|
13
|
+
# @see https://synapsepay.com/api/v3/institutions/show valid bank_name options
|
14
|
+
# @param username [String] user's bank login username
|
15
|
+
# @param password [String] user's bank login password
|
16
|
+
#
|
17
|
+
# @raise [SynapsePayRest::Error]
|
18
|
+
#
|
19
|
+
# @return [Array<SynapsePayRest::AchUsNode>] may contain multiple nodes (checking and/or savings)s
|
20
|
+
def create_via_bank_login(user:, bank_name:, username:, password:)
|
21
|
+
raise ArgumentError, 'user must be a User object' unless user.is_a?(User)
|
22
|
+
raise ArgumentError, 'bank_name must be a String' unless bank_name.is_a?(String)
|
23
|
+
raise ArgumentError, 'username must be a String' unless username.is_a?(String)
|
24
|
+
raise ArgumentError, 'password must be a String' unless password.is_a?(String)
|
25
|
+
|
26
|
+
payload = payload_for_create_via_bank_login(bank_name: bank_name, username: username, password: password)
|
27
|
+
user.authenticate
|
28
|
+
response = user.client.nodes.add(payload: payload)
|
29
|
+
# MFA questions
|
30
|
+
if response['mfa']
|
31
|
+
create_unverified_node(user, response)
|
32
|
+
else
|
33
|
+
create_multiple_from_response(user, response['nodes'])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# Converts args into payload for request JSON.
|
40
|
+
def payload_for_create(nickname:, account_number:, routing_number:,
|
41
|
+
account_type:, account_class:, **options)
|
42
|
+
payload = {
|
43
|
+
'type' => 'ACH-US',
|
44
|
+
'info' => {
|
45
|
+
'nickname' => nickname,
|
46
|
+
'account_num' => account_number,
|
47
|
+
'routing_num' => routing_number,
|
48
|
+
'type' => account_type,
|
49
|
+
'class' => account_class
|
50
|
+
}
|
51
|
+
}
|
52
|
+
# optional payload fields
|
53
|
+
extra = {}
|
54
|
+
extra['supp_id'] = options[:supp_id] if options[:supp_id]
|
55
|
+
extra['gateway_restricted'] = options[:gateway_restricted] if options[:gateway_restricted]
|
56
|
+
payload['extra'] = extra if extra.any?
|
57
|
+
|
58
|
+
payload
|
59
|
+
end
|
60
|
+
|
61
|
+
def payload_for_create_via_bank_login(bank_name:, username:, password:)
|
62
|
+
{
|
63
|
+
'type' => 'ACH-US',
|
64
|
+
'info' => {
|
65
|
+
'bank_id' => username,
|
66
|
+
'bank_pw' => password,
|
67
|
+
'bank_name' => bank_name
|
68
|
+
}
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
# Creates a SynapsePayRest::UnverifiedNode when bank responds with MFA
|
73
|
+
# questions.
|
74
|
+
def create_unverified_node(user, response)
|
75
|
+
UnverifiedNode.new(
|
76
|
+
user: user,
|
77
|
+
mfa_access_token: response['mfa']['access_token'],
|
78
|
+
mfa_message: response['mfa']['message'],
|
79
|
+
mfa_verified: false
|
80
|
+
)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Verifies the microdeposit amounts sent to the user's account to verify
|
85
|
+
# a node added by account and routing number. Node will be locked if max
|
86
|
+
# tries exceeded.
|
87
|
+
#
|
88
|
+
# @param amount1 [Float]
|
89
|
+
# @param amount2 [Float]
|
90
|
+
#
|
91
|
+
# @raise [SynapsePayRest::Error] if wrong guess or HTTP error
|
92
|
+
#
|
93
|
+
# @return [SynapsePayRest::AchUsNode]
|
94
|
+
def verify_microdeposits(amount1:, amount2:)
|
95
|
+
[amount1, amount2].each do |arg|
|
96
|
+
raise ArgumentError, "#{arg} must be float" unless arg.is_a?(Float)
|
97
|
+
end
|
98
|
+
|
99
|
+
payload = verify_microdeposits_payload(amount1: amount1, amount2: amount2)
|
100
|
+
response = user.client.nodes.patch(node_id: id, payload: payload)
|
101
|
+
self.class.create_from_response(user, response)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
# Converts the data to hash format for request JSON.
|
107
|
+
def verify_microdeposits_payload(amount1:, amount2:)
|
108
|
+
{'micro' => [amount1, amount2]}
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|