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