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.
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