zai_payment 2.0.2 → 2.2.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/badges/coverage.json +1 -1
- data/changelog.md +75 -0
- data/docs/readme.md +55 -25
- data/docs/token_auths.md +523 -0
- data/docs/user_id_field.md +1 -1
- data/docs/users.md +303 -6
- data/examples/rails_card_payment.md +716 -0
- data/examples/token_auths.md +687 -0
- data/lib/zai_payment/resources/token_auth.rb +75 -0
- data/lib/zai_payment/resources/user.rb +131 -3
- data/lib/zai_payment/response.rb +10 -4
- data/lib/zai_payment/version.rb +1 -1
- data/lib/zai_payment.rb +6 -0
- data/readme.md +55 -0
- data/token_auth_implementation_summary.md +249 -0
- metadata +7 -2
|
@@ -0,0 +1,687 @@
|
|
|
1
|
+
# Token Auth Examples
|
|
2
|
+
|
|
3
|
+
This file contains practical examples of using the TokenAuth resource to generate tokens for bank or card accounts.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
- [Basic Usage](#basic-usage)
|
|
7
|
+
- [Bank Tokens](#bank-tokens)
|
|
8
|
+
- [Card Tokens](#card-tokens)
|
|
9
|
+
- [Integration Examples](#integration-examples)
|
|
10
|
+
- [Error Handling](#error-handling)
|
|
11
|
+
- [Security Best Practices](#security-best-practices)
|
|
12
|
+
|
|
13
|
+
## Basic Usage
|
|
14
|
+
|
|
15
|
+
### Generate a Bank Token (Default)
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# Generate a bank token (default token_type)
|
|
19
|
+
response = ZaiPayment.token_auths.generate(
|
|
20
|
+
user_id: "seller-68611249"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
puts response.data
|
|
24
|
+
# => {
|
|
25
|
+
# "token_auth" => {
|
|
26
|
+
# "token" => "tok_bank_abc123...",
|
|
27
|
+
# "user_id" => "seller-68611249",
|
|
28
|
+
# "token_type" => "bank",
|
|
29
|
+
# "created_at" => "2025-10-24T12:00:00Z",
|
|
30
|
+
# "expires_at" => "2025-10-24T13:00:00Z"
|
|
31
|
+
# }
|
|
32
|
+
# }
|
|
33
|
+
|
|
34
|
+
token = response.data['token_auth']['token']
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Generate with Explicit Token Type
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
# Generate a bank token (explicit)
|
|
41
|
+
response = ZaiPayment.token_auths.generate(
|
|
42
|
+
user_id: "seller-68611249",
|
|
43
|
+
token_type: "bank"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Generate a card token (explicit)
|
|
47
|
+
response = ZaiPayment.token_auths.generate(
|
|
48
|
+
user_id: "buyer-12345",
|
|
49
|
+
token_type: "card"
|
|
50
|
+
)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Bank Tokens
|
|
54
|
+
|
|
55
|
+
Bank tokens are used for securely collecting bank account information from users.
|
|
56
|
+
|
|
57
|
+
### Example: Collect Bank Account Details
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# Step 1: Create a payout user (seller)
|
|
61
|
+
seller = ZaiPayment.users.create(
|
|
62
|
+
user_type: "payout",
|
|
63
|
+
email: "seller@example.com",
|
|
64
|
+
first_name: "Jane",
|
|
65
|
+
last_name: "Smith",
|
|
66
|
+
country: "AUS",
|
|
67
|
+
dob: "01/01/1990",
|
|
68
|
+
address_line1: "456 Market St",
|
|
69
|
+
city: "Sydney",
|
|
70
|
+
state: "NSW",
|
|
71
|
+
zip: "2000"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
seller_id = seller.data['users']['id']
|
|
75
|
+
|
|
76
|
+
# Step 2: Generate a bank token for the seller
|
|
77
|
+
token_response = ZaiPayment.token_auths.generate(
|
|
78
|
+
user_id: seller_id,
|
|
79
|
+
token_type: "bank"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
bank_token = token_response.data['token_auth']['token']
|
|
83
|
+
|
|
84
|
+
# Step 3: Use this token with PromisePay.js on the frontend
|
|
85
|
+
# to securely collect bank account details
|
|
86
|
+
puts "Bank Token: #{bank_token}"
|
|
87
|
+
puts "Send this token to your frontend to use with PromisePay.js"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Example: Multiple Bank Accounts
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
# Generate tokens for multiple sellers to collect their bank account details
|
|
94
|
+
sellers = ["seller-001", "seller-002", "seller-003"]
|
|
95
|
+
|
|
96
|
+
bank_tokens = sellers.map do |seller_id|
|
|
97
|
+
response = ZaiPayment.token_auths.generate(
|
|
98
|
+
user_id: seller_id,
|
|
99
|
+
token_type: "bank"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
{
|
|
103
|
+
seller_id: seller_id,
|
|
104
|
+
token: response.data['token_auth']['token'],
|
|
105
|
+
expires_at: response.data['token_auth']['expires_at']
|
|
106
|
+
}
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
puts bank_tokens
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Card Tokens
|
|
113
|
+
|
|
114
|
+
Card tokens are used for securely collecting credit card information from buyers.
|
|
115
|
+
|
|
116
|
+
### Example: Collect Card Details for Payment
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# Step 1: Create a payin user (buyer)
|
|
120
|
+
buyer = ZaiPayment.users.create(
|
|
121
|
+
user_type: "payin",
|
|
122
|
+
email: "buyer@example.com",
|
|
123
|
+
first_name: "John",
|
|
124
|
+
last_name: "Doe",
|
|
125
|
+
country: "USA"
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
buyer_id = buyer.data['users']['id']
|
|
129
|
+
|
|
130
|
+
# Step 2: Generate a card token for the buyer
|
|
131
|
+
token_response = ZaiPayment.token_auths.generate(
|
|
132
|
+
user_id: buyer_id,
|
|
133
|
+
token_type: "card"
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
card_token = token_response.data['token_auth']['token']
|
|
137
|
+
|
|
138
|
+
# Step 3: Use this token with PromisePay.js on the frontend
|
|
139
|
+
# to securely collect credit card details
|
|
140
|
+
puts "Card Token: #{card_token}"
|
|
141
|
+
puts "Send this token to your frontend to use with PromisePay.js"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Example: Card Token for Checkout Flow
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# In a checkout flow, generate a card token for the buyer
|
|
148
|
+
def generate_card_token_for_checkout(buyer_id)
|
|
149
|
+
response = ZaiPayment.token_auths.generate(
|
|
150
|
+
user_id: buyer_id,
|
|
151
|
+
token_type: "card"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
token_data = response.data['token_auth']
|
|
155
|
+
|
|
156
|
+
{
|
|
157
|
+
token: token_data['token'],
|
|
158
|
+
expires_at: token_data['expires_at'],
|
|
159
|
+
# Send this to your frontend
|
|
160
|
+
frontend_data: {
|
|
161
|
+
token: token_data['token'],
|
|
162
|
+
user_id: buyer_id
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Usage
|
|
168
|
+
checkout_data = generate_card_token_for_checkout("buyer-12345")
|
|
169
|
+
puts checkout_data[:frontend_data].to_json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Integration Examples
|
|
173
|
+
|
|
174
|
+
### Example: Rails Controller Integration
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
# app/controllers/payment_tokens_controller.rb
|
|
178
|
+
class PaymentTokensController < ApplicationController
|
|
179
|
+
before_action :authenticate_user!
|
|
180
|
+
|
|
181
|
+
# POST /payment_tokens/bank
|
|
182
|
+
def create_bank_token
|
|
183
|
+
user_id = current_user.zai_seller_id
|
|
184
|
+
|
|
185
|
+
response = ZaiPayment.token_auths.generate(
|
|
186
|
+
user_id: user_id,
|
|
187
|
+
token_type: "bank"
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
render json: {
|
|
191
|
+
token: response.data['token_auth']['token'],
|
|
192
|
+
expires_at: response.data['token_auth']['expires_at']
|
|
193
|
+
}
|
|
194
|
+
rescue ZaiPayment::Errors::ValidationError => e
|
|
195
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
196
|
+
rescue ZaiPayment::Errors::ApiError => e
|
|
197
|
+
render json: { error: "Failed to generate token" }, status: :bad_gateway
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# POST /payment_tokens/card
|
|
201
|
+
def create_card_token
|
|
202
|
+
user_id = current_user.zai_buyer_id
|
|
203
|
+
|
|
204
|
+
response = ZaiPayment.token_auths.generate(
|
|
205
|
+
user_id: user_id,
|
|
206
|
+
token_type: "card"
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
render json: {
|
|
210
|
+
token: response.data['token_auth']['token'],
|
|
211
|
+
expires_at: response.data['token_auth']['expires_at']
|
|
212
|
+
}
|
|
213
|
+
rescue ZaiPayment::Errors::ValidationError => e
|
|
214
|
+
render json: { error: e.message }, status: :unprocessable_entity
|
|
215
|
+
rescue ZaiPayment::Errors::ApiError => e
|
|
216
|
+
render json: { error: "Failed to generate token" }, status: :bad_gateway
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Example: Service Object Pattern
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
# app/services/token_generator_service.rb
|
|
225
|
+
class TokenGeneratorService
|
|
226
|
+
def initialize(user_id)
|
|
227
|
+
@user_id = user_id
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def generate_bank_token
|
|
231
|
+
generate_token(type: 'bank')
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def generate_card_token
|
|
235
|
+
generate_token(type: 'card')
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
private
|
|
239
|
+
|
|
240
|
+
def generate_token(type:)
|
|
241
|
+
response = ZaiPayment.token_auths.generate(
|
|
242
|
+
user_id: @user_id,
|
|
243
|
+
token_type: type
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
token_data = response.data['token_auth']
|
|
247
|
+
|
|
248
|
+
{
|
|
249
|
+
success: true,
|
|
250
|
+
token: token_data['token'],
|
|
251
|
+
expires_at: token_data['expires_at'],
|
|
252
|
+
type: type
|
|
253
|
+
}
|
|
254
|
+
rescue ZaiPayment::Errors::ValidationError => e
|
|
255
|
+
{
|
|
256
|
+
success: false,
|
|
257
|
+
error: e.message,
|
|
258
|
+
type: :validation_error
|
|
259
|
+
}
|
|
260
|
+
rescue ZaiPayment::Errors::ApiError => e
|
|
261
|
+
{
|
|
262
|
+
success: false,
|
|
263
|
+
error: "API error: #{e.message}",
|
|
264
|
+
type: :api_error
|
|
265
|
+
}
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Usage
|
|
270
|
+
service = TokenGeneratorService.new("seller-123")
|
|
271
|
+
result = service.generate_bank_token
|
|
272
|
+
|
|
273
|
+
if result[:success]
|
|
274
|
+
puts "Token: #{result[:token]}"
|
|
275
|
+
else
|
|
276
|
+
puts "Error: #{result[:error]}"
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Example: Frontend Integration
|
|
281
|
+
|
|
282
|
+
```ruby
|
|
283
|
+
# Backend: Generate and send token to frontend
|
|
284
|
+
def generate_frontend_token(user_id, token_type)
|
|
285
|
+
response = ZaiPayment.token_auths.generate(
|
|
286
|
+
user_id: user_id,
|
|
287
|
+
token_type: token_type
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
token_data = response.data['token_auth']
|
|
291
|
+
|
|
292
|
+
# Return data formatted for frontend
|
|
293
|
+
{
|
|
294
|
+
token: token_data['token'],
|
|
295
|
+
expiresAt: token_data['expires_at'],
|
|
296
|
+
userId: user_id,
|
|
297
|
+
tokenType: token_type
|
|
298
|
+
}
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Frontend JavaScript (example)
|
|
302
|
+
# <script src="https://cdn.assemblypay.com/promisepay.js"></script>
|
|
303
|
+
# <script>
|
|
304
|
+
# // Receive token from backend
|
|
305
|
+
# fetch('/api/payment_tokens/card', { method: 'POST' })
|
|
306
|
+
# .then(res => res.json())
|
|
307
|
+
# .then(data => {
|
|
308
|
+
# // Use the token with PromisePay.js
|
|
309
|
+
# PromisePay.setToken(data.token);
|
|
310
|
+
#
|
|
311
|
+
# // Collect card details
|
|
312
|
+
# PromisePay.createCardAccount({
|
|
313
|
+
# card_number: '4111111111111111',
|
|
314
|
+
# expiry_month: '12',
|
|
315
|
+
# expiry_year: '2025',
|
|
316
|
+
# cvv: '123'
|
|
317
|
+
# }, function(response) {
|
|
318
|
+
# console.log('Card account created:', response);
|
|
319
|
+
# });
|
|
320
|
+
# });
|
|
321
|
+
# </script>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Error Handling
|
|
325
|
+
|
|
326
|
+
### Example: Comprehensive Error Handling
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
def generate_token_with_error_handling(user_id, token_type)
|
|
330
|
+
begin
|
|
331
|
+
response = ZaiPayment.token_auths.generate(
|
|
332
|
+
user_id: user_id,
|
|
333
|
+
token_type: token_type
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
{
|
|
337
|
+
success: true,
|
|
338
|
+
token: response.data['token_auth']['token'],
|
|
339
|
+
expires_at: response.data['token_auth']['expires_at']
|
|
340
|
+
}
|
|
341
|
+
rescue ZaiPayment::Errors::ValidationError => e
|
|
342
|
+
# Invalid parameters (user_id or token_type)
|
|
343
|
+
{
|
|
344
|
+
success: false,
|
|
345
|
+
error: e.message,
|
|
346
|
+
error_type: :validation,
|
|
347
|
+
retryable: false
|
|
348
|
+
}
|
|
349
|
+
rescue ZaiPayment::Errors::UnauthorizedError => e
|
|
350
|
+
# Authentication failed
|
|
351
|
+
{
|
|
352
|
+
success: false,
|
|
353
|
+
error: "Authentication failed",
|
|
354
|
+
error_type: :auth,
|
|
355
|
+
retryable: false
|
|
356
|
+
}
|
|
357
|
+
rescue ZaiPayment::Errors::NotFoundError => e
|
|
358
|
+
# User not found
|
|
359
|
+
{
|
|
360
|
+
success: false,
|
|
361
|
+
error: "User not found: #{user_id}",
|
|
362
|
+
error_type: :not_found,
|
|
363
|
+
retryable: false
|
|
364
|
+
}
|
|
365
|
+
rescue ZaiPayment::Errors::RateLimitError => e
|
|
366
|
+
# Rate limit exceeded
|
|
367
|
+
{
|
|
368
|
+
success: false,
|
|
369
|
+
error: "Rate limit exceeded",
|
|
370
|
+
error_type: :rate_limit,
|
|
371
|
+
retryable: true,
|
|
372
|
+
retry_after: 60 # seconds
|
|
373
|
+
}
|
|
374
|
+
rescue ZaiPayment::Errors::ServerError => e
|
|
375
|
+
# Server error (5xx)
|
|
376
|
+
{
|
|
377
|
+
success: false,
|
|
378
|
+
error: "Server error",
|
|
379
|
+
error_type: :server,
|
|
380
|
+
retryable: true
|
|
381
|
+
}
|
|
382
|
+
rescue ZaiPayment::Errors::TimeoutError => e
|
|
383
|
+
# Request timeout
|
|
384
|
+
{
|
|
385
|
+
success: false,
|
|
386
|
+
error: "Request timeout",
|
|
387
|
+
error_type: :timeout,
|
|
388
|
+
retryable: true
|
|
389
|
+
}
|
|
390
|
+
rescue ZaiPayment::Errors::ApiError => e
|
|
391
|
+
# Generic API error
|
|
392
|
+
{
|
|
393
|
+
success: false,
|
|
394
|
+
error: e.message,
|
|
395
|
+
error_type: :api,
|
|
396
|
+
retryable: false
|
|
397
|
+
}
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Usage
|
|
402
|
+
result = generate_token_with_error_handling("buyer-123", "card")
|
|
403
|
+
|
|
404
|
+
if result[:success]
|
|
405
|
+
puts "Token: #{result[:token]}"
|
|
406
|
+
else
|
|
407
|
+
puts "Error: #{result[:error]}"
|
|
408
|
+
puts "Retryable: #{result[:retryable]}" if result[:retryable]
|
|
409
|
+
end
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Example: Retry Logic
|
|
413
|
+
|
|
414
|
+
```ruby
|
|
415
|
+
def generate_token_with_retry(user_id, token_type, max_retries: 3)
|
|
416
|
+
retries = 0
|
|
417
|
+
|
|
418
|
+
begin
|
|
419
|
+
response = ZaiPayment.token_auths.generate(
|
|
420
|
+
user_id: user_id,
|
|
421
|
+
token_type: token_type
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
response.data['token_auth']['token']
|
|
425
|
+
rescue ZaiPayment::Errors::RateLimitError,
|
|
426
|
+
ZaiPayment::Errors::ServerError,
|
|
427
|
+
ZaiPayment::Errors::TimeoutError => e
|
|
428
|
+
retries += 1
|
|
429
|
+
|
|
430
|
+
if retries < max_retries
|
|
431
|
+
sleep_time = 2 ** retries # Exponential backoff: 2, 4, 8 seconds
|
|
432
|
+
puts "Retry #{retries}/#{max_retries} after #{sleep_time}s..."
|
|
433
|
+
sleep(sleep_time)
|
|
434
|
+
retry
|
|
435
|
+
else
|
|
436
|
+
puts "Max retries reached"
|
|
437
|
+
raise
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
# Usage
|
|
443
|
+
begin
|
|
444
|
+
token = generate_token_with_retry("buyer-123", "card")
|
|
445
|
+
puts "Token: #{token}"
|
|
446
|
+
rescue => e
|
|
447
|
+
puts "Failed after retries: #{e.message}"
|
|
448
|
+
end
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Security Best Practices
|
|
452
|
+
|
|
453
|
+
### Example: Token Expiry Management
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
class TokenManager
|
|
457
|
+
def initialize(user_id)
|
|
458
|
+
@user_id = user_id
|
|
459
|
+
@cache = {}
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
def get_bank_token
|
|
463
|
+
get_token('bank')
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
def get_card_token
|
|
467
|
+
get_token('card')
|
|
468
|
+
end
|
|
469
|
+
|
|
470
|
+
private
|
|
471
|
+
|
|
472
|
+
def get_token(type)
|
|
473
|
+
cache_key = "#{@user_id}_#{type}"
|
|
474
|
+
|
|
475
|
+
# Check if we have a valid cached token
|
|
476
|
+
if @cache[cache_key] && !token_expired?(@cache[cache_key][:expires_at])
|
|
477
|
+
return @cache[cache_key][:token]
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Generate new token
|
|
481
|
+
response = ZaiPayment.token_auths.generate(
|
|
482
|
+
user_id: @user_id,
|
|
483
|
+
token_type: type
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
token_data = response.data['token_auth']
|
|
487
|
+
|
|
488
|
+
# Cache the token
|
|
489
|
+
@cache[cache_key] = {
|
|
490
|
+
token: token_data['token'],
|
|
491
|
+
expires_at: Time.parse(token_data['expires_at'])
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
token_data['token']
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def token_expired?(expires_at)
|
|
498
|
+
# Consider token expired 5 minutes before actual expiry
|
|
499
|
+
expires_at < Time.now + 300
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
# Usage
|
|
504
|
+
manager = TokenManager.new("buyer-123")
|
|
505
|
+
token = manager.get_card_token # Generates new token
|
|
506
|
+
token2 = manager.get_card_token # Returns cached token if not expired
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Example: Audit Logging
|
|
510
|
+
|
|
511
|
+
```ruby
|
|
512
|
+
class AuditedTokenGenerator
|
|
513
|
+
def self.generate(user_id:, token_type:, request_context: {})
|
|
514
|
+
start_time = Time.now
|
|
515
|
+
|
|
516
|
+
begin
|
|
517
|
+
response = ZaiPayment.token_auths.generate(
|
|
518
|
+
user_id: user_id,
|
|
519
|
+
token_type: token_type
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
# Log successful generation
|
|
523
|
+
log_token_generation(
|
|
524
|
+
user_id: user_id,
|
|
525
|
+
token_type: token_type,
|
|
526
|
+
success: true,
|
|
527
|
+
duration: Time.now - start_time,
|
|
528
|
+
context: request_context
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
response
|
|
532
|
+
rescue => e
|
|
533
|
+
# Log failed generation
|
|
534
|
+
log_token_generation(
|
|
535
|
+
user_id: user_id,
|
|
536
|
+
token_type: token_type,
|
|
537
|
+
success: false,
|
|
538
|
+
error: e.message,
|
|
539
|
+
duration: Time.now - start_time,
|
|
540
|
+
context: request_context
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
raise
|
|
544
|
+
end
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def self.log_token_generation(data)
|
|
548
|
+
Rails.logger.info({
|
|
549
|
+
event: 'token_generation',
|
|
550
|
+
timestamp: Time.now.iso8601,
|
|
551
|
+
**data
|
|
552
|
+
}.to_json)
|
|
553
|
+
|
|
554
|
+
# Optionally store in database for audit trail
|
|
555
|
+
# AuditLog.create!(
|
|
556
|
+
# event_type: 'token_generation',
|
|
557
|
+
# user_id: data[:user_id],
|
|
558
|
+
# success: data[:success],
|
|
559
|
+
# metadata: data
|
|
560
|
+
# )
|
|
561
|
+
end
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Usage
|
|
565
|
+
response = AuditedTokenGenerator.generate(
|
|
566
|
+
user_id: "buyer-123",
|
|
567
|
+
token_type: "card",
|
|
568
|
+
request_context: {
|
|
569
|
+
ip_address: request.ip,
|
|
570
|
+
user_agent: request.user_agent,
|
|
571
|
+
session_id: session.id
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
### Example: Rate Limiting Protection
|
|
577
|
+
|
|
578
|
+
```ruby
|
|
579
|
+
class RateLimitedTokenGenerator
|
|
580
|
+
def initialize(user_id)
|
|
581
|
+
@user_id = user_id
|
|
582
|
+
@redis = Redis.new
|
|
583
|
+
end
|
|
584
|
+
|
|
585
|
+
def generate(token_type:, limit: 10, window: 3600)
|
|
586
|
+
# Check rate limit
|
|
587
|
+
rate_limit_key = "token_gen:#{@user_id}:#{Time.now.to_i / window}"
|
|
588
|
+
current_count = @redis.incr(rate_limit_key)
|
|
589
|
+
@redis.expire(rate_limit_key, window)
|
|
590
|
+
|
|
591
|
+
if current_count > limit
|
|
592
|
+
raise "Rate limit exceeded: #{limit} tokens per #{window} seconds"
|
|
593
|
+
end
|
|
594
|
+
|
|
595
|
+
# Generate token
|
|
596
|
+
response = ZaiPayment.token_auths.generate(
|
|
597
|
+
user_id: @user_id,
|
|
598
|
+
token_type: token_type
|
|
599
|
+
)
|
|
600
|
+
|
|
601
|
+
response.data['token_auth']['token']
|
|
602
|
+
end
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
# Usage
|
|
606
|
+
generator = RateLimitedTokenGenerator.new("buyer-123")
|
|
607
|
+
token = generator.generate(token_type: "card", limit: 5, window: 3600)
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
## Complete Example: Payment Flow
|
|
611
|
+
|
|
612
|
+
```ruby
|
|
613
|
+
# Complete example showing the full payment flow with token generation
|
|
614
|
+
|
|
615
|
+
class PaymentFlowService
|
|
616
|
+
def initialize(buyer_id, seller_id)
|
|
617
|
+
@buyer_id = buyer_id
|
|
618
|
+
@seller_id = seller_id
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
def process_payment(amount:, payment_method: :card)
|
|
622
|
+
# Step 1: Generate token for buyer to collect payment details
|
|
623
|
+
token_response = generate_payment_token(payment_method)
|
|
624
|
+
|
|
625
|
+
# Step 2: Return token to frontend for secure data collection
|
|
626
|
+
# (In real app, frontend would use PromisePay.js to collect details)
|
|
627
|
+
|
|
628
|
+
# Step 3: Create item for payment
|
|
629
|
+
item_response = create_payment_item(amount)
|
|
630
|
+
|
|
631
|
+
{
|
|
632
|
+
success: true,
|
|
633
|
+
token: token_response[:token],
|
|
634
|
+
item_id: item_response[:item_id],
|
|
635
|
+
message: "Ready to process payment"
|
|
636
|
+
}
|
|
637
|
+
rescue => e
|
|
638
|
+
{
|
|
639
|
+
success: false,
|
|
640
|
+
error: e.message
|
|
641
|
+
}
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
private
|
|
645
|
+
|
|
646
|
+
def generate_payment_token(payment_method)
|
|
647
|
+
token_type = payment_method == :card ? 'card' : 'bank'
|
|
648
|
+
|
|
649
|
+
response = ZaiPayment.token_auths.generate(
|
|
650
|
+
user_id: @buyer_id,
|
|
651
|
+
token_type: token_type
|
|
652
|
+
)
|
|
653
|
+
|
|
654
|
+
{
|
|
655
|
+
token: response.data['token_auth']['token'],
|
|
656
|
+
expires_at: response.data['token_auth']['expires_at']
|
|
657
|
+
}
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def create_payment_item(amount)
|
|
661
|
+
response = ZaiPayment.items.create(
|
|
662
|
+
name: "Payment Transaction",
|
|
663
|
+
amount: amount,
|
|
664
|
+
payment_type: 2, # Credit card
|
|
665
|
+
buyer_id: @buyer_id,
|
|
666
|
+
seller_id: @seller_id
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
{
|
|
670
|
+
item_id: response.data['items']['id']
|
|
671
|
+
}
|
|
672
|
+
end
|
|
673
|
+
end
|
|
674
|
+
|
|
675
|
+
# Usage
|
|
676
|
+
service = PaymentFlowService.new("buyer-123", "seller-456")
|
|
677
|
+
result = service.process_payment(amount: 10000) # $100.00
|
|
678
|
+
|
|
679
|
+
if result[:success]
|
|
680
|
+
puts "Payment token: #{result[:token]}"
|
|
681
|
+
puts "Item ID: #{result[:item_id]}"
|
|
682
|
+
puts "Send token to frontend for payment collection"
|
|
683
|
+
else
|
|
684
|
+
puts "Error: #{result[:error]}"
|
|
685
|
+
end
|
|
686
|
+
```
|
|
687
|
+
|