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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +7 -2
  3. data/Gemfile.lock +10 -6
  4. data/LICENSE +20 -0
  5. data/README.md +80 -22
  6. data/lib/synapse_pay_rest.rb +65 -21
  7. data/lib/synapse_pay_rest/api/nodes.rb +93 -19
  8. data/lib/synapse_pay_rest/api/transactions.rb +103 -0
  9. data/lib/synapse_pay_rest/api/users.rb +101 -41
  10. data/lib/synapse_pay_rest/client.rb +49 -0
  11. data/lib/synapse_pay_rest/error.rb +8 -2
  12. data/lib/synapse_pay_rest/http_client.rb +94 -27
  13. data/lib/synapse_pay_rest/models/node/ach_us_node.rb +111 -0
  14. data/lib/synapse_pay_rest/models/node/base_node.rb +192 -0
  15. data/lib/synapse_pay_rest/models/node/eft_ind_node.rb +19 -0
  16. data/lib/synapse_pay_rest/models/node/eft_node.rb +27 -0
  17. data/lib/synapse_pay_rest/models/node/eft_np_node.rb +19 -0
  18. data/lib/synapse_pay_rest/models/node/iou_node.rb +27 -0
  19. data/lib/synapse_pay_rest/models/node/node.rb +99 -0
  20. data/lib/synapse_pay_rest/models/node/reserve_us_node.rb +23 -0
  21. data/lib/synapse_pay_rest/models/node/synapse_ind_node.rb +22 -0
  22. data/lib/synapse_pay_rest/models/node/synapse_node.rb +25 -0
  23. data/lib/synapse_pay_rest/models/node/synapse_np_node.rb +22 -0
  24. data/lib/synapse_pay_rest/models/node/synapse_us_node.rb +23 -0
  25. data/lib/synapse_pay_rest/models/node/unverified_node.rb +73 -0
  26. data/lib/synapse_pay_rest/models/node/wire_int_node.rb +23 -0
  27. data/lib/synapse_pay_rest/models/node/wire_node.rb +38 -0
  28. data/lib/synapse_pay_rest/models/node/wire_us_node.rb +23 -0
  29. data/lib/synapse_pay_rest/models/transaction/transaction.rb +212 -0
  30. data/lib/synapse_pay_rest/models/user/base_document.rb +346 -0
  31. data/lib/synapse_pay_rest/models/user/document.rb +71 -0
  32. data/lib/synapse_pay_rest/models/user/physical_document.rb +29 -0
  33. data/lib/synapse_pay_rest/models/user/question.rb +45 -0
  34. data/lib/synapse_pay_rest/models/user/social_document.rb +7 -0
  35. data/lib/synapse_pay_rest/models/user/user.rb +593 -0
  36. data/lib/synapse_pay_rest/models/user/virtual_document.rb +77 -0
  37. data/lib/synapse_pay_rest/version.rb +2 -1
  38. data/samples.md +391 -219
  39. data/synapse_pay_rest.gemspec +13 -12
  40. metadata +78 -24
  41. 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
- # should maybe create User class
6
+ # Wrapper class for /users endpoints
7
7
  class Users
8
- # Should refactor this to HTTPClient
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
- # refactor to automate oauth
18
- def refresh(payload: raise("payload is required"))
19
- path = "/oauth/#{@client.user_id}"
20
- response = @client.post(path, payload)
21
- client.update_headers(oauth_key: response['oauth_key']) if response['oauth_key']
22
- response
23
- end
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
- def update(payload: raise("payload is required"))
48
- path = create_user_path(user_id: client.user_id)
49
- response = client.patch(path, payload)
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
- def create(payload: raise("payload is required"))
55
- path = create_user_path
56
- response = client.post(path, payload)
57
- client.update_headers(user_id: response['_id']) if response['_id']
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
- def encode_attachment(file_path: raise("file_path is required"), file_type: nil)
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("File type not found. Specify a file_type argument.")
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
- # this is just an alias for update. leaving here for legacy users.
80
- def answer_kba(payload: raise("payload is required"))
81
- update(payload: payload)
82
- end
83
-
84
- # this is just an alias for update. leaving here for legacy users.
85
- def add_doc(payload: raise("payload is required"))
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("File type not found. Use attach_file_with_file_type(file_path: <file_path>, file_type: <file_type>)")
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
- def attach_file_with_file_type(file_path: raise("file_path is required"), file_type: raise("file_type is required"))
105
- warn caller.first + " DEPRECATION WARNING: the method #{self.class}##{__method__} is deprecated. Use SynapsePayRest::Users::update with encode_attachment instead."
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
- attr_accessor :base_url, :config, :headers, :user_id
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
- def initialize(config, base_url, user_id: nil)
9
- @config = config
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
- # RestClient.log = 'stdout'
12
- @user_id = user_id
35
+ @user_id = user_id
13
36
  end
14
37
 
15
- def get_headers
16
- # refactor to use symbols
17
- user = "#{config['oauth_key']}|#{config['fingerprint']}"
18
- gateway = "#{config['client_id']}|#{config['client_secret']}"
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 => :json,
21
- :accept => :json,
45
+ :content_type => :json,
46
+ :accept => :json,
22
47
  'X-SP-GATEWAY' => gateway,
23
- 'X-SP-USER' => user,
24
- 'X-SP-USER-IP' => config['ip_address']
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
- def update_headers(user_id: nil, oauth_key: nil, fingerprint: nil, client_id: nil, client_secret: nil, ip_address: nil)
29
- # this doesn't really belongs in headers
30
- self.user_id = user_id if user_id
31
-
32
- config['fingerprint'] = fingerprint if fingerprint
33
- config['oauth_key'] = oauth_key if oauth_key
34
- config['client_id'] = client_id if client_id
35
- config['client_secret'] = client_secret if client_secret
36
- config['ip_address'] = ip_address if ip_address
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, get_headers) }
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, get_headers) }
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), get_headers) }
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), get_headers) }
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, e.response.code)
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