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
@@ -0,0 +1,22 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Represents a Synapse node allowing any user to hold Nepali Rupees.
|
3
|
+
class SynapseNpNode < SynapseNode
|
4
|
+
class << self
|
5
|
+
private
|
6
|
+
|
7
|
+
def payload_for_create(nickname:, **options)
|
8
|
+
args = {
|
9
|
+
type: 'SYNAPSE-NP',
|
10
|
+
nickname: nickname
|
11
|
+
}.merge(options)
|
12
|
+
payload = super(args)
|
13
|
+
# optional payload fields
|
14
|
+
extra = {}
|
15
|
+
extra['supp_id'] = options[:supp_id] if options[:supp_id]
|
16
|
+
extra['gateway_restricted'] = options[:gateway_restricted] if options[:gateway_restricted]
|
17
|
+
payload['extra'] = extra if extra.any?
|
18
|
+
payload
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Represents a Synapse node allowing any user to hold funds. You can use this
|
3
|
+
# node as a wallet, an escrow account or something else along those lines.
|
4
|
+
class SynapseUsNode < SynapseNode
|
5
|
+
class << self
|
6
|
+
private
|
7
|
+
|
8
|
+
def payload_for_create(nickname:, **options)
|
9
|
+
args = {
|
10
|
+
type: 'SYNAPSE-US',
|
11
|
+
nickname: nickname
|
12
|
+
}.merge(options)
|
13
|
+
payload = super(args)
|
14
|
+
# optional payload fields
|
15
|
+
extra = {}
|
16
|
+
extra['supp_id'] = options[:supp_id] if options[:supp_id]
|
17
|
+
extra['gateway_restricted'] = options[:gateway_restricted] if options[:gateway_restricted]
|
18
|
+
payload['extra'] = extra if extra.any?
|
19
|
+
payload
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Represents a node that has not yet been created due to pending bank login
|
3
|
+
# MFA questions.
|
4
|
+
class UnverifiedNode
|
5
|
+
# @!attribute [r] user
|
6
|
+
# @return [SynapsePayRest::User] the user to whom the node belongs
|
7
|
+
# @!attribute [r] mfa_access_token
|
8
|
+
# @return [String] access token that must be included in the response (handled automatically)
|
9
|
+
# @!attribute [r] mfa_message
|
10
|
+
# @return [String] question or MFA prompt from bank that must be answered
|
11
|
+
# @!attribute [r] mfa_verified
|
12
|
+
# @return [Boolean] whether the node is verified yet
|
13
|
+
# @todo should be mfa_verified? in Ruby idiom
|
14
|
+
attr_reader :user, :mfa_access_token, :mfa_message, :mfa_verified
|
15
|
+
|
16
|
+
def initialize(user:, mfa_access_token:, mfa_message:, mfa_verified:)
|
17
|
+
@user = user
|
18
|
+
@mfa_access_token = mfa_access_token
|
19
|
+
@mfa_message = mfa_message
|
20
|
+
@mfa_verified = mfa_verified
|
21
|
+
end
|
22
|
+
|
23
|
+
# Allows the user to submit an answer to the bank in response to mfa_message.
|
24
|
+
#
|
25
|
+
# @param answer [String] the user's answer to the mfa_message asked by the bank
|
26
|
+
#
|
27
|
+
# @raise [SynapsePayRest::Error] if incorrect answer
|
28
|
+
#
|
29
|
+
# @return [Array<SynapsePayRest::AchUsNode>,SynapsePayRest::UnverifiedNode] may contain multiple nodes if successful, else self if new MFA question to answer
|
30
|
+
#
|
31
|
+
# @todo make a new Error subclass for incorrect MFA
|
32
|
+
def answer_mfa(answer)
|
33
|
+
payload = payload_for_answer_mfa(answer: answer)
|
34
|
+
response = user.client.nodes.post(payload: payload)
|
35
|
+
|
36
|
+
handle_answer_mfa_response(response)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def payload_for_answer_mfa(answer:)
|
42
|
+
{
|
43
|
+
'access_token' => mfa_access_token,
|
44
|
+
'mfa_answer' => answer
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
# Determines whether the response is successful in verifying the node, has
|
49
|
+
# follow-up MFA questions, or failed with an incorrect answer.
|
50
|
+
#
|
51
|
+
# @todo Use Error#code instead of parsing the response for the code.
|
52
|
+
def handle_answer_mfa_response(response)
|
53
|
+
if response['error_code'] == '0'
|
54
|
+
# correct answer
|
55
|
+
@mfa_verified = true
|
56
|
+
AchUsNode.create_multiple_from_response(user, response['nodes'])
|
57
|
+
elsif response['error_code'] == '10' && response['mfa']['message'] == mfa_message
|
58
|
+
# wrong answer (mfa message the same), retry if allowed
|
59
|
+
args = {
|
60
|
+
message: 'incorrect bank login mfa answer',
|
61
|
+
code: response['http_code'],
|
62
|
+
response: response
|
63
|
+
}
|
64
|
+
raise SynapsePayRest::Error, args
|
65
|
+
elsif response['error_code'] == '10'
|
66
|
+
# new additional MFA question. need to call #answer_mfa with new answer
|
67
|
+
@mfa_access_token = response['mfa']['access_token']
|
68
|
+
@mfa_message = response['mfa']['message']
|
69
|
+
self
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Represents a non-US account for wire payments.
|
3
|
+
class WireIntNode < WireNode
|
4
|
+
class << self
|
5
|
+
private
|
6
|
+
|
7
|
+
def payload_for_create(nickname:, bank_name:, account_number:, swift:,
|
8
|
+
name_on_account:, address:, **options)
|
9
|
+
args = {
|
10
|
+
type: 'WIRE-INT',
|
11
|
+
nickname: nickname,
|
12
|
+
bank_name: bank_name,
|
13
|
+
account_number: account_number,
|
14
|
+
name_on_account: name_on_account,
|
15
|
+
address: address
|
16
|
+
}.merge(options)
|
17
|
+
payload = super(args)
|
18
|
+
payload['info']['swift'] = swift
|
19
|
+
payload
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Parent of all Wire nodes. Should not be instantiated directly.
|
3
|
+
#
|
4
|
+
# @todo Make this a module instead.
|
5
|
+
class WireNode < BaseNode
|
6
|
+
class << self
|
7
|
+
private
|
8
|
+
|
9
|
+
def payload_for_create(type:, nickname:, bank_name:, account_number:, address:,
|
10
|
+
name_on_account:, **options)
|
11
|
+
payload = {
|
12
|
+
'type' => type,
|
13
|
+
'info' => {
|
14
|
+
'nickname' => nickname,
|
15
|
+
'name_on_account' => name_on_account,
|
16
|
+
'account_num' => account_number,
|
17
|
+
'bank_name' => bank_name,
|
18
|
+
'address' => address,
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
# optional payload fields
|
23
|
+
payload['info']['routing_num'] = options[:routing_number] if options[:routing_number]
|
24
|
+
correspondent_info = {}
|
25
|
+
correspondent_info['routing_num'] = options[:correspondent_routing_number] if options[:correspondent_routing_number]
|
26
|
+
correspondent_info['bank_name'] = options[:correspondent_bank_name] if options[:correspondent_bank_name]
|
27
|
+
correspondent_info['address'] = options[:correspondent_address] if options[:correspondent_address]
|
28
|
+
correspondent_info['swift'] = options[:correspondent_swift] if options[:correspondent_swift]
|
29
|
+
payload['info']['correspondent_info'] = correspondent_info if correspondent_info.any?
|
30
|
+
extra = {}
|
31
|
+
extra['supp_id'] = options[:supp_id] if options[:supp_id]
|
32
|
+
extra['gateway_restricted'] = options[:gateway_restricted] if options[:gateway_restricted]
|
33
|
+
payload['extra'] = extra if extra.any?
|
34
|
+
payload
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Represents a US bank account for processing wire payments.
|
3
|
+
class WireUsNode < WireNode
|
4
|
+
class << self
|
5
|
+
private
|
6
|
+
|
7
|
+
def payload_for_create(nickname:, bank_name:, account_number:, routing_number:,
|
8
|
+
name_on_account:, address:, **options)
|
9
|
+
args = {
|
10
|
+
type: 'WIRE-US',
|
11
|
+
nickname: nickname,
|
12
|
+
bank_name: bank_name,
|
13
|
+
account_number: account_number,
|
14
|
+
routing_number: routing_number,
|
15
|
+
name_on_account: name_on_account,
|
16
|
+
address: address
|
17
|
+
}.merge(options)
|
18
|
+
|
19
|
+
super(args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Represents a transaction record and holds methods for constructing transaction instances
|
3
|
+
# from API calls. This is built on top of the SynapsePayRest::Transactions class and
|
4
|
+
# is intended to make it easier to use the API without knowing payload formats
|
5
|
+
# or knowledge of REST.
|
6
|
+
#
|
7
|
+
# @todo use mixins to remove duplication between Node and BaseNode. May be
|
8
|
+
# better to refactor this into a mixin altogether since this shouldn't be instantiated.
|
9
|
+
# @todo reduce duplicated logic between User/BaseNode/Transaction
|
10
|
+
class Transaction
|
11
|
+
|
12
|
+
# @!attribute [rw] node
|
13
|
+
# @return [SynapsePayRest::Node] the node to which the transaction belongs
|
14
|
+
attr_reader :node, :id, :amount, :currency, :client_id, :client_name, :created_on,
|
15
|
+
:ip, :latlon, :note, :process_on, :supp_id, :webhook, :fees,
|
16
|
+
:recent_status, :timeline, :from, :to, :to_type, :to_id,
|
17
|
+
:fee_amount, :fee_note, :fee_to_id
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Creates a new transaction in the API belonging to the provided node and
|
21
|
+
# returns a transaction instance from the response data.
|
22
|
+
#
|
23
|
+
# @param node [SynapsePayRest::BaseNode] node to which the transaction belongs
|
24
|
+
# @param to_id [String] node id of the receiving node
|
25
|
+
# @param to_type [String] node type of the receiving node
|
26
|
+
# @see https://docs.synapsepay.com/docs/node-resources valid node types
|
27
|
+
# @param amount [Float] 100.0 = $100.00 for example
|
28
|
+
# @param currency [String] e.g. 'USD'
|
29
|
+
# @param ip [String]
|
30
|
+
# @param note [String] (optional)
|
31
|
+
# @param process_in [Integer] (optional) days until processed (default/minimum 1)
|
32
|
+
# @param fee_amount [Float] (optional) fee amount to add to the transaction
|
33
|
+
# @param fee_to_id [String] (optional) node id to which to send the fee (must be SYNAPSE-US)
|
34
|
+
# @param fee_note [String] (optional)
|
35
|
+
# @param supp_id [String] (optional)
|
36
|
+
#
|
37
|
+
# @raise [SynapsePayRest::Error] if HTTP error or invalid argument format
|
38
|
+
#
|
39
|
+
# @return [SynapsePayRest::Transaction]
|
40
|
+
#
|
41
|
+
# @todo allow either to_node or to_type/to_id
|
42
|
+
# @todo allow node to be entered as alternative to fee_to node
|
43
|
+
# @todo validate if fee_to node is synapse-us
|
44
|
+
# @todo allow multiple fees
|
45
|
+
def create(node:, to_type:, to_id:, amount:, currency:, ip:, **options)
|
46
|
+
raise ArgumentError, 'cannot create a transaction with an UnverifiedNode' if node.is_a?(UnverifiedNode)
|
47
|
+
raise ArgumentError, 'node must be a type of BaseNode object' unless node.is_a?(BaseNode)
|
48
|
+
raise ArgumentError, 'amount must be a Numeric (Integer or Float)' unless amount.is_a?(Numeric)
|
49
|
+
[to_type, to_id, currency, ip].each do |arg|
|
50
|
+
raise ArgumentError, "#{arg} must be a String" unless arg.is_a?(String)
|
51
|
+
end
|
52
|
+
|
53
|
+
payload = payload_for_create(node: node, to_type: to_type, to_id: to_id,
|
54
|
+
amount: amount, currency: currency, ip: ip, **options)
|
55
|
+
node.user.authenticate
|
56
|
+
response = node.user.client.trans.create(node_id: node.id, payload: payload)
|
57
|
+
create_from_response(node, response)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Queries the API for a transaction belonging to the supplied node by transaction id
|
61
|
+
# and returns a Transaction instance if found.
|
62
|
+
#
|
63
|
+
# @param node [SynapsePayRest::BaseNode] node to which the transaction belongs
|
64
|
+
# @param id [String] id of the transaction to find
|
65
|
+
#
|
66
|
+
# @raise [SynapsePayRest::Error] if not found or other HTTP error
|
67
|
+
#
|
68
|
+
# @return [SynapsePayRest::Transaction]
|
69
|
+
def find(node:, id:)
|
70
|
+
raise ArgumentError, 'node must be a type of BaseNode object' unless node.is_a?(BaseNode)
|
71
|
+
raise ArgumentError, 'id must be a String' unless id.is_a?(String)
|
72
|
+
|
73
|
+
node.user.authenticate
|
74
|
+
response = node.user.client.trans.get(node_id: node.id, trans_id: id)
|
75
|
+
create_from_response(node, response)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Queries the API for all transactions belonging to the supplied node and returns
|
79
|
+
# them as Transaction instances.
|
80
|
+
#
|
81
|
+
# @param node [SynapsePayRest::BaseNode] node to which the transaction belongs
|
82
|
+
# @param page [String,Integer] (optional) response will default to 1
|
83
|
+
# @param per_page [String,Integer] (optional) response will default to 20
|
84
|
+
#
|
85
|
+
# @raise [SynapsePayRest::Error]
|
86
|
+
#
|
87
|
+
# @return [Array<SynapsePayRest::Transaction>]
|
88
|
+
def all(node:, page: nil, per_page: nil)
|
89
|
+
raise ArgumentError, 'node must be a type of BaseNode object' unless node.is_a?(BaseNode)
|
90
|
+
[page, per_page].each do |arg|
|
91
|
+
if arg && (!arg.is_a?(Integer) || arg < 1)
|
92
|
+
raise ArgumentError, "#{arg} must be nil or an Integer >= 1"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
node.user.authenticate
|
97
|
+
response = node.user.client.trans.get(node_id: node.id, page: page, per_page: per_page)
|
98
|
+
create_multiple_from_response(node, response['trans'])
|
99
|
+
end
|
100
|
+
|
101
|
+
# Creates a Transaction from a response hash.
|
102
|
+
#
|
103
|
+
# @note Shouldn't need to call this directly.
|
104
|
+
#
|
105
|
+
# @todo convert the nodes and users in response into User/Node objects
|
106
|
+
# @todo rework to handle multiple fees
|
107
|
+
def create_from_response(node, response)
|
108
|
+
args = {
|
109
|
+
node: node,
|
110
|
+
id: response['_id'],
|
111
|
+
amount: response['amount']['amount'],
|
112
|
+
currency: response['amount']['currency'],
|
113
|
+
client_id: response['client']['id'],
|
114
|
+
client_name: response['client']['name'],
|
115
|
+
created_on: response['extra']['created_on'],
|
116
|
+
ip: response['extra']['ip'],
|
117
|
+
latlon: response['extra']['latlon'],
|
118
|
+
note: response['extra']['note'],
|
119
|
+
process_on: response['extra']['process_on'],
|
120
|
+
supp_id: response['extra']['supp_id'],
|
121
|
+
webhook: response['extra']['webhook'],
|
122
|
+
fees: response['fees'],
|
123
|
+
recent_status: response['recent_status'],
|
124
|
+
timeline: response['timeline'],
|
125
|
+
from: response['from'],
|
126
|
+
to: response['to'],
|
127
|
+
to_type: response['to']['type'],
|
128
|
+
to_id: response['to']['id'],
|
129
|
+
fee_amount: response['fees'].last['fee'],
|
130
|
+
fee_note: response['fees'].last['note'],
|
131
|
+
fee_to_id: response['fees'].last['to']['id'],
|
132
|
+
}
|
133
|
+
self.new(args)
|
134
|
+
end
|
135
|
+
|
136
|
+
private
|
137
|
+
|
138
|
+
def payload_for_create(node:, to_type:, to_id:, amount:, currency:, ip:,
|
139
|
+
**options)
|
140
|
+
payload = {
|
141
|
+
'to' => {
|
142
|
+
'type' => to_type,
|
143
|
+
'id' => to_id
|
144
|
+
},
|
145
|
+
'amount' => {
|
146
|
+
'amount' => amount,
|
147
|
+
'currency' => currency
|
148
|
+
},
|
149
|
+
'extra' => {
|
150
|
+
'ip' => ip
|
151
|
+
}
|
152
|
+
}
|
153
|
+
# optional payload fields
|
154
|
+
payload['extra']['supp_id'] = options[:supp_id] if options[:supp_id]
|
155
|
+
payload['extra']['note'] = options[:note] if options[:note]
|
156
|
+
payload['extra']['process_on'] = options[:process_in] if options[:process_in]
|
157
|
+
other = {}
|
158
|
+
other['attachments'] = options[:attachments] if options[:attachments]
|
159
|
+
payload['other'] = other if other.any?
|
160
|
+
fees = []
|
161
|
+
fee = {}
|
162
|
+
fee['fee'] = options[:fee_amount] if options[:fee_amount]
|
163
|
+
fee['note'] = options[:fee_note] if options[:fee_note]
|
164
|
+
fee_to = {}
|
165
|
+
fee_to['id'] = options[:fee_to_id] if options[:fee_to_id]
|
166
|
+
fee['to'] = fee_to if fee_to.any?
|
167
|
+
fees << fee if fee.any?
|
168
|
+
payload['fees'] = fees if fees.any?
|
169
|
+
payload
|
170
|
+
end
|
171
|
+
|
172
|
+
def create_multiple_from_response(node, response)
|
173
|
+
return [] if response.empty?
|
174
|
+
response.map { |trans_data| create_from_response(node, trans_data) }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# @note Do not call directly. Use Transaction.create or other class
|
179
|
+
# method to instantiate via API action.
|
180
|
+
def initialize(**options)
|
181
|
+
options.each { |key, value| instance_variable_set("@#{key}", value) }
|
182
|
+
end
|
183
|
+
|
184
|
+
# Adds a comment to the transaction's timeline/recent_status fields.
|
185
|
+
#
|
186
|
+
# @param comment [String]
|
187
|
+
#
|
188
|
+
# @raise [SynapsePayRest::Error]
|
189
|
+
#
|
190
|
+
# @return [Array<SynapsePayRest::Transaction>] (self)
|
191
|
+
def add_comment(comment)
|
192
|
+
payload = {'comment': comment}
|
193
|
+
response = node.user.client.trans.update(node_id: node.id, trans_id: id, payload: payload)
|
194
|
+
self.class.create_from_response(node, response['trans'])
|
195
|
+
end
|
196
|
+
|
197
|
+
# Cancels this transaction if it has not already settled.
|
198
|
+
#
|
199
|
+
# @raise [SynapsePayRest::Error]
|
200
|
+
#
|
201
|
+
# @return [Array<SynapsePayRest::Transaction>] (self)
|
202
|
+
def cancel
|
203
|
+
response = node.user.client.trans.delete(node_id: node.id, trans_id: id)
|
204
|
+
self.class.create_from_response(node, response)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Checks if two Transaction instances have same id (different instances of same record).
|
208
|
+
def ==(other)
|
209
|
+
other.instance_of?(self.class) && !id.nil? && id == other.id
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,346 @@
|
|
1
|
+
module SynapsePayRest
|
2
|
+
# Stores info on the base document portion (personal/business info) of the CIP
|
3
|
+
# document and also manages physical/social/virtual documents.
|
4
|
+
class BaseDocument
|
5
|
+
# @!attribute [rw] user
|
6
|
+
# @return [SynapsePayRest::User] the user to whom the transaction belongs
|
7
|
+
# @!attribute [r] permission_scope
|
8
|
+
# @return [String] https://docs.synapsepay.com/docs/user-resources#section-document-permission-scope
|
9
|
+
attr_accessor :user, :email, :phone_number, :ip, :name, :aka, :entity_type,
|
10
|
+
:entity_scope, :birth_day, :birth_month, :birth_year,
|
11
|
+
:address_street, :address_city, :address_subdivision,
|
12
|
+
:address_postal_code, :address_country_code,
|
13
|
+
:physical_documents, :social_documents, :virtual_documents
|
14
|
+
attr_reader :id, :permission_scope
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# Creates a new base document in the API belonging to the provided user and
|
18
|
+
# returns a base document instance from the response data.
|
19
|
+
#
|
20
|
+
# @param user [SynapsePayRest::User] the user to whom the base document belongs
|
21
|
+
# @param email [String]
|
22
|
+
# @param phone_number [String]
|
23
|
+
# @param ip [String]
|
24
|
+
# @param name [String]
|
25
|
+
# @param aka [String] corresponds to 'alias' in docs, use name if no alias
|
26
|
+
# @param entity_type [String] consult your organization's CIP for valid options
|
27
|
+
# @see https://docs.synapsepay.com/docs/user-resources#section-supported-entity-types all supported entity_type values
|
28
|
+
# @param entity_scope [String] consult your organization's CIP for valid options
|
29
|
+
# @see https://docs.synapsepay.com/docs/user-resources#section-supported-entity-scope all entity_scope options
|
30
|
+
# @param birth_day [Integer]
|
31
|
+
# @param birth_month [Integer]
|
32
|
+
# @param birth_year [Integer]
|
33
|
+
# @param address_street [String]
|
34
|
+
# @param address_city [String]
|
35
|
+
# @param address_subdivision [String]
|
36
|
+
# @param address_postal_code [String]
|
37
|
+
# @param address_country_code [String]
|
38
|
+
# @param physical_documents [Array<SynapsePayRest::PhysicalDocument>] (optional)
|
39
|
+
# @param social_documents [Array<SynapsePayRest::SocialDocument>] (optional)
|
40
|
+
# @param virtual_documents [Array<SynapsePayRest::VirtualDocument>] (optional)
|
41
|
+
#
|
42
|
+
# @raise [SynapsePayRest::Error]
|
43
|
+
#
|
44
|
+
# @return [SynapsePayRest::BaseDocument] new instance with updated info
|
45
|
+
def create(user:, email:, phone_number:, ip:, name:,
|
46
|
+
aka:, entity_type:, entity_scope:, birth_day:, birth_month:, birth_year:,
|
47
|
+
address_street:, address_city:, address_subdivision:, address_postal_code:,
|
48
|
+
address_country_code:, physical_documents: [], social_documents: [],
|
49
|
+
virtual_documents: [])
|
50
|
+
raise ArgumentError, 'user must be a User object' unless user.is_a?(User)
|
51
|
+
[email, phone_number, ip, name, aka, entity_type, entity_scope,
|
52
|
+
address_street, address_city, address_subdivision, address_postal_code,
|
53
|
+
address_country_code].each do |arg|
|
54
|
+
raise ArgumentError, "#{arg} must be a String" unless arg.is_a?(String)
|
55
|
+
end
|
56
|
+
[physical_documents, social_documents, virtual_documents].each do |arg|
|
57
|
+
raise ArgumentError, "#{arg} must be an Array" unless arg.is_a?(Array)
|
58
|
+
end
|
59
|
+
unless physical_documents.empty? || physical_documents.first.is_a?(PhysicalDocument)
|
60
|
+
raise ArgumentError, 'physical_documents be empty or contain PhysicalDocument(s)'
|
61
|
+
end
|
62
|
+
unless social_documents.empty? || social_documents.first.is_a?(SocialDocument)
|
63
|
+
raise ArgumentError, 'social_documents be empty or contain SocialDocument(s)'
|
64
|
+
end
|
65
|
+
unless virtual_documents.empty? || virtual_documents.first.is_a?(VirtualDocument)
|
66
|
+
raise ArgumentError, 'virtual_documents be empty or contain VirtualDocument(s)'
|
67
|
+
end
|
68
|
+
|
69
|
+
base_document = BaseDocument.new(
|
70
|
+
user: user,
|
71
|
+
email: email,
|
72
|
+
phone_number: phone_number,
|
73
|
+
ip: ip,
|
74
|
+
name: name,
|
75
|
+
aka: aka,
|
76
|
+
entity_type: entity_type,
|
77
|
+
entity_scope: entity_scope,
|
78
|
+
birth_day: birth_day,
|
79
|
+
birth_month: birth_month,
|
80
|
+
birth_year: birth_year,
|
81
|
+
address_street: address_street,
|
82
|
+
address_city: address_city,
|
83
|
+
address_subdivision: address_subdivision,
|
84
|
+
address_postal_code: address_postal_code,
|
85
|
+
address_country_code: address_country_code,
|
86
|
+
physical_documents: physical_documents,
|
87
|
+
social_documents: social_documents,
|
88
|
+
virtual_documents: virtual_documents
|
89
|
+
)
|
90
|
+
|
91
|
+
base_document.submit
|
92
|
+
end
|
93
|
+
|
94
|
+
# Parses multiple base_documents from response
|
95
|
+
# @note Do not call directly (it's automatic).
|
96
|
+
def create_from_response(user, response)
|
97
|
+
base_documents_data = response['documents']
|
98
|
+
base_documents_data.map do |base_document_data|
|
99
|
+
physical_docs = base_document_data['physical_docs'].map do |data|
|
100
|
+
doc = PhysicalDocument.create_from_response(data)
|
101
|
+
doc.base_document = self
|
102
|
+
doc
|
103
|
+
end
|
104
|
+
social_docs = base_document_data['social_docs'].map do |data|
|
105
|
+
doc = SocialDocument.create_from_response(data)
|
106
|
+
doc.base_document = self
|
107
|
+
doc
|
108
|
+
end
|
109
|
+
virtual_docs = base_document_data['virtual_docs'].map do |data|
|
110
|
+
doc = VirtualDocument.create_from_response(data)
|
111
|
+
doc.base_document = self
|
112
|
+
doc
|
113
|
+
end
|
114
|
+
|
115
|
+
args = {
|
116
|
+
user: user,
|
117
|
+
id: base_document_data['id'],
|
118
|
+
name: base_document_data['name'],
|
119
|
+
permission_scope: base_document_data['permission_scope'],
|
120
|
+
physical_documents: physical_docs,
|
121
|
+
social_documents: social_docs,
|
122
|
+
virtual_documents: virtual_docs
|
123
|
+
}
|
124
|
+
|
125
|
+
base_doc = self.new(args)
|
126
|
+
[physical_docs, social_docs, virtual_docs].flatten.each do |doc|
|
127
|
+
doc.base_document = base_doc
|
128
|
+
end
|
129
|
+
|
130
|
+
base_doc
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# @note It should not be necessary to call this method directly.
|
136
|
+
def initialize(**args)
|
137
|
+
@id = args[:id]
|
138
|
+
@permission_scope = args[:permission_scope]
|
139
|
+
@user = args[:user]
|
140
|
+
@email = args[:email]
|
141
|
+
@phone_number = args[:phone_number]
|
142
|
+
@ip = args[:ip]
|
143
|
+
@name = args[:name]
|
144
|
+
@aka = args[:aka]
|
145
|
+
@entity_type = args[:entity_type]
|
146
|
+
@entity_scope = args[:entity_scope]
|
147
|
+
@birth_day = args[:birth_day]
|
148
|
+
@birth_month = args[:birth_month]
|
149
|
+
@birth_year = args[:birth_year]
|
150
|
+
@address_street = args[:address_street]
|
151
|
+
@address_city = args[:address_city]
|
152
|
+
@address_subdivision = args[:address_subdivision]
|
153
|
+
@address_postal_code = args[:address_postal_code]
|
154
|
+
@address_country_code = args[:address_country_code]
|
155
|
+
@physical_documents = args[:physical_documents]
|
156
|
+
@social_documents = args[:social_documents]
|
157
|
+
@virtual_documents = args[:virtual_documents]
|
158
|
+
end
|
159
|
+
|
160
|
+
# Submits the base document to the API.
|
161
|
+
# @note It should not be necessary to call this method directly.
|
162
|
+
#
|
163
|
+
# @raise [SynapsePayRest::Error]
|
164
|
+
#
|
165
|
+
# @return [SynapsePayRest::BaseDocument] new instance with updated info (id will be different if email or phone changed)
|
166
|
+
def submit
|
167
|
+
user.authenticate
|
168
|
+
response = user.client.users.update(payload: payload_for_submit)
|
169
|
+
@user = User.create_from_response(user.client, response)
|
170
|
+
|
171
|
+
if id
|
172
|
+
# return updated version of self
|
173
|
+
user.base_documents.find { |doc| doc.id == id }
|
174
|
+
else
|
175
|
+
# first time submission, assume last doc is updated version of self
|
176
|
+
require 'pry';
|
177
|
+
user.base_documents.last
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Updates the supplied fields in the base document. See #create for valid
|
182
|
+
#
|
183
|
+
# @param email [String] (optional)
|
184
|
+
# @param phone_number [String] (optional)
|
185
|
+
# @param ip [String] (optional)
|
186
|
+
# @param name [String] (optional)
|
187
|
+
# @param aka [String] (optional) corresponds to 'alias' in docs, use name if no alias
|
188
|
+
# @param entity_type [String] (optional) consult your organization's CIP for valid options
|
189
|
+
# @see https://docs.synapsepay.com/docs/user-resources#section-supported-entity-types all supported entity_type values
|
190
|
+
# @param entity_scope [String] (optional) consult your organization's CIP for valid options
|
191
|
+
# @see https://docs.synapsepay.com/docs/user-resources#section-supported-entity-scope all entity_scope options
|
192
|
+
# @param birth_day [Integer] (optional)
|
193
|
+
# @param birth_month [Integer] (optional)
|
194
|
+
# @param birth_year [Integer] (optional)
|
195
|
+
# @param address_street [String] (optional)
|
196
|
+
# @param address_city [String] (optional)
|
197
|
+
# @param address_subdivision [String] (optional)
|
198
|
+
# @param address_postal_code [String] (optional)
|
199
|
+
# @param address_country_code [String] (optional)
|
200
|
+
# @param physical_documents [Array<SynapsePayRest::PhysicalDocument>] (optional)
|
201
|
+
# @param social_documents [Array<SynapsePayRest::SocialDocument>] (optional)
|
202
|
+
# @param virtual_documents [Array<SynapsePayRest::VirtualDocument>] (optional)
|
203
|
+
#
|
204
|
+
# @raise [SynapsePayRest::Error]
|
205
|
+
#
|
206
|
+
# @return [SynapsePayRest::BaseDocument] new instance with updated info
|
207
|
+
#
|
208
|
+
# @todo validate changes are valid fields in base_document
|
209
|
+
# (or make other methods more like this)
|
210
|
+
def update(**changes)
|
211
|
+
if changes.empty?
|
212
|
+
raise ArgumentError, 'must provide some key-value pairs to update'
|
213
|
+
end
|
214
|
+
user.authenticate
|
215
|
+
payload = payload_for_update(changes)
|
216
|
+
response = user.client.users.update(payload: payload)
|
217
|
+
@user = User.create_from_response(user.client, response)
|
218
|
+
|
219
|
+
if id
|
220
|
+
# return updated version of self
|
221
|
+
return user.base_documents.find { |doc| doc.id == id }
|
222
|
+
else
|
223
|
+
# first time submission, assume last doc is updated version of self
|
224
|
+
return user.base_documents.last
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
# Adds one or more physical documents to the base document and submits
|
229
|
+
# them to the API using KYC 2.0 endpoints.
|
230
|
+
#
|
231
|
+
# @param documents [Array<SynapsePayRest::PhysicalDocument>]
|
232
|
+
#
|
233
|
+
# @raise [SynapsePayRest::Error]
|
234
|
+
#
|
235
|
+
# @return [SynapsePayRest::BaseDocument] new instance with updated info
|
236
|
+
def add_physical_documents(documents)
|
237
|
+
raise ArgumentError, 'must be an Array' unless documents.is_a?(Array)
|
238
|
+
unless documents.first.is_a?(PhysicalDocument)
|
239
|
+
raise ArgumentError, 'must contain a PhysicalDocument'
|
240
|
+
end
|
241
|
+
|
242
|
+
update(physical_documents: documents)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Adds one or more social documents to the base document and submits
|
246
|
+
# them to the API using KYC 2.0 endpoints.
|
247
|
+
#
|
248
|
+
# @param documents [Array<SynapsePayRest::SocialDocument>]
|
249
|
+
#
|
250
|
+
# @raise [SynapsePayRest::Error]
|
251
|
+
#
|
252
|
+
# @return [SynapsePayRest::BaseDocument] new instance with updated info
|
253
|
+
def add_social_documents(documents)
|
254
|
+
raise ArgumentError, 'must be an Array' unless documents.is_a?(Array)
|
255
|
+
unless documents.first.is_a?(SocialDocument)
|
256
|
+
raise ArgumentError, 'must contain a SocialDocument'
|
257
|
+
end
|
258
|
+
|
259
|
+
update(social_documents: documents)
|
260
|
+
end
|
261
|
+
|
262
|
+
# Adds one or more virtual documents to the base document and submits
|
263
|
+
# them to the API using KYC 2.0 endpoints.
|
264
|
+
#
|
265
|
+
# @param documents [Array<SynapsePayRest::VirtualDocument>]
|
266
|
+
#
|
267
|
+
# @raise [SynapsePayRest::Error]
|
268
|
+
#
|
269
|
+
# @return [SynapsePayRest::BaseDocument] new instance with updated info
|
270
|
+
def add_virtual_documents(documents)
|
271
|
+
raise ArgumentError, 'must be an Array' unless documents.is_a?(Array)
|
272
|
+
unless documents.first.is_a?(VirtualDocument)
|
273
|
+
raise ArgumentError, 'must contain a VirtualDocument'
|
274
|
+
end
|
275
|
+
|
276
|
+
update(virtual_documents: documents)
|
277
|
+
end
|
278
|
+
|
279
|
+
# Checks if two BaseDocument instances have same id (different instances of same record).
|
280
|
+
def ==(other)
|
281
|
+
other.instance_of?(self.class) && !id.nil? && id == other.id
|
282
|
+
end
|
283
|
+
|
284
|
+
private
|
285
|
+
|
286
|
+
def payload_for_submit
|
287
|
+
payload = {
|
288
|
+
'documents' => [{
|
289
|
+
'email' => email,
|
290
|
+
'phone_number' => phone_number,
|
291
|
+
'ip' => ip,
|
292
|
+
'name' => name,
|
293
|
+
'alias' => aka,
|
294
|
+
'entity_type' => entity_type,
|
295
|
+
'entity_scope' => entity_scope,
|
296
|
+
'day' => birth_day,
|
297
|
+
'month' => birth_month,
|
298
|
+
'year' => birth_year,
|
299
|
+
'address_street' => address_street,
|
300
|
+
'address_city' => address_city,
|
301
|
+
'address_subdivision' => address_subdivision,
|
302
|
+
'address_postal_code' => address_postal_code,
|
303
|
+
'address_country_code' => address_country_code
|
304
|
+
}]
|
305
|
+
}
|
306
|
+
|
307
|
+
unless physical_documents.empty?
|
308
|
+
payload['documents'].first['physical_docs'] = physical_documents.map(&:to_hash)
|
309
|
+
end
|
310
|
+
|
311
|
+
unless social_documents.empty?
|
312
|
+
payload['documents'].first['social_docs'] = social_documents.map(&:to_hash)
|
313
|
+
end
|
314
|
+
|
315
|
+
unless virtual_documents.empty?
|
316
|
+
payload['documents'].first['virtual_docs'] = virtual_documents.map(&:to_hash)
|
317
|
+
end
|
318
|
+
|
319
|
+
payload
|
320
|
+
end
|
321
|
+
|
322
|
+
def payload_for_update(changes)
|
323
|
+
payload = {
|
324
|
+
'documents' => [{
|
325
|
+
'id' => id
|
326
|
+
}]
|
327
|
+
}
|
328
|
+
|
329
|
+
changes.each do |field, new_value|
|
330
|
+
# convert docs to their hash format for payload
|
331
|
+
if field == :physical_documents
|
332
|
+
payload['documents'].first['physical_docs'] = new_value.map(&:to_hash)
|
333
|
+
elsif field == :social_documents
|
334
|
+
payload['documents'].first['social_docs'] = new_value.map(&:to_hash)
|
335
|
+
elsif field == :virtual_documents
|
336
|
+
payload['documents'].first['virtual_docs'] = new_value.map(&:to_hash)
|
337
|
+
else
|
338
|
+
# insert non-document fields into payload
|
339
|
+
payload['documents'].first[field.to_s] = new_value
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
payload
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|