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
@@ -0,0 +1,192 @@
1
+ module SynapsePayRest
2
+ # Ancestor of all node types. Should never be instantiated.
3
+ # Represents a Node record and holds methods for constructing Node instances
4
+ # from API calls. This is built on top of the SynapsePayRest::Nodes class and
5
+ # is intended to make it easier to use the API without knowing payload formats
6
+ # or knowledge of REST.
7
+ #
8
+ # @todo use mixins to remove duplication between Node and BaseNode. May be
9
+ # better to refactor this into a mixin altogether since this shouldn't be instantiated.
10
+ # @todo reduce duplicated logic between User/BaseNode/Transaction
11
+ class BaseNode
12
+
13
+ # @!attribute [rw] user
14
+ # @return [SynapsePayRest::User] the user to which the node belongs
15
+ # @!attribute [r] permission
16
+ # @return [String] https://docs.synapsepay.com/docs/user-resources#section-user-permissions
17
+ attr_reader :user, :id, :nickname, :supp_id, :currency, :is_active, :permission,
18
+ :account_number, :routing_number, :name_on_account, :address,
19
+ :bank_name, :bank_id, :bank_pw, :account_class, :account_type,
20
+ :correspondent_routing_number, :correspondent_bank_name,
21
+ :correspondent_address, :correspondent_swift, :account_id, :balance,
22
+ :ifsc, :swift, :bank_long_name, :type, :gateway_restricted
23
+
24
+ class << self
25
+ # Creates a new node in the API associated to the provided user and
26
+ # returns a node instance from the response data. See subclasses for type-specific
27
+ # arguments.
28
+ #
29
+ # @param user [SynapsePayRest::User] user to whom the node belongs
30
+ # @param nickname [String]
31
+ #
32
+ # @raise [SynapsePayRest::Error] if HTTP error or invalid argument format
33
+ #
34
+ # @return [SynapsePayRest::BaseNode]
35
+ def create(user:, nickname:, **options)
36
+ raise ArgumentError, 'user must be a User object' unless user.is_a?(User)
37
+ raise ArgumentError, 'nickname must be a String' unless nickname.is_a?(String)
38
+
39
+ payload = payload_for_create(nickname: nickname, **options)
40
+ user.authenticate
41
+ response = user.client.nodes.add(payload: payload)
42
+ create_from_response(user, response['nodes'].first)
43
+ end
44
+
45
+ # Queries the API for all nodes belonging to the supplied user (with optional
46
+ # filters) and matching the given type of this node.
47
+ #
48
+ # @param user [SynapsePayRest::User]
49
+ # @param page [String,Integer] (optional) response will default to 1
50
+ # @param per_page [String,Integer] (optional) response will default to 20
51
+ #
52
+ # @raise [SynapsePayRest::Error] if HTTP error or invalid argument format
53
+ #
54
+ # @return [Array<SynapsePayRest::BaseNode>] BaseNode will be whatever subclass the method was called on
55
+ def all(user:, page: nil, per_page: nil)
56
+ raise ArgumentError, 'user must be a User object' unless user.is_a?(User)
57
+ [page, per_page].each do |arg|
58
+ if arg && (!arg.is_a?(Integer) || arg < 1)
59
+ raise ArgumentError, "#{arg} must be nil or an Integer >= 1"
60
+ end
61
+ end
62
+ unless type.nil? || NODE_TYPES_TO_CLASSES.keys.include(type)
63
+ raise ArgumentError, "type must be nil or in #{NODE_TYPES_TO_CLASSES.keys}"
64
+ end
65
+
66
+ user.authenticate
67
+ response = user.client.nodes.get(page: page, per_page: per_page, type: self.type)
68
+ create_multiple_from_response(user, response['nodes'])
69
+ end
70
+
71
+ # @note Not meant to be accessed directly on BaseNode but through children.
72
+ def create_from_response(user, response)
73
+ args = {
74
+ user: user,
75
+ type: response['type'],
76
+ id: response['_id'],
77
+ is_active: response['is_active'],
78
+ permission: response['allowed'],
79
+ nickname: response['info']['nickname'],
80
+ name_on_account: response['info']['name_on_account'],
81
+ bank_long_name: response['info']['bank_long_name'],
82
+ bank_name: response['info']['bank_name'],
83
+ account_type: response['info']['type'],
84
+ account_class: response['info']['class'],
85
+ account_number: response['info']['account_num'],
86
+ routing_number: response['info']['routing_num'],
87
+ account_id: response['info']['account_id'],
88
+ address: response['info']['address'],
89
+ swift: response['info']['swift'],
90
+ ifsc: response['info']['ifsc']
91
+ }
92
+
93
+ if response['info']['correspondent_info']
94
+ args[:correspondent_swift] = response['info']['correspondent_info']['swift']
95
+ args[:correspondent_bank_name] = response['info']['correspondent_info']['bank_name']
96
+ args[:correspondent_routing_number] = response['info']['correspondent_info']['routing_num']
97
+ args[:correspondent_address] = response['info']['correspondent_info']['address']
98
+ args[:correspondent_swift] = response['info']['correspondent_info']['swift']
99
+ end
100
+
101
+ if response['info']['balance']
102
+ args[:balance] = response['info']['balance']['amount']
103
+ args[:currency] = response['info']['balance']['currency']
104
+ end
105
+
106
+ if response['extra']
107
+ args[:supp_id] = response['extra']['supp_id']
108
+ args[:gateway_restricted] = response['extra']['gateway_restricted']
109
+ end
110
+
111
+ self.new(**args)
112
+ end
113
+
114
+ # @note Not meant to be accessed directly on BaseNode but through children.
115
+ def create_multiple_from_response(user, response)
116
+ response.map { |node_data| create_from_response(user, node_data)}
117
+ end
118
+ end
119
+
120
+ # @note Do not call directly. Use <BaseNode subclass>.create or other
121
+ # class method to instantiate via API action.
122
+ def initialize(**options)
123
+ options.each { |key, value| instance_variable_set("@#{key}", value) }
124
+ end
125
+
126
+ # Creates a transaction belonging to this node and returns it as a Transaction
127
+ # instance.
128
+ #
129
+ # @param to_id [String] node id of the receiving node
130
+ # @param to_type [String] node type of the receiving node
131
+ # @see https://docs.synapsepay.com/docs/node-resources valid node types
132
+ # @param amount [Float] 100.0 = $100.00 for example
133
+ # @param currency [String] e.g. 'USD'
134
+ # @param ip [String]
135
+ # @param note [String] (optional)
136
+ # @param process_in [Integer] (optional) days until processed (default/minimum 1)
137
+ # @param fee_amount [Float] (optional) fee amount to add to the transaction
138
+ # @param fee_note [String] (optional)
139
+ # @param fee_to_id [String] (optional) node id to which to send the fee
140
+ # @param supp_id [String] (optional)
141
+ #
142
+ # @raise [SynapsePayRest::Error] if HTTP error or invalid argument format
143
+ #
144
+ # @return [SynapsePayRest::Transaction]
145
+ def create_transaction(**options)
146
+ Transaction.create(node: self, **options)
147
+ end
148
+
149
+ # Queries the API for all transactions belonging to this node and returns
150
+ # them as Transaction instances.
151
+ #
152
+ # @param page [String,Integer] (optional) response will default to 1
153
+ # @param per_page [String,Integer] (optional) response will default to 20
154
+ #
155
+ # @raise [SynapsePayRest::Error]
156
+ #
157
+ # @return [Array<SynapsePayRest::Transaction>]
158
+ def transactions(**options)
159
+ Transaction.all(node: self, **options)
160
+ end
161
+
162
+ # Queries the API for a transaction belonging to this node by transaction id
163
+ # and returns a Transaction instance if found.
164
+ #
165
+ # @param id [String] id of the transaction to find
166
+ #
167
+ # @raise [SynapsePayRest::Error] if not found or other HTTP error
168
+ #
169
+ # @return [SynapsePayRest::Transaction]
170
+ def find_transaction(id:)
171
+ raise ArgumentError, 'id must be a String' unless id.is_a?(String)
172
+
173
+ Transaction.find(node: self, id: id)
174
+ end
175
+
176
+ # Deactivates the node.
177
+ #
178
+ # @raise [SynapsePayRest::Error]
179
+ #
180
+ # @return [:success]
181
+ def deactivate
182
+ user.authenticate
183
+ user.client.nodes.delete(node_id: id)
184
+ :success
185
+ end
186
+
187
+ # Checks if two BaseNode instances have same id (different instances of same record).
188
+ def ==(other)
189
+ other.instance_of?(self.class) && !id.nil? && id == other.id
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,19 @@
1
+ module SynapsePayRest
2
+ # Represents an Indian bank account for EFT credits.
3
+ class EftIndNode < EftNode
4
+ class << self
5
+ private
6
+
7
+ def payload_for_create(nickname:, account_number:, ifsc:, **options)
8
+ args = {
9
+ type: 'EFT-IND',
10
+ nickname: nickname,
11
+ account_number: account_number
12
+ }.merge(options)
13
+ payload = super(args)
14
+ payload['info']['ifsc'] = ifsc
15
+ payload
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module SynapsePayRest
2
+ # Parent of all EFT nodes. Should not be instantiated directly.
3
+ #
4
+ # @todo Make this a module instead.
5
+ class EftNode < BaseNode
6
+ class << self
7
+ private
8
+
9
+ def payload_for_create(type:, nickname:, account_number:, **options)
10
+ payload = {
11
+ 'type' => type,
12
+ 'info' => {
13
+ 'nickname' => nickname,
14
+ 'account_num' => account_number
15
+ }
16
+ }
17
+
18
+ # optional payload fields
19
+ extra = {}
20
+ extra['supp_id'] = options[:supp_id] if options[:supp_id]
21
+ extra['gateway_restricted'] = options[:gateway_restricted] if options[:gateway_restricted]
22
+ payload['extra'] = extra if extra.any?
23
+ payload
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ module SynapsePayRest
2
+ # Represents a Nepali bank account for EFT credits.
3
+ class EftNpNode < EftNode
4
+ class << self
5
+ private
6
+
7
+ def payload_for_create(nickname:, bank_name:, account_number:, **options)
8
+ args = {
9
+ type: 'EFT-NP',
10
+ nickname: nickname,
11
+ account_number: account_number
12
+ }.merge(options)
13
+ payload = super(args)
14
+ payload['info']['bank_name'] = bank_name
15
+ payload
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module SynapsePayRest
2
+ # Non-USD payments can be tracked using the IOU node type. This can be
3
+ # anything (e.g. another currency or commodity).
4
+ class IouNode < BaseNode
5
+ class << self
6
+ private
7
+
8
+ def payload_for_create(nickname:, currency:, **options)
9
+ payload = {
10
+ 'type' => 'IOU',
11
+ 'info' => {
12
+ 'nickname' => nickname,
13
+ 'balance' => {
14
+ 'currency' => currency
15
+ }
16
+ }
17
+ }
18
+ # optional payload fields
19
+ extra = {}
20
+ extra['supp_id'] = options[:supp_id] if options[:supp_id]
21
+ extra['gateway_restricted'] = options[:gateway_restricted] if options[:gateway_restricted]
22
+ payload['extra'] = extra if extra.any?
23
+ payload
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,99 @@
1
+ module SynapsePayRest
2
+ # Factory for BaseNode subclasses.
3
+ #
4
+ # @todo use mixins to remove duplication between Node and BaseNode
5
+ module Node
6
+
7
+ # Node type to node class mappings.
8
+ NODE_TYPES_TO_CLASSES = {
9
+ 'ACH-US' => AchUsNode,
10
+ 'EFT-NP' => EftNpNode,
11
+ 'EFT-IND' => EftIndNode,
12
+ 'IOU' => IouNode,
13
+ 'RESERVE-US' => ReserveUsNode,
14
+ 'SYNAPSE-IND' => SynapseIndNode,
15
+ 'SYNAPSE-NP' => SynapseNpNode,
16
+ 'SYNAPSE-US' => SynapseUsNode,
17
+ 'WIRE-INT' => WireIntNode,
18
+ 'WIRE-US' => WireUsNode
19
+ }.freeze
20
+
21
+ class << self
22
+ # Queries the API for a node with the supplied id belong to the supplied user,
23
+ # and returns a node instance from the response data.
24
+ #
25
+ # @param user [SynapsePayRest::User]
26
+ # @param id [String] id of the node to find
27
+ #
28
+ # @raise [SynapsePayRest::Error] if HTTP error
29
+ #
30
+ # @return [SynapsePayRest::BaseNode] subclass depends on node type
31
+ def find(user:, id:)
32
+ raise ArgumentError, 'user must be a User object' unless user.is_a?(User)
33
+ raise ArgumentError, 'id must be a String' unless id.is_a?(String)
34
+
35
+ user.authenticate
36
+ response = user.client.nodes.get(user_id: user.id, node_id: id)
37
+ create_from_response(user, response)
38
+ end
39
+
40
+ # Queries the API for all nodes belonging to the supplied user (with optional
41
+ # filters) and returns them as node instances.
42
+ #
43
+ # @param user [SynapsePayRest::User]
44
+ # @param page [String,Integer] (optional) response will default to 1
45
+ # @param per_page [String,Integer] (optional) response will default to 20
46
+ # @param type [String] (optional)
47
+ # @see https://docs.synapsepay.com/docs/node-resources node types
48
+ #
49
+ # @raise [SynapsePayRest::Error] if HTTP error or invalid argument format
50
+ #
51
+ # @return [Array<SynapsePayRest::BaseNode>] subclass depends on node types
52
+ def all(user:, page: nil, per_page: nil, type: nil)
53
+ raise ArgumentError, 'user must be a User object' unless user.is_a?(User)
54
+ [page, per_page].each do |arg|
55
+ if arg && (!arg.is_a?(Integer) || arg < 1)
56
+ raise ArgumentError, "#{arg} must be nil or an Integer >= 1"
57
+ end
58
+ end
59
+ unless type.nil? || NODE_TYPES_TO_CLASSES.keys.include?(type)
60
+ raise ArgumentError, "type must be nil or in #{NODE_TYPES_TO_CLASSES.keys}"
61
+ end
62
+
63
+ user.authenticate
64
+ response = user.client.nodes.get(page: page, per_page: per_page, type: type)
65
+ create_multiple_from_response(user, response['nodes'])
66
+ end
67
+
68
+ # Queries the API for all nodes belonging to the supplied user (with optional
69
+ # filters) and matching the given type.
70
+ #
71
+ # @param user [SynapsePayRest::User]
72
+ # @param type [String]
73
+ # @see https://docs.synapsepay.com/docs/node-resources node types
74
+ # @param page [String,Integer] (optional) response will default to 1
75
+ # @param per_page [String,Integer] (optional) response will default to 20
76
+ #
77
+ # @raise [SynapsePayRest::Error] if HTTP error or invalid argument format
78
+ #
79
+ # @return [Array<SynapsePayRest::BaseNode>] BaseNode will be subclass corresponding to type arg
80
+ def by_type(user:, type:, page: nil, per_page: nil)
81
+ all(user: user, page: page, per_page: per_page, type: type)
82
+ end
83
+
84
+ private
85
+
86
+ # determines the proper node type to instantiate from the response
87
+ # implemented differently in each BaseNode subclass
88
+ def create_from_response(user, response)
89
+ klass = NODE_TYPES_TO_CLASSES.fetch(response['type'])
90
+ klass.create_from_response(user, response)
91
+ end
92
+
93
+ def create_multiple_from_response(user, response)
94
+ return [] if response.empty?
95
+ response.map { |node_data| create_from_response(user, node_data)}
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,23 @@
1
+ module SynapsePayRest
2
+ # A Synapse node allowing any user to hold funds. You can use this node for
3
+ # storing reserves with Synapse.
4
+ class ReserveUsNode < BaseNode
5
+ class << self
6
+ private
7
+
8
+ def payload_for_create(nickname:, **options)
9
+ payload = {
10
+ 'type' => 'RESERVE-US',
11
+ 'info' => {
12
+ 'nickname' => nickname,
13
+ }
14
+ }
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,22 @@
1
+ module SynapsePayRest
2
+ # Represents a Synapse node allowing any user to hold Indian Rupees.
3
+ class SynapseIndNode < SynapseNode
4
+ class << self
5
+ private
6
+
7
+ def payload_for_create(nickname:, **options)
8
+ args = {
9
+ type: 'SYNAPSE-IND',
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,25 @@
1
+ module SynapsePayRest
2
+ # Parent of all Synapse nodes. Should not be instantiated directly.
3
+ #
4
+ # @todo Make this a module instead.
5
+ class SynapseNode < BaseNode
6
+ class << self
7
+ private
8
+
9
+ def payload_for_create(type:, nickname:, **options)
10
+ payload = {
11
+ 'type' => type,
12
+ 'info' => {
13
+ 'nickname' => nickname
14
+ }
15
+ }
16
+ # optional payload fields
17
+ extra = {}
18
+ extra['supp_id'] = options[:supp_id] if options[:supp_id]
19
+ extra['gateway_restricted'] = options[:gateway_restricted] if options[:gateway_restricted]
20
+ payload['extra'] = extra if extra.any?
21
+ payload
22
+ end
23
+ end
24
+ end
25
+ end