zai_payment 2.3.2 → 2.4.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.
@@ -25,6 +25,22 @@ module ZaiPayment
25
25
  tax_invoice: :tax_invoice
26
26
  }.freeze
27
27
 
28
+ ITEM_PAYMENT_ATTRIBUTES = {
29
+ account_id: :account_id,
30
+ device_id: :device_id,
31
+ ip_address: :ip_address,
32
+ cvv: :cvv,
33
+ merchant_phone: :merchant_phone
34
+ }.freeze
35
+
36
+ ITEM_ASYNC_PAYMENT_ATTRIBUTES = {
37
+ account_id: :account_id,
38
+ request_three_d_secure: :request_three_d_secure
39
+ }.freeze
40
+
41
+ # Valid values for request_three_d_secure parameter
42
+ REQUEST_THREE_D_SECURE_VALUES = %w[automatic challenge any].freeze
43
+
28
44
  def initialize(client: nil)
29
45
  @client = client || Client.new
30
46
  end
@@ -299,6 +315,197 @@ module ZaiPayment
299
315
  client.get("/items/#{item_id}/status")
300
316
  end
301
317
 
318
+ # Make a payment
319
+ #
320
+ # @param item_id [String] the item ID
321
+ # @option attributes [String] :account_id Required account ID
322
+ # @option attributes [String] :device_id Optional device ID
323
+ # @option attributes [String] :ip_address Optional IP address
324
+ # @option attributes [String] :cvv Optional CVV
325
+ # @option attributes [String] :merchant_phone Optional merchant phone number
326
+ # @return [Response] the API response containing payment details
327
+ #
328
+ # @example Make a payment with required parameters
329
+ # items = ZaiPayment::Resources::Item.new
330
+ # response = items.make_payment("item_id", account_id: "account_id")
331
+ # response.data # => {"items" => {"id" => "...", "amount" => "...", ...}}
332
+ #
333
+ # @example Make a payment with optional parameters
334
+ # response = items.make_payment(
335
+ # "item_id",
336
+ # account_id: "account_id",
337
+ # device_id: "device_789",
338
+ # ip_address: "192.168.1.1",
339
+ # cvv: "123",
340
+ # merchant_phone: "+1234567890"
341
+ # )
342
+ #
343
+ # @see https://developer.hellozai.com/reference/makepayment
344
+ def make_payment(item_id, **attributes)
345
+ validate_id!(item_id, 'item_id')
346
+
347
+ body = build_item_payment_body(attributes)
348
+
349
+ client.patch("/items/#{item_id}/make_payment", body: body)
350
+ end
351
+
352
+ # Cancel an item
353
+
354
+ # @param item_id [String] the item ID
355
+ # @return [Response] the API response containing cancellation details
356
+ #
357
+ # @example Cancel a payment
358
+ # items = ZaiPayment::Resources::Item.new
359
+ # response = items.cancel("item_id")
360
+ # response.data # => {"items" => {"id" => "...", "state" => "...", ...}}
361
+ #
362
+ # @see https://developer.hellozai.com/reference/cancelitem
363
+ def cancel(item_id)
364
+ validate_id!(item_id, 'item_id')
365
+ client.patch("/items/#{item_id}/cancel")
366
+ end
367
+
368
+ # Refund an item
369
+ #
370
+ # @param item_id [String] the item ID
371
+ # @option attributes [String] :refund_amount Optional refund amount
372
+ # @option attributes [String] :refund_message Optional refund message
373
+ # @option attributes [String] :account_id Optional account ID
374
+ # @return [Response] the API response containing refund details
375
+ #
376
+ # @example Refund an item
377
+ # items = ZaiPayment::Resources::Item.new
378
+ # response = items.refund("item_id")
379
+ # response.data # => {"items" => {"id" => "...", "state" => "...", ...}}
380
+ #
381
+ # @example Refund an item with optional parameters
382
+ # response = items.refund(
383
+ # "item_id",
384
+ # refund_amount: 10000,
385
+ # refund_message: "Refund for product XYZ",
386
+ # account_id: "account_789"
387
+ # )
388
+ #
389
+ # @see https://developer.hellozai.com/reference/refund
390
+ def refund(item_id, refund_amount: nil, refund_message: nil, account_id: nil)
391
+ validate_id!(item_id, 'item_id')
392
+
393
+ body = build_refund_body(
394
+ refund_amount: refund_amount,
395
+ refund_message: refund_message,
396
+ account_id: account_id
397
+ )
398
+
399
+ client.patch("/items/#{item_id}/refund", body: body)
400
+ end
401
+
402
+ # Authorize Payment
403
+ #
404
+ # @param item_id [String] the item ID
405
+ # @option attributes [String] :account_id Required account ID
406
+ # @option attributes [String] :cvv Optional CVV
407
+ # @option attributes [String] :merchant_phone Optional merchant phone number
408
+ # @return [Response] the API response containing authorization details
409
+ #
410
+ # @example Authorize a payment
411
+ # items = ZaiPayment::Resources::Item.new
412
+ # response = items.authorize_payment("item_id", account_id: "account_id")
413
+ # response.data # => {"items" => {"id" => "...", "state" => "...", ...}}
414
+ #
415
+ # @example Authorize a payment with optional parameters
416
+ # response = items.authorize_payment(
417
+ # "item_id",
418
+ # account_id: "account_id",
419
+ # cvv: "123",
420
+ # merchant_phone: "+1234567890"
421
+ # )
422
+ #
423
+ # @see https://developer.hellozai.com/reference/authorizepayment
424
+ def authorize_payment(item_id, **attributes)
425
+ validate_id!(item_id, 'item_id')
426
+
427
+ client.patch("/items/#{item_id}/authorize_payment", body: build_item_payment_body(attributes))
428
+ end
429
+
430
+ # Capture Payment
431
+ #
432
+ # @param item_id [String] the item ID
433
+ # @option attributes [String] :amount Optional amount to capture
434
+ # @return [Response] the API response containing capture details
435
+ #
436
+ # @example Capture a payment
437
+ # items = ZaiPayment::Resources::Item.new
438
+ # response = items.capture_payment("item_id", amount: 10000)
439
+ # response.data # => {"items" => {"id" => "...", "state" => "...", ...}}
440
+ #
441
+ # @example Capture a payment with optional parameters
442
+ # response = items.capture_payment(
443
+ # "item_id",
444
+ # amount: 10000
445
+ # )
446
+ #
447
+ # @see https://developer.hellozai.com/reference/capturepayment
448
+ def capture_payment(item_id, **attributes)
449
+ validate_id!(item_id, 'item_id')
450
+
451
+ body = {}
452
+ body[:amount] = attributes[:amount] if attributes[:amount]
453
+
454
+ client.patch("/items/#{item_id}/capture_payment", body: body)
455
+ end
456
+
457
+ # Void Payment
458
+ #
459
+ # @param item_id [String] the item ID
460
+ # @return [Response] the API response containing void details
461
+ #
462
+ # @example Void a payment
463
+ # items = ZaiPayment::Resources::Item.new
464
+ # response = items.void_payment("item_id")
465
+ # response.data # => {"items" => {"id" => "...", "state" => "...", ...}}
466
+ #
467
+ # @see https://developer.hellozai.com/reference/voidpayment
468
+ def void_payment(item_id)
469
+ validate_id!(item_id, 'item_id')
470
+ client.patch("/items/#{item_id}/void_payment")
471
+ end
472
+
473
+ # Make an async Payment
474
+ #
475
+ # Initiate a card payment with 3D Secure 2.0 authentication support. This endpoint
476
+ # initiates the payment process and returns a payment_token required for 3DS2
477
+ # component initialisation.
478
+ #
479
+ # @param item_id [String] the item ID
480
+ # @param account_id [String] Account id of the bank account/credit card, etc making payment (not user id)
481
+ # @option attributes [String] :request_three_d_secure Customise the 3DS (3D Secure) preference for this payment.
482
+ # Allowed values: 'automatic', 'challenge', 'any'. Defaults to 'automatic'.
483
+ # - 'automatic': 3DS preference is determined automatically by the system
484
+ # - 'challenge': Request a 3DS challenge is presented to the user
485
+ # - 'any': Request a 3DS challenge regardless of the challenge flow
486
+ # @return [Response] the API response containing payment details with payment_token
487
+ #
488
+ # @example Make an async payment with required parameters
489
+ # items = ZaiPayment::Resources::Item.new
490
+ # response = items.make_payment_async("item_id", account_id: "account_id")
491
+ # response.data # => {"payment_id" => "...", "payment_token" => "...", "items" => {...}}
492
+ #
493
+ # @example Make an async payment with 3DS challenge
494
+ # response = items.make_payment_async(
495
+ # "item_id",
496
+ # account_id: "account_id",
497
+ # request_three_d_secure: "challenge"
498
+ # )
499
+ #
500
+ # @see https://developer.hellozai.com/reference/makepaymentasync
501
+ def make_payment_async(item_id, **attributes)
502
+ validate_id!(item_id, 'item_id')
503
+
504
+ body = build_async_payment_body(attributes)
505
+
506
+ client.patch("/items/#{item_id}/make_payment_async", body: body)
507
+ end
508
+
302
509
  private
303
510
 
304
511
  def validate_id!(value, field_name)
@@ -346,6 +553,28 @@ module ZaiPayment
346
553
  raise Errors::ValidationError, 'payment_type must be between 1 and 7'
347
554
  end
348
555
 
556
+ def validate_request_three_d_secure!(value)
557
+ return if REQUEST_THREE_D_SECURE_VALUES.include?(value.to_s)
558
+
559
+ raise Errors::ValidationError,
560
+ "request_three_d_secure must be one of: #{REQUEST_THREE_D_SECURE_VALUES.join(', ')}"
561
+ end
562
+
563
+ def build_item_payment_body(attributes)
564
+ validate_presence!(attributes[:account_id], 'account_id')
565
+
566
+ body = {}
567
+
568
+ attributes.each do |key, value|
569
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
570
+
571
+ api_field = ITEM_PAYMENT_ATTRIBUTES[key]
572
+ body[api_field] = value if api_field
573
+ end
574
+
575
+ body
576
+ end
577
+
349
578
  def build_item_body(attributes)
350
579
  body = {}
351
580
 
@@ -358,6 +587,34 @@ module ZaiPayment
358
587
 
359
588
  body
360
589
  end
590
+
591
+ def build_refund_body(refund_amount: nil, refund_message: nil, account_id: nil)
592
+ body = {}
593
+
594
+ body[:refund_amount] = refund_amount if refund_amount
595
+ body[:refund_message] = refund_message if refund_message
596
+ body[:account_id] = account_id if account_id
597
+
598
+ body
599
+ end
600
+
601
+ def build_async_payment_body(attributes)
602
+ validate_presence!(attributes[:account_id], 'account_id')
603
+
604
+ # Validate request_three_d_secure if provided
605
+ validate_request_three_d_secure!(attributes[:request_three_d_secure]) if attributes[:request_three_d_secure]
606
+
607
+ body = {}
608
+
609
+ attributes.each do |key, value|
610
+ next if value.nil? || (value.respond_to?(:empty?) && value.empty?)
611
+
612
+ api_field = ITEM_ASYNC_PAYMENT_ATTRIBUTES[key]
613
+ body[api_field] = value if api_field
614
+ end
615
+
616
+ body
617
+ end
361
618
  end
362
619
  end
363
620
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ZaiPayment
4
- VERSION = '2.3.2'
4
+ VERSION = '2.4.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zai_payment
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.2
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eddy Jaga
@@ -99,7 +99,6 @@ files:
99
99
  - lib/zai_payment/version.rb
100
100
  - readme.md
101
101
  - sig/zai_payment.rbs
102
- - token_auth_implementation_summary.md
103
102
  homepage: https://github.com/Sentia/zai-payment
104
103
  licenses:
105
104
  - MIT
@@ -1,249 +0,0 @@
1
- # Token Auth API Implementation Summary
2
-
3
- ## Overview
4
-
5
- Successfully implemented the **Generate Token** endpoint from the Zai API (https://developer.hellozai.com/reference/generatetoken) to enable secure bank and card account data collection.
6
-
7
- ## What Was Implemented
8
-
9
- ### 1. Core Resource Implementation
10
-
11
- **File:** `lib/zai_payment/resources/token_auth.rb`
12
-
13
- - Created `TokenAuth` resource class following the project's resource pattern
14
- - Implemented `generate(user_id:, token_type:)` method
15
- - Added token type validation (bank or card)
16
- - Added user ID validation
17
- - Support for case-insensitive token types
18
- - Default token type: 'bank'
19
-
20
- **Key Features:**
21
- ```ruby
22
- # Generate a bank token (default)
23
- ZaiPayment.token_auths.generate(user_id: "seller-123")
24
-
25
- # Generate a card token
26
- ZaiPayment.token_auths.generate(user_id: "buyer-123", token_type: "card")
27
- ```
28
-
29
- ### 2. Module Integration
30
-
31
- **File:** `lib/zai_payment.rb`
32
-
33
- - Added `require_relative` for token_auth resource
34
- - Added `token_auths` accessor method
35
- - Configured to use `core_base` endpoint (https://test.api.promisepay.com)
36
-
37
- ### 3. Test Suite
38
-
39
- **File:** `spec/zai_payment/resources/token_auth_spec.rb`
40
-
41
- - 15 comprehensive test cases covering:
42
- - Bank token generation
43
- - Card token generation
44
- - Default token type behavior
45
- - Validation errors (nil, empty, whitespace, invalid types)
46
- - Case-insensitive token types
47
- - Client initialization
48
- - Constants verification
49
- - All tests passing with 95.22% line coverage
50
- - Follows project's testing pattern using Faraday test stubs
51
- - RuboCop compliant
52
-
53
- ### 4. Documentation
54
-
55
- **File:** `examples/token_auths.md` (600+ lines)
56
-
57
- Comprehensive examples including:
58
- - Basic usage patterns
59
- - Bank token collection workflows
60
- - Card token collection workflows
61
- - Rails controller integration
62
- - Service object pattern
63
- - Frontend integration with PromisePay.js
64
- - Complete payment flow examples
65
- - Error handling strategies
66
- - Retry logic with exponential backoff
67
- - Security best practices:
68
- - Token expiry management
69
- - Audit logging
70
- - Rate limiting protection
71
-
72
- **File:** `docs/token_auths.md` (500+ lines)
73
-
74
- Complete technical guide covering:
75
- - When to use Token Auth
76
- - How it works (flow diagram)
77
- - API methods reference
78
- - Token types (bank vs card) with use cases
79
- - Security considerations (PCI compliance, token lifecycle)
80
- - Integration guide (backend + frontend)
81
- - Error handling patterns
82
- - Best practices with code examples
83
- - Related resources and API references
84
-
85
- ### 5. Version Updates
86
-
87
- **File:** `lib/zai_payment/version.rb`
88
-
89
- - Updated version from `2.0.2` to `2.1.0`
90
- - Follows semantic versioning (minor version bump for new feature)
91
-
92
- ### 6. Changelog
93
-
94
- **File:** `changelog.md`
95
-
96
- Added comprehensive release notes for version 2.1.0:
97
- - Feature description
98
- - API methods
99
- - Documentation additions
100
- - Testing coverage
101
- - Link to full changelog
102
-
103
- ### 7. README Updates
104
-
105
- **File:** `readme.md`
106
-
107
- - Added Token Auth to features list (🎫 emoji)
108
- - Added Token Auth section with quick examples
109
- - Updated roadmap (marked Token Auth as Done ✅)
110
- - Added documentation links in Examples & Patterns section
111
-
112
- ### 8. Documentation Index
113
-
114
- **File:** `docs/readme.md`
115
-
116
- - Added token_auths.md to Architecture & Design section
117
- - Added Token Auth Examples to Examples section
118
- - Added Token Auth to Quick Links with guide, examples, and API reference
119
- - Updated documentation structure tree
120
- - Added tips for collecting payment data
121
-
122
- ## API Endpoint
123
-
124
- **POST** `https://test.api.promisepay.com/token_auths`
125
-
126
- **Request Body:**
127
- ```json
128
- {
129
- "token_type": "bank", // or "card"
130
- "user_id": "seller-68611249"
131
- }
132
- ```
133
-
134
- **Response:**
135
- ```json
136
- {
137
- "token_auth": {
138
- "token": "tok_bank_abc123...",
139
- "user_id": "seller-68611249",
140
- "token_type": "bank",
141
- "created_at": "2025-10-24T12:00:00Z",
142
- "expires_at": "2025-10-24T13:00:00Z"
143
- }
144
- }
145
- ```
146
-
147
- ## Usage Example
148
-
149
- ```ruby
150
- # Configure the gem
151
- ZaiPayment.configure do |c|
152
- c.environment = :prelive
153
- c.client_id = ENV['ZAI_CLIENT_ID']
154
- c.client_secret = ENV['ZAI_CLIENT_SECRET']
155
- c.scope = ENV['ZAI_OAUTH_SCOPE']
156
- end
157
-
158
- # Generate a card token for buyer
159
- response = ZaiPayment.token_auths.generate(
160
- user_id: "buyer-12345",
161
- token_type: "card"
162
- )
163
-
164
- token = response.data['token_auth']['token']
165
- # Send token to frontend for use with PromisePay.js
166
- ```
167
-
168
- ## Integration with PromisePay.js
169
-
170
- ```javascript
171
- // Frontend (JavaScript)
172
- PromisePay.setToken(token_from_backend);
173
-
174
- PromisePay.createCardAccount({
175
- card_number: '4111111111111111',
176
- expiry_month: '12',
177
- expiry_year: '2025',
178
- cvv: '123'
179
- }, function(response) {
180
- if (response.error) {
181
- console.error('Error:', response.error);
182
- } else {
183
- const cardAccountId = response.card_accounts.id;
184
- // Send cardAccountId back to backend
185
- }
186
- });
187
- ```
188
-
189
- ## Test Results
190
-
191
- - **Total Tests:** 272 examples
192
- - **Failures:** 0
193
- - **Line Coverage:** 95.22% (518 / 544)
194
- - **Branch Coverage:** 80.79% (143 / 177)
195
- - **RuboCop:** No offenses detected
196
-
197
- ## Files Created
198
-
199
- 1. `lib/zai_payment/resources/token_auth.rb` - Core resource implementation
200
- 2. `spec/zai_payment/resources/token_auth_spec.rb` - Test suite
201
- 3. `examples/token_auths.md` - Comprehensive usage examples
202
- 4. `docs/token_auths.md` - Technical documentation
203
-
204
- ## Files Modified
205
-
206
- 1. `lib/zai_payment.rb` - Added token_auths accessor
207
- 2. `lib/zai_payment/version.rb` - Version bump to 2.1.0
208
- 3. `changelog.md` - Added 2.1.0 release notes
209
- 4. `readme.md` - Added Token Auth documentation
210
- 5. `docs/readme.md` - Updated documentation index
211
-
212
- ## Security Considerations
213
-
214
- - **PCI Compliance:** Tokens enable PCI-compliant card data collection without sensitive data touching your server
215
- - **Token Expiration:** Tokens have limited lifespan (typically 1 hour)
216
- - **Single-use:** Tokens should be generated fresh for each session
217
- - **User-specific:** Each token is tied to a specific user ID
218
- - **Type-specific:** Bank tokens only for bank accounts, card tokens only for cards
219
- - **HTTPS Required:** All communication over secure HTTPS
220
-
221
- ## Next Steps
222
-
223
- The Token Auth implementation is complete and ready for use. Developers can now:
224
-
225
- 1. Generate tokens for buyers to collect credit card information
226
- 2. Generate tokens for sellers to collect bank account details
227
- 3. Integrate with PromisePay.js for secure frontend data collection
228
- 4. Process payments without handling sensitive payment data directly
229
-
230
- ## Related APIs
231
-
232
- This implementation complements existing resources:
233
- - **Users API** - Create users before generating tokens
234
- - **Items API** - Use payment accounts to create transactions
235
- - **Webhooks API** - Receive notifications about payment events
236
-
237
- ## Documentation Links
238
-
239
- - **API Reference:** https://developer.hellozai.com/reference/generatetoken
240
- - **Examples:** [examples/token_auths.md](examples/token_auths.md)
241
- - **Guide:** [docs/token_auths.md](docs/token_auths.md)
242
- - **PromisePay.js:** https://developer.hellozai.com/docs/promisepay-js
243
-
244
- ---
245
-
246
- **Implementation Date:** October 24, 2025
247
- **Version:** 2.1.0
248
- **Status:** ✅ Complete and tested
249
-