zai_payment 1.3.2 → 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.
@@ -0,0 +1,363 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ZaiPayment
4
+ module Resources
5
+ # Item resource for managing Zai items (transactions/payments)
6
+ #
7
+ # @see https://developer.hellozai.com/reference/listitems
8
+ class Item
9
+ attr_reader :client
10
+
11
+ # Map of attribute keys to API field names
12
+ FIELD_MAPPING = {
13
+ id: :id,
14
+ name: :name,
15
+ amount: :amount,
16
+ payment_type: :payment_type,
17
+ buyer_id: :buyer_id,
18
+ seller_id: :seller_id,
19
+ fee_ids: :fee_ids,
20
+ description: :description,
21
+ currency: :currency,
22
+ custom_descriptor: :custom_descriptor,
23
+ buyer_url: :buyer_url,
24
+ seller_url: :seller_url,
25
+ tax_invoice: :tax_invoice
26
+ }.freeze
27
+
28
+ def initialize(client: nil)
29
+ @client = client || Client.new
30
+ end
31
+
32
+ # List all items
33
+ #
34
+ # @param limit [Integer] number of records to return (default: 10, max: 200)
35
+ # @param offset [Integer] number of records to skip (default: 0)
36
+ # @param search [String] optional text value to search within item description
37
+ # @param created_before [String] optional ISO 8601 date/time to filter items created before
38
+ # (e.g. '2020-02-27T23:54:59Z')
39
+ # @param created_after [String] optional ISO 8601 date/time to filter items created after
40
+ # (e.g. '2020-02-27T23:54:59Z')
41
+ # @return [Response] the API response containing items array
42
+ #
43
+ # @example List all items
44
+ # items = ZaiPayment::Resources::Item.new
45
+ # response = items.list
46
+ # response.data # => [{"id" => "...", "name" => "..."}, ...]
47
+ #
48
+ # @example List items with search
49
+ # response = items.list(search: "product")
50
+ #
51
+ # @example List items created within a date range
52
+ # response = items.list(
53
+ # created_after: "2024-01-01T00:00:00Z",
54
+ # created_before: "2024-12-31T23:59:59Z"
55
+ # )
56
+ #
57
+ # @see https://developer.hellozai.com/reference/listitems
58
+ def list(limit: 10, offset: 0, search: nil, created_before: nil, created_after: nil)
59
+ params = {
60
+ limit: limit,
61
+ offset: offset
62
+ }
63
+
64
+ params[:search] = search if search
65
+ params[:created_before] = created_before if created_before
66
+ params[:created_after] = created_after if created_after
67
+
68
+ client.get('/items', params: params)
69
+ end
70
+
71
+ # Get a specific item by ID
72
+ #
73
+ # @param item_id [String] the item ID
74
+ # @return [Response] the API response containing item details
75
+ #
76
+ # @example
77
+ # items = ZaiPayment::Resources::Item.new
78
+ # response = items.show("item_id")
79
+ # response.data # => {"items" => {"id" => "item_id", "name" => "...", ...}}
80
+ #
81
+ # @see https://developer.hellozai.com/reference/showitem
82
+ def show(item_id)
83
+ validate_id!(item_id, 'item_id')
84
+ client.get("/items/#{item_id}")
85
+ end
86
+
87
+ # Create a new item
88
+ #
89
+ # @param attributes [Hash] item attributes
90
+ # @option attributes [String] :id Optional unique ID for the item
91
+ # @option attributes [String] :name (Required) Name of the item
92
+ # @option attributes [Integer] :amount (Required) Amount in cents
93
+ # @option attributes [String] :payment_type (Required) Payment type (1-7, default: 2)
94
+ # @option attributes [String] :buyer_id (Required) Buyer user ID
95
+ # @option attributes [String] :seller_id (Required) Seller user ID
96
+ # @option attributes [Array<String>] :fee_ids Optional array of fee IDs
97
+ # @option attributes [String] :description Optional description
98
+ # @option attributes [String] :currency Optional currency code (e.g., 'AUD')
99
+ # @option attributes [String] :custom_descriptor Optional custom descriptor
100
+ # @option attributes [String] :buyer_url Optional buyer URL
101
+ # @option attributes [String] :seller_url Optional seller URL
102
+ # @option attributes [Boolean] :tax_invoice Optional tax invoice flag
103
+ # @return [Response] the API response containing created item
104
+ #
105
+ # @example
106
+ # items = ZaiPayment::Resources::Item.new
107
+ # response = items.create(
108
+ # name: "Product Purchase",
109
+ # amount: 10000,
110
+ # payment_type: 2,
111
+ # buyer_id: "buyer-123",
112
+ # seller_id: "seller-456",
113
+ # description: "Purchase of product XYZ"
114
+ # )
115
+ #
116
+ # @see https://developer.hellozai.com/reference/createitem
117
+ def create(**attributes)
118
+ validate_create_attributes!(attributes)
119
+
120
+ body = build_item_body(attributes)
121
+ client.post('/items', body: body)
122
+ end
123
+
124
+ # Update an existing item
125
+ #
126
+ # @param item_id [String] the item ID
127
+ # @param attributes [Hash] item attributes to update
128
+ # @option attributes [String] :name Name of the item
129
+ # @option attributes [Integer] :amount Amount in cents
130
+ # @option attributes [String] :description Description
131
+ # @option attributes [String] :buyer_id Buyer user ID
132
+ # @option attributes [String] :seller_id Seller user ID
133
+ # @option attributes [Array<String>] :fee_ids Array of fee IDs
134
+ # @option attributes [String] :custom_descriptor Custom descriptor
135
+ # @option attributes [String] :buyer_url Buyer URL
136
+ # @option attributes [String] :seller_url Seller URL
137
+ # @option attributes [Boolean] :tax_invoice Tax invoice flag
138
+ # @return [Response] the API response containing updated item
139
+ #
140
+ # @example
141
+ # items = ZaiPayment::Resources::Item.new
142
+ # response = items.update(
143
+ # "item_id",
144
+ # name: "Updated Product Name",
145
+ # description: "Updated description"
146
+ # )
147
+ #
148
+ # @see https://developer.hellozai.com/reference/updateitem
149
+ def update(item_id, **attributes)
150
+ validate_id!(item_id, 'item_id')
151
+
152
+ body = build_item_body(attributes)
153
+
154
+ raise Errors::ValidationError, 'At least one attribute must be provided for update' if body.empty?
155
+
156
+ client.patch("/items/#{item_id}", body: body)
157
+ end
158
+
159
+ # Delete an item
160
+ #
161
+ # @param item_id [String] the item ID
162
+ # @return [Response] the API response
163
+ #
164
+ # @example
165
+ # items = ZaiPayment::Resources::Item.new
166
+ # response = items.delete("item_id")
167
+ #
168
+ # @see https://developer.hellozai.com/reference/deleteitem
169
+ def delete(item_id)
170
+ validate_id!(item_id, 'item_id')
171
+ client.delete("/items/#{item_id}")
172
+ end
173
+
174
+ # Show item seller
175
+ #
176
+ # @param item_id [String] the item ID
177
+ # @return [Response] the API response containing seller details
178
+ #
179
+ # @example
180
+ # items = ZaiPayment::Resources::Item.new
181
+ # response = items.show_seller("item_id")
182
+ # response.data # => {"users" => {"id" => "...", "email" => "...", ...}}
183
+ #
184
+ # @see https://developer.hellozai.com/reference/showitemseller
185
+ def show_seller(item_id)
186
+ validate_id!(item_id, 'item_id')
187
+ client.get("/items/#{item_id}/sellers")
188
+ end
189
+
190
+ # Show item buyer
191
+ #
192
+ # @param item_id [String] the item ID
193
+ # @return [Response] the API response containing buyer details
194
+ #
195
+ # @example
196
+ # items = ZaiPayment::Resources::Item.new
197
+ # response = items.show_buyer("item_id")
198
+ # response.data # => {"users" => {"id" => "...", "email" => "...", ...}}
199
+ #
200
+ # @see https://developer.hellozai.com/reference/showitembuyer
201
+ def show_buyer(item_id)
202
+ validate_id!(item_id, 'item_id')
203
+ client.get("/items/#{item_id}/buyers")
204
+ end
205
+
206
+ # Show item fees
207
+ #
208
+ # @param item_id [String] the item ID
209
+ # @return [Response] the API response containing fees details
210
+ #
211
+ # @example
212
+ # items = ZaiPayment::Resources::Item.new
213
+ # response = items.show_fees("item_id")
214
+ # response.data # => {"fees" => [{"id" => "...", "amount" => "...", ...}]}
215
+ #
216
+ # @see https://developer.hellozai.com/reference/showitemfees
217
+ def show_fees(item_id)
218
+ validate_id!(item_id, 'item_id')
219
+ client.get("/items/#{item_id}/fees")
220
+ end
221
+
222
+ # Show item wire details
223
+ #
224
+ # @param item_id [String] the item ID
225
+ # @return [Response] the API response containing wire transfer details
226
+ #
227
+ # @example
228
+ # items = ZaiPayment::Resources::Item.new
229
+ # response = items.show_wire_details("item_id")
230
+ # response.data # => {"items" => {"wire_details" => {...}}}
231
+ #
232
+ # @see https://developer.hellozai.com/reference/showitemwiredetails
233
+ def show_wire_details(item_id)
234
+ validate_id!(item_id, 'item_id')
235
+ client.get("/items/#{item_id}/wire_details")
236
+ end
237
+
238
+ # List item transactions
239
+ #
240
+ # @param item_id [String] the item ID
241
+ # @param limit [Integer] number of records to return (default: 10)
242
+ # @param offset [Integer] number of records to skip (default: 0)
243
+ # @return [Response] the API response containing transactions array
244
+ #
245
+ # @example
246
+ # items = ZaiPayment::Resources::Item.new
247
+ # response = items.list_transactions("item_id")
248
+ # response.data # => {"transactions" => [{"id" => "...", "amount" => "...", ...}]}
249
+ #
250
+ # @see https://developer.hellozai.com/reference/listitemtransactions
251
+ def list_transactions(item_id, limit: 10, offset: 0)
252
+ validate_id!(item_id, 'item_id')
253
+
254
+ params = {
255
+ limit: limit,
256
+ offset: offset
257
+ }
258
+
259
+ client.get("/items/#{item_id}/transactions", params: params)
260
+ end
261
+
262
+ # List item batch transactions
263
+ #
264
+ # @param item_id [String] the item ID
265
+ # @param limit [Integer] number of records to return (default: 10)
266
+ # @param offset [Integer] number of records to skip (default: 0)
267
+ # @return [Response] the API response containing batch transactions array
268
+ #
269
+ # @example
270
+ # items = ZaiPayment::Resources::Item.new
271
+ # response = items.list_batch_transactions("item_id")
272
+ # response.data # => {"batch_transactions" => [{"id" => "...", ...}]}
273
+ #
274
+ # @see https://developer.hellozai.com/reference/listitembatchtransactions
275
+ def list_batch_transactions(item_id, limit: 10, offset: 0)
276
+ validate_id!(item_id, 'item_id')
277
+
278
+ params = {
279
+ limit: limit,
280
+ offset: offset
281
+ }
282
+
283
+ client.get("/items/#{item_id}/batch_transactions", params: params)
284
+ end
285
+
286
+ # Show item status
287
+ #
288
+ # @param item_id [String] the item ID
289
+ # @return [Response] the API response containing status details
290
+ #
291
+ # @example
292
+ # items = ZaiPayment::Resources::Item.new
293
+ # response = items.show_status("item_id")
294
+ # response.data # => {"items" => {"state" => "...", ...}}
295
+ #
296
+ # @see https://developer.hellozai.com/reference/showitemstatus
297
+ def show_status(item_id)
298
+ validate_id!(item_id, 'item_id')
299
+ client.get("/items/#{item_id}/status")
300
+ end
301
+
302
+ private
303
+
304
+ def validate_id!(value, field_name)
305
+ return unless value.nil? || value.to_s.strip.empty?
306
+
307
+ raise Errors::ValidationError, "#{field_name} is required and cannot be blank"
308
+ end
309
+
310
+ def validate_presence!(value, field_name)
311
+ return unless value.nil? || value.to_s.strip.empty?
312
+
313
+ raise Errors::ValidationError, "#{field_name} is required and cannot be blank"
314
+ end
315
+
316
+ def validate_create_attributes!(attributes)
317
+ validate_required_attributes!(attributes)
318
+ validate_amount!(attributes[:amount]) if attributes[:amount]
319
+ validate_payment_type!(attributes[:payment_type]) if attributes[:payment_type]
320
+ end
321
+
322
+ def validate_required_attributes!(attributes)
323
+ required_fields = %i[name amount payment_type buyer_id seller_id]
324
+
325
+ missing_fields = required_fields.select do |field|
326
+ attributes[field].nil? || (attributes[field].respond_to?(:empty?) && attributes[field].to_s.strip.empty?)
327
+ end
328
+
329
+ return if missing_fields.empty?
330
+
331
+ raise Errors::ValidationError,
332
+ "Missing required fields: #{missing_fields.join(', ')}"
333
+ end
334
+
335
+ def validate_amount!(amount)
336
+ return if amount.is_a?(Integer) && amount.positive?
337
+
338
+ raise Errors::ValidationError, 'amount must be a positive integer (in cents)'
339
+ end
340
+
341
+ def validate_payment_type!(payment_type)
342
+ # Payment types: 1-7 (2 is default)
343
+ valid_types = %w[1 2 3 4 5 6 7]
344
+ return if valid_types.include?(payment_type.to_s)
345
+
346
+ raise Errors::ValidationError, 'payment_type must be between 1 and 7'
347
+ end
348
+
349
+ def build_item_body(attributes)
350
+ body = {}
351
+
352
+ attributes.each do |key, value|
353
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
354
+
355
+ api_field = FIELD_MAPPING[key]
356
+ body[api_field] = value if api_field
357
+ end
358
+
359
+ body
360
+ end
361
+ end
362
+ end
363
+ end
@@ -107,19 +107,22 @@ module ZaiPayment
107
107
  # @option attributes [String] :id Optional unique ID for the user. If not provided,
108
108
  # Zai will generate one automatically. Cannot contain '.' character.
109
109
  # Useful for mapping to your existing system's user IDs.
110
+ # @option attributes [String] :user_type User type ('payin' or 'payout').
111
+ # This determines which fields are required.
110
112
  # @option attributes [String] :email (Required) user's email address
111
113
  # @option attributes [String] :first_name (Required) user's first name
112
114
  # @option attributes [String] :last_name (Required) user's last name
113
115
  # @option attributes [String] :country (Required) user's country code (ISO 3166-1 alpha-3)
114
- # @option attributes [String] :user_type Optional user type ('payin' or 'payout')
116
+ # @option attributes [String] :address_line1 (Required for payout) user's address line 1
117
+ # @option attributes [String] :city (Required for payout) user's city
118
+ # @option attributes [String] :state (Required for payout) user's state
119
+ # @option attributes [String] :zip (Required for payout) user's postal/zip code
120
+ # @option attributes [String] :dob (Required for payout) user's date of birth (DD/MM/YYYY)
121
+ # @option attributes [String] :device_id device ID for fraud prevention (required when charging card)
122
+ # @option attributes [String] :ip_address IP address for fraud prevention (required when charging card)
123
+ # @option attributes [String] :address_line2 user's address line 2
115
124
  # @option attributes [String] :mobile user's mobile phone number (international format with '+')
116
125
  # @option attributes [String] :phone user's phone number
117
- # @option attributes [String] :address_line1 user's address line 1
118
- # @option attributes [String] :address_line2 user's address line 2
119
- # @option attributes [String] :city user's city
120
- # @option attributes [String] :state user's state
121
- # @option attributes [String] :zip user's postal/zip code
122
- # @option attributes [String] :dob user's date of birth (DD/MM/YYYY)
123
126
  # @option attributes [String] :government_number user's government ID number (SSN, TFN, etc.)
124
127
  # @option attributes [String] :drivers_license_number driving license number
125
128
  # @option attributes [String] :drivers_license_state state section of the user's driving license
@@ -129,13 +132,12 @@ module ZaiPayment
129
132
  # @option attributes [String] :custom_descriptor custom descriptor for bundle direct debit statements
130
133
  # @option attributes [String] :authorized_signer_title job title for AMEX merchants (e.g., Director)
131
134
  # @option attributes [Hash] :company company details (creates a company for the user)
132
- # @option attributes [String] :device_id device ID for fraud prevention
133
- # @option attributes [String] :ip_address IP address for fraud prevention
134
135
  # @return [Response] the API response containing created user
135
136
  #
136
137
  # @example Create a payin user (buyer) with auto-generated ID
137
138
  # users = ZaiPayment::Resources::User.new
138
139
  # response = users.create(
140
+ # user_type: "payin",
139
141
  # email: "buyer@example.com",
140
142
  # first_name: "John",
141
143
  # last_name: "Doe",
@@ -146,25 +148,29 @@ module ZaiPayment
146
148
  # state: "NY",
147
149
  # zip: "10001"
148
150
  # )
151
+ # # Note: device_id and ip_address are not required at user creation,
152
+ # # but will be required when creating an item and charging a card
149
153
  #
150
154
  # @example Create a payin user with custom ID
151
155
  # users = ZaiPayment::Resources::User.new
152
156
  # response = users.create(
153
157
  # id: "buyer-#{your_user_id}",
158
+ # user_type: "payin",
154
159
  # email: "buyer@example.com",
155
160
  # first_name: "John",
156
161
  # last_name: "Doe",
157
162
  # country: "USA"
158
163
  # )
159
164
  #
160
- # @example Create a payout user (seller/merchant)
165
+ # @example Create a payout user (seller/merchant) - individual
161
166
  # users = ZaiPayment::Resources::User.new
162
167
  # response = users.create(
168
+ # user_type: "payout",
163
169
  # email: "seller@example.com",
164
170
  # first_name: "Jane",
165
171
  # last_name: "Smith",
166
172
  # country: "AUS",
167
- # dob: "19900101",
173
+ # dob: "01/01/1990",
168
174
  # address_line1: "456 Market St",
169
175
  # city: "Sydney",
170
176
  # state: "NSW",
@@ -172,13 +178,19 @@ module ZaiPayment
172
178
  # mobile: "+61412345678"
173
179
  # )
174
180
  #
175
- # @example Create a user with company details
181
+ # @example Create a payout user with company details
176
182
  # users = ZaiPayment::Resources::User.new
177
183
  # response = users.create(
184
+ # user_type: "payout",
178
185
  # email: "business@example.com",
179
186
  # first_name: "John",
180
187
  # last_name: "Doe",
181
188
  # country: "AUS",
189
+ # dob: "15/06/1985",
190
+ # address_line1: "789 Business Ave",
191
+ # city: "Melbourne",
192
+ # state: "VIC",
193
+ # zip: "3000",
182
194
  # mobile: "+61412345678",
183
195
  # authorized_signer_title: "Director",
184
196
  # company: {
@@ -186,13 +198,12 @@ module ZaiPayment
186
198
  # legal_name: "ABC Pty Ltd",
187
199
  # tax_number: "123456789",
188
200
  # business_email: "admin@abc.com",
189
- # country: "AUS",
190
- # charge_tax: true,
191
201
  # address_line1: "123 Business St",
192
202
  # city: "Melbourne",
193
203
  # state: "VIC",
194
204
  # zip: "3000",
195
- # phone: "+61398765432"
205
+ # phone: "+61398765432",
206
+ # country: "AUS"
196
207
  # }
197
208
  # )
198
209
  #
@@ -274,12 +285,23 @@ module ZaiPayment
274
285
  validate_country!(attributes[:country])
275
286
  validate_dob!(attributes[:dob]) if attributes[:dob]
276
287
  validate_user_id!(attributes[:id]) if attributes[:id]
277
- validate_company!(attributes[:company]) if attributes[:company]
288
+ validate_company!(attributes[:company], attributes[:user_type]) if attributes[:company]
278
289
  end
279
290
 
280
291
  def validate_required_attributes!(attributes)
292
+ # Base required fields for all users
281
293
  required_fields = %i[email first_name last_name country]
282
294
 
295
+ # Additional required fields for payout users
296
+ user_type = attributes[:user_type]&.to_s&.downcase
297
+ if user_type == USER_TYPE_PAYOUT
298
+ # For payout users, these fields become required
299
+ required_fields += %i[address_line1 city state zip dob]
300
+ end
301
+
302
+ # NOTE: device_id and ip_address are NOT required at user creation for payin users.
303
+ # They are only required later when an item is created and a card is charged.
304
+
283
305
  missing_fields = required_fields.select do |field|
284
306
  attributes[field].nil? || attributes[field].to_s.strip.empty?
285
307
  end
@@ -329,15 +351,11 @@ module ZaiPayment
329
351
  raise Errors::ValidationError, 'id cannot be blank if provided'
330
352
  end
331
353
 
332
- def validate_company!(company)
354
+ def validate_company!(company, user_type = nil)
333
355
  return unless company.is_a?(Hash)
334
356
 
335
- # Required company fields
336
- required_company_fields = %i[name legal_name tax_number business_email country]
337
-
338
- missing_fields = required_company_fields.select do |field|
339
- company[field].nil? || company[field].to_s.strip.empty?
340
- end
357
+ required_fields = required_company_fields(user_type)
358
+ missing_fields = find_missing_company_fields(company, required_fields)
341
359
 
342
360
  return if missing_fields.empty?
343
361
 
@@ -345,6 +363,26 @@ module ZaiPayment
345
363
  "Company is missing required fields: #{missing_fields.join(', ')}"
346
364
  end
347
365
 
366
+ def required_company_fields(user_type)
367
+ base_fields = %i[name legal_name tax_number business_email]
368
+ additional_fields = payout_company?(user_type) ? payout_company_fields : %i[country]
369
+ base_fields + additional_fields
370
+ end
371
+
372
+ def payout_company?(user_type)
373
+ user_type&.to_s&.downcase == USER_TYPE_PAYOUT
374
+ end
375
+
376
+ def payout_company_fields
377
+ %i[address_line1 city state zip phone country]
378
+ end
379
+
380
+ def find_missing_company_fields(company, required_fields)
381
+ required_fields.select do |field|
382
+ company[field].nil? || company[field].to_s.strip.empty?
383
+ end
384
+ end
385
+
348
386
  def build_user_body(attributes) # rubocop:disable Metrics/CyclomaticComplexity
349
387
  body = {}
350
388
 
@@ -30,9 +30,14 @@ module ZaiPayment
30
30
  end
31
31
 
32
32
  # Get the data from the response body
33
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
33
34
  def data
34
- body.is_a?(Hash) ? body['webhooks'] || body['users'] || body : body
35
+ return body unless body.is_a?(Hash)
36
+
37
+ body['webhooks'] || body['users'] || body['items'] || body['fees'] ||
38
+ body['transactions'] || body['batch_transactions'] || body
35
39
  end
40
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
36
41
 
37
42
  # Get pagination or metadata info
38
43
  def meta
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZaiPayment
4
- VERSION = '1.3.2'
4
+ VERSION = '2.0.0'
5
5
  end
data/lib/zai_payment.rb CHANGED
@@ -12,6 +12,7 @@ require_relative 'zai_payment/client'
12
12
  require_relative 'zai_payment/response'
13
13
  require_relative 'zai_payment/resources/webhook'
14
14
  require_relative 'zai_payment/resources/user'
15
+ require_relative 'zai_payment/resources/item'
15
16
 
16
17
  module ZaiPayment
17
18
  class << self
@@ -45,5 +46,10 @@ module ZaiPayment
45
46
  def users
46
47
  @users ||= Resources::User.new(client: Client.new(base_endpoint: :core_base))
47
48
  end
49
+
50
+ # @return [ZaiPayment::Resources::Item] item resource instance
51
+ def items
52
+ @items ||= Resources::Item.new
53
+ end
48
54
  end
49
55
  end