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