zai_payment 2.7.0 → 2.8.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,916 @@
1
+ # Virtual Account Management
2
+
3
+ The VirtualAccount resource provides methods for creating and managing Zai virtual accounts for Australian payments.
4
+
5
+ ## Overview
6
+
7
+ Virtual Accounts are bank account details that can be created for a wallet account, allowing users to receive funds via standard bank transfers. Each virtual account has unique BSB (routing number) and account number details that can be shared with customers or partners to receive payments.
8
+
9
+ Virtual accounts are particularly useful for:
10
+ - Receiving payments from customers via direct bank transfer
11
+ - Creating unique account details for different payment purposes
12
+ - Enabling Confirmation of Payee (CoP) lookups with account name and AKA names
13
+ - Managing trust accounts in real estate or property management
14
+
15
+ ## Key Features
16
+
17
+ - **Unique Banking Details**: Each virtual account gets unique BSB and account number
18
+ - **AKA Names**: Support for alternative names (up to 3) for CoP lookups
19
+ - **Automatic Linking**: Virtual accounts are automatically linked to wallet accounts
20
+ - **Status Tracking**: Monitor account status (pending_activation, active, etc.)
21
+ - **Multiple Currencies**: Support for different currencies (primarily AUD)
22
+
23
+ ## References
24
+
25
+ - [Virtual Accounts API](https://developer.hellozai.com/reference)
26
+ - [Zai API Documentation](https://developer.hellozai.com/docs)
27
+
28
+ ## Usage
29
+
30
+ ### Initialize the VirtualAccount Resource
31
+
32
+ ```ruby
33
+ # Using a new instance
34
+ virtual_accounts = ZaiPayment::Resources::VirtualAccount.new
35
+
36
+ # Or use with custom client
37
+ client = ZaiPayment::Client.new(base_endpoint: :va_base)
38
+ virtual_accounts = ZaiPayment::Resources::VirtualAccount.new(client: client)
39
+
40
+ # Or use the convenience method
41
+ ZaiPayment.virtual_accounts
42
+ ```
43
+
44
+ ## Methods
45
+
46
+ ### List Virtual Accounts
47
+
48
+ List all Virtual Accounts for a given Wallet Account. This retrieves an array of all virtual accounts associated with the wallet account.
49
+
50
+ #### Parameters
51
+
52
+ - `wallet_account_id` (required) - The wallet account ID
53
+
54
+ #### Example
55
+
56
+ ```ruby
57
+ # List all virtual accounts for a wallet
58
+ response = virtual_accounts.list('ae07556e-22ef-11eb-adc1-0242ac120002')
59
+
60
+ # Access the list of virtual accounts
61
+ if response.success?
62
+ accounts = response.data # Array of virtual accounts
63
+ total = response.meta['total']
64
+
65
+ puts "Found #{accounts.length} virtual accounts"
66
+
67
+ accounts.each do |account|
68
+ puts "ID: #{account['id']}"
69
+ puts "Name: #{account['account_name']}"
70
+ puts "BSB: #{account['routing_number']}"
71
+ puts "Account: #{account['account_number']}"
72
+ puts "Status: #{account['status']}"
73
+ end
74
+ end
75
+ ```
76
+
77
+ #### Response
78
+
79
+ ```ruby
80
+ {
81
+ "virtual_accounts" => [
82
+ {
83
+ "id" => "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
84
+ "routing_number" => "123456",
85
+ "account_number" => "100000017",
86
+ "wallet_account_id" => "ae07556e-22ef-11eb-adc1-0242ac120002",
87
+ "user_external_id" => "ca12346e-22ef-11eb-adc1-0242ac120002",
88
+ "currency" => "AUD",
89
+ "status" => "active",
90
+ "created_at" => "2020-04-27T20:28:22.378Z",
91
+ "updated_at" => "2020-04-27T20:28:22.378Z",
92
+ "account_type" => "NIND",
93
+ "full_legal_account_name" => "Prop Tech Marketplace",
94
+ "account_name" => "Real Estate Agency X",
95
+ "aka_names" => ["Realestate agency X"],
96
+ "merchant_id" => "46deb476c1a641eb8eb726a695bbe5bc"
97
+ },
98
+ {
99
+ "id" => "aaaaaaaa-cccc-dddd-eeee-ffffffffffff",
100
+ "routing_number" => "123456",
101
+ "account_number" => "100000025",
102
+ "currency" => "AUD",
103
+ "wallet_account_id" => "ae07556e-22ef-11eb-adc1-0242ac120002",
104
+ "user_external_id" => "ca12346e-22ef-11eb-adc1-0242ac120002",
105
+ "status" => "pending_activation",
106
+ "created_at" => "2020-04-27T20:28:22.378Z",
107
+ "updated_at" => "2020-04-27T20:28:22.378Z",
108
+ "account_type" => "NIND",
109
+ "full_legal_account_name" => "Prop Tech Marketplace",
110
+ "account_name" => "Real Estate Agency X",
111
+ "aka_names" => ["Realestate agency X"],
112
+ "merchant_id" => "46deb476c1a641eb8eb726a695bbe5bc"
113
+ }
114
+ ],
115
+ "meta" => {
116
+ "total" => 2
117
+ }
118
+ }
119
+ ```
120
+
121
+ **Response Fields:**
122
+
123
+ The response contains an array of virtual account objects. Each object has the same fields as described in the Create Virtual Account section.
124
+
125
+ **Additional Response Data:**
126
+
127
+ - `meta` - Contains pagination and metadata information
128
+ - `total` - Total number of virtual accounts
129
+
130
+ **Use Cases:**
131
+
132
+ - Retrieve all virtual accounts for auditing purposes
133
+ - Display available payment accounts to customers
134
+ - Filter accounts by status (active, pending_activation, etc.)
135
+ - Check if virtual accounts exist before creating new ones
136
+ - Monitor account statuses across multiple properties
137
+ - Generate reports on virtual account usage
138
+
139
+ ### Show Virtual Account
140
+
141
+ Show details of a specific Virtual Account using the given virtual account ID.
142
+
143
+ #### Parameters
144
+
145
+ - `virtual_account_id` (required) - The virtual account ID
146
+
147
+ #### Example
148
+
149
+ ```ruby
150
+ # Get specific virtual account details
151
+ response = virtual_accounts.show('46deb476-c1a6-41eb-8eb7-26a695bbe5bc')
152
+
153
+ # Access virtual account details
154
+ if response.success?
155
+ account = response.data
156
+
157
+ puts "Virtual Account: #{account['account_name']}"
158
+ puts "Status: #{account['status']}"
159
+ puts "BSB: #{account['routing_number']}"
160
+ puts "Account Number: #{account['account_number']}"
161
+ puts "Currency: #{account['currency']}"
162
+
163
+ # Access AKA names
164
+ account['aka_names'].each do |aka_name|
165
+ puts "AKA: #{aka_name}"
166
+ end
167
+ end
168
+ ```
169
+
170
+ #### Response
171
+
172
+ ```ruby
173
+ {
174
+ "virtual_accounts" => {
175
+ "id" => "46deb476-c1a6-41eb-8eb7-26a695bbe5bc",
176
+ "routing_number" => "123456",
177
+ "account_number" => "100000017",
178
+ "currency" => "AUD",
179
+ "user_external_id" => "46deb476-c1a6-41eb-8eb7-26a695bbe5bc",
180
+ "wallet_account_id" => "46deb476-c1a6-41eb-8eb7-26a695bbe5bc",
181
+ "status" => "active",
182
+ "created_at" => "2020-04-27T20:28:22.378Z",
183
+ "updated_at" => "2020-04-27T20:28:22.378Z",
184
+ "account_type" => "NIND",
185
+ "full_legal_account_name" => "Prop Tech Marketplace",
186
+ "account_name" => "Real Estate Agency X",
187
+ "aka_names" => [
188
+ "Realestate Agency X",
189
+ "Realestate Agency X of PropTech Marketplace"
190
+ ],
191
+ "merchant_id" => "46deb476c1a641eb8eb726a695bbe5bc"
192
+ }
193
+ }
194
+ ```
195
+
196
+ **Response Fields:**
197
+
198
+ The response contains a single virtual account object with all the fields described in the Create Virtual Account section.
199
+
200
+ **Use Cases:**
201
+
202
+ - Verify virtual account details before sharing with customers
203
+ - Check account status before processing payments
204
+ - Generate payment instructions for customers
205
+ - Audit specific virtual account configurations
206
+ - Validate account information
207
+ - Monitor individual account updates
208
+
209
+ ### Update AKA Names
210
+
211
+ Update (replace) the list of AKA Names for a Virtual Account. This operation completely replaces the existing AKA names with the new list provided.
212
+
213
+ #### Parameters
214
+
215
+ - `virtual_account_id` (required) - The virtual account ID
216
+ - `aka_names` (required) - Array of AKA names (0 to 3 items)
217
+
218
+ #### Example
219
+
220
+ ```ruby
221
+ # Update AKA names
222
+ response = virtual_accounts.update_aka_names(
223
+ '46deb476-c1a6-41eb-8eb7-26a695bbe5bc',
224
+ ['Updated Name 1', 'Updated Name 2']
225
+ )
226
+
227
+ # Access updated virtual account
228
+ if response.success?
229
+ account = response.data
230
+
231
+ puts "Updated AKA names for: #{account['account_name']}"
232
+ account['aka_names'].each do |aka_name|
233
+ puts " - #{aka_name}"
234
+ end
235
+ end
236
+
237
+ # Clear all AKA names
238
+ response = virtual_accounts.update_aka_names(
239
+ '46deb476-c1a6-41eb-8eb7-26a695bbe5bc',
240
+ []
241
+ )
242
+ ```
243
+
244
+ #### Response
245
+
246
+ The response contains the complete updated virtual account object with the same structure as the Show Virtual Account response.
247
+
248
+ **Use Cases:**
249
+
250
+ - Update AKA names after business name changes
251
+ - Add or remove alternative names for better CoP matching
252
+ - Clear AKA names when no longer needed
253
+ - Standardize naming across virtual accounts
254
+ - Update names based on customer feedback
255
+ - Maintain up-to-date payment reference information
256
+
257
+ ### Update Account Name
258
+
259
+ Update (change) the name of a Virtual Account. This is used in CoP lookups.
260
+
261
+ #### Parameters
262
+
263
+ - `virtual_account_id` (required) - The virtual account ID
264
+ - `account_name` (required) - The new account name (max 140 characters)
265
+
266
+ #### Example
267
+
268
+ ```ruby
269
+ # Update account name
270
+ response = virtual_accounts.update_account_name(
271
+ '46deb476-c1a6-41eb-8eb7-26a695bbe5bc',
272
+ 'New Real Estate Agency Name'
273
+ )
274
+
275
+ # Access updated virtual account
276
+ if response.success?
277
+ account = response.data
278
+
279
+ puts "Account name updated successfully"
280
+ puts "New name: #{account['account_name']}"
281
+ puts "Virtual Account ID: #{account['id']}"
282
+ end
283
+ ```
284
+
285
+ #### Response
286
+
287
+ The response contains the complete updated virtual account object with the same structure as the Show Virtual Account response.
288
+
289
+ **Use Cases:**
290
+
291
+ - Update account name after business rebranding
292
+ - Change name to match legal business name changes
293
+ - Correct misspellings or formatting issues
294
+ - Update names for better CoP matching
295
+ - Align virtual account names with organizational changes
296
+ - Update names for regulatory compliance
297
+
298
+ ### Update Status
299
+
300
+ Update the status of a Virtual Account. Currently, this endpoint only supports closing virtual accounts by setting the status to 'closed'. This is an asynchronous operation that returns a 202 Accepted response.
301
+
302
+ **Important:** Once a virtual account is closed, it cannot be reopened and will no longer be able to receive payments.
303
+
304
+ #### Parameters
305
+
306
+ - `virtual_account_id` (required) - The virtual account ID
307
+ - `status` (required) - The new status (must be 'closed')
308
+
309
+ #### Example
310
+
311
+ ```ruby
312
+ # Close a virtual account
313
+ response = virtual_accounts.update_status(
314
+ '46deb476-c1a6-41eb-8eb7-26a695bbe5bc',
315
+ 'closed'
316
+ )
317
+
318
+ # Check the response
319
+ if response.success?
320
+ puts "Virtual account closure initiated"
321
+ puts "ID: #{response.data['id']}"
322
+ puts "Message: #{response.data['message']}"
323
+ puts "Link: #{response.data['links']['self']}"
324
+
325
+ # Note: The operation is asynchronous
326
+ # Use the show method to check the current status
327
+ sleep(2) # Wait a moment
328
+
329
+ show_response = virtual_accounts.show(response.data['id'])
330
+ puts "Current status: #{show_response.data['status']}"
331
+ end
332
+ ```
333
+
334
+ #### Response
335
+
336
+ ```ruby
337
+ {
338
+ "id" => "46deb476-c1a6-41eb-8eb7-26a695bbe5bc",
339
+ "message" => "Virtual Account update has been accepted for processing",
340
+ "links" => {
341
+ "self" => "/virtual_accounts/46deb476-c1a6-41eb-8eb7-26a695bbe5bc"
342
+ }
343
+ }
344
+ ```
345
+
346
+ **Response Fields:**
347
+
348
+ - `id` - The virtual account ID
349
+ - `message` - Confirmation message about the status update
350
+ - `links` - Object containing related resource links
351
+ - `self` - URL to the virtual account resource
352
+
353
+ **Use Cases:**
354
+
355
+ - Close unused virtual accounts to maintain clean account lists
356
+ - Deactivate accounts when a customer relationship ends
357
+ - Close accounts as part of account cleanup or migration
358
+ - Permanently disable accounts that should no longer receive payments
359
+ - Close test or temporary accounts after use
360
+ - Implement account lifecycle management
361
+ - Meet regulatory requirements for account closure
362
+
363
+ **Important Notes:**
364
+
365
+ - This operation returns 202 Accepted because it's processed asynchronously
366
+ - The actual status change may take a few moments to complete
367
+ - Use the `show` method to verify the status has been updated
368
+ - Only 'closed' is a valid status value; other values will raise a ValidationError
369
+ - Closed accounts cannot be reopened
370
+ - Ensure no pending transactions before closing an account
371
+
372
+ ### Create Virtual Account
373
+
374
+ Create a Virtual Account for a given Wallet Account. This generates unique bank account details that can be used to receive funds.
375
+
376
+ #### Parameters
377
+
378
+ - `wallet_account_id` (required) - The wallet account ID
379
+ - `account_name` (required) - A name for the virtual account (max 140 characters)
380
+ - `aka_names` (optional) - Array of alternative names for CoP lookups (0 to 3 items)
381
+
382
+ #### Example
383
+
384
+ ```ruby
385
+ # Basic creation with account name only
386
+ response = virtual_accounts.create(
387
+ 'ae07556e-22ef-11eb-adc1-0242ac120002',
388
+ account_name: 'Real Estate Agency X'
389
+ )
390
+
391
+ # With AKA names for Confirmation of Payee
392
+ response = virtual_accounts.create(
393
+ 'ae07556e-22ef-11eb-adc1-0242ac120002',
394
+ account_name: 'Real Estate Agency X',
395
+ aka_names: ['Realestate agency X', 'RE Agency X', 'Agency X']
396
+ )
397
+
398
+ # Access virtual account details
399
+ if response.success?
400
+ virtual_account = response.data
401
+ puts "Virtual Account ID: #{virtual_account['id']}"
402
+ puts "BSB: #{virtual_account['routing_number']}"
403
+ puts "Account Number: #{virtual_account['account_number']}"
404
+ puts "Account Name: #{virtual_account['account_name']}"
405
+ puts "Status: #{virtual_account['status']}"
406
+ end
407
+ ```
408
+
409
+ #### Response
410
+
411
+ ```ruby
412
+ {
413
+ "virtual_accounts" => {
414
+ "id" => "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
415
+ "routing_number" => "123456",
416
+ "account_number" => "100000017",
417
+ "currency" => "AUD",
418
+ "wallet_account_id" => "ae07556e-22ef-11eb-adc1-0242ac120002",
419
+ "user_external_id" => "ca12346e-22ef-11eb-adc1-0242ac120002",
420
+ "status" => "pending_activation",
421
+ "created_at" => "2020-04-27T20:28:22.378Z",
422
+ "updated_at" => "2020-04-27T20:28:22.378Z",
423
+ "account_type" => "NIND",
424
+ "full_legal_account_name" => "Prop Tech Marketplace",
425
+ "account_name" => "Real Estate Agency X",
426
+ "aka_names" => ["Realestate agency X"],
427
+ "merchant_id" => "46deb476c1a641eb8eb726a695bbe5bc"
428
+ }
429
+ }
430
+ ```
431
+
432
+ **Response Fields:**
433
+
434
+ | Field | Type | Description |
435
+ |-------|------|-------------|
436
+ | `id` | String | Unique identifier for the virtual account |
437
+ | `routing_number` | String | BSB/routing number (6 digits) |
438
+ | `account_number` | String | Bank account number |
439
+ | `currency` | String | Account currency (e.g., "AUD") |
440
+ | `wallet_account_id` | String | Associated wallet account ID |
441
+ | `user_external_id` | String | Associated user's external ID |
442
+ | `status` | String | Account status (pending_activation, active, etc.) |
443
+ | `created_at` | String | ISO 8601 timestamp of creation |
444
+ | `updated_at` | String | ISO 8601 timestamp of last update |
445
+ | `account_type` | String | Type of account (e.g., "NIND") |
446
+ | `full_legal_account_name` | String | Full legal name of the account |
447
+ | `account_name` | String | Display name of the account |
448
+ | `aka_names` | Array | Alternative names for CoP lookups |
449
+ | `merchant_id` | String | Merchant identifier |
450
+
451
+ **Use Cases:**
452
+
453
+ - Create unique payment collection accounts for different properties or services
454
+ - Enable customers to pay via direct bank transfer
455
+ - Set up trust accounts for real estate transactions
456
+ - Configure multiple name variations for better Confirmation of Payee matching
457
+ - Generate dedicated account details for recurring payment arrangements
458
+
459
+ ## Validation Rules
460
+
461
+ ### account_name
462
+
463
+ - **Required**: Yes
464
+ - **Type**: String
465
+ - **Max Length**: 140 characters
466
+ - **Description**: The display name for the virtual account. This is used in CoP lookups and shown to customers when confirming payments.
467
+
468
+ ### aka_names
469
+
470
+ - **Required**: No
471
+ - **Type**: Array of Strings
472
+ - **Min Items**: 0
473
+ - **Max Items**: 3
474
+ - **Description**: Alternative names for the virtual account. These are used in Confirmation of Payee (CoP) lookups to improve matching when customers initiate transfers.
475
+
476
+ ### wallet_account_id
477
+
478
+ - **Required**: Yes
479
+ - **Type**: String (UUID)
480
+ - **Description**: The ID of the wallet account that this virtual account will be linked to. The wallet account must exist before creating a virtual account.
481
+
482
+ ## Error Handling
483
+
484
+ The virtual account methods can raise the following errors:
485
+
486
+ ### ZaiPayment::Errors::ValidationError
487
+
488
+ Raised when input parameters fail validation:
489
+
490
+ ```ruby
491
+ begin
492
+ response = virtual_accounts.create(
493
+ '', # Empty wallet_account_id
494
+ account_name: 'Test Account'
495
+ )
496
+ rescue ZaiPayment::Errors::ValidationError => e
497
+ puts "Validation failed: #{e.message}"
498
+ # Output: "wallet_account_id is required and cannot be blank"
499
+ end
500
+ ```
501
+
502
+ **Common validation errors:**
503
+ - `wallet_account_id is required and cannot be blank`
504
+ - `account_name cannot be blank`
505
+ - `account_name must be 140 characters or less`
506
+ - `aka_names must be an array`
507
+ - `aka_names must contain between 0 and 3 items`
508
+
509
+ ### ZaiPayment::Errors::NotFoundError
510
+
511
+ Raised when the wallet account doesn't exist:
512
+
513
+ ```ruby
514
+ begin
515
+ response = virtual_accounts.create(
516
+ 'invalid-wallet-id',
517
+ account_name: 'Test Account'
518
+ )
519
+ rescue ZaiPayment::Errors::NotFoundError => e
520
+ puts "Not found: #{e.message}"
521
+ end
522
+ ```
523
+
524
+ ### ZaiPayment::Errors::UnauthorizedError
525
+
526
+ Raised when authentication fails:
527
+
528
+ ```ruby
529
+ begin
530
+ response = virtual_accounts.create(
531
+ wallet_account_id,
532
+ account_name: 'Test Account'
533
+ )
534
+ rescue ZaiPayment::Errors::UnauthorizedError => e
535
+ puts "Authentication failed: #{e.message}"
536
+ # Check your API credentials
537
+ end
538
+ ```
539
+
540
+ ### ZaiPayment::Errors::BadRequestError
541
+
542
+ Raised when the request is malformed or contains invalid data:
543
+
544
+ ```ruby
545
+ begin
546
+ response = virtual_accounts.create(
547
+ wallet_account_id,
548
+ account_name: 'Test Account'
549
+ )
550
+ rescue ZaiPayment::Errors::BadRequestError => e
551
+ puts "Bad request: #{e.message}"
552
+ end
553
+ ```
554
+
555
+ ## Complete Example
556
+
557
+ Here's a complete workflow showing how to list and create virtual accounts with proper error handling:
558
+
559
+ ```ruby
560
+ require 'zai_payment'
561
+
562
+ # Configure ZaiPayment
563
+ ZaiPayment.configure do |config|
564
+ config.environment = :prelive
565
+ config.client_id = ENV['ZAI_CLIENT_ID']
566
+ config.client_secret = ENV['ZAI_CLIENT_SECRET']
567
+ config.scope = ENV['ZAI_SCOPE']
568
+ end
569
+
570
+ # Initialize resource
571
+ virtual_accounts = ZaiPayment::Resources::VirtualAccount.new
572
+ wallet_account_id = 'ae07556e-22ef-11eb-adc1-0242ac120002'
573
+
574
+ begin
575
+ # First, list existing virtual accounts
576
+ puts "Fetching existing virtual accounts..."
577
+ list_response = virtual_accounts.list(wallet_account_id)
578
+
579
+ if list_response.success?
580
+ existing_accounts = list_response.data
581
+ puts "✓ Found #{existing_accounts.length} existing virtual account(s)"
582
+
583
+ # Display existing accounts
584
+ existing_accounts.each do |account|
585
+ puts " - #{account['account_name']} (#{account['status']})"
586
+ puts " BSB: #{account['routing_number']} | Account: #{account['account_number']}"
587
+ end
588
+
589
+ # Check if we need to create a new one
590
+ property_name = 'Property 123 Trust Account'
591
+ existing = existing_accounts.find { |a| a['account_name'] == property_name }
592
+
593
+ if existing
594
+ puts "\n✓ Virtual account already exists for '#{property_name}'"
595
+ puts " ID: #{existing['id']}"
596
+ puts " Status: #{existing['status']}"
597
+ else
598
+ puts "\nCreating new virtual account for '#{property_name}'..."
599
+
600
+ # Create new virtual account
601
+ create_response = virtual_accounts.create(
602
+ wallet_account_id,
603
+ account_name: property_name,
604
+ aka_names: ['Prop 123', 'Property Trust', 'Trust 123']
605
+ )
606
+
607
+ if create_response.success?
608
+ virtual_account = create_response.data
609
+
610
+ puts "✓ Virtual Account Created Successfully!"
611
+ puts "─" * 60
612
+ puts "ID: #{virtual_account['id']}"
613
+ puts "Status: #{virtual_account['status']}"
614
+ puts ""
615
+ puts "Bank Details (share with customers):"
616
+ puts " BSB: #{virtual_account['routing_number']}"
617
+ puts " Account: #{virtual_account['account_number']}"
618
+ puts " Name: #{virtual_account['account_name']}"
619
+ puts ""
620
+ puts "Alternative Names for CoP:"
621
+ virtual_account['aka_names'].each do |aka_name|
622
+ puts " - #{aka_name}"
623
+ end
624
+ puts "─" * 60
625
+
626
+ # Store the details in your database for future reference
627
+ # YourDatabase.store_virtual_account(virtual_account)
628
+ else
629
+ puts "Failed to create virtual account"
630
+ end
631
+ end
632
+ else
633
+ puts "Failed to list virtual accounts"
634
+ end
635
+
636
+ rescue ZaiPayment::Errors::ValidationError => e
637
+ puts "Validation Error: #{e.message}"
638
+ puts "Please check your input parameters"
639
+
640
+ rescue ZaiPayment::Errors::NotFoundError => e
641
+ puts "Wallet Account Not Found: #{e.message}"
642
+ puts "Please verify the wallet_account_id exists"
643
+
644
+ rescue ZaiPayment::Errors::UnauthorizedError => e
645
+ puts "Authentication Failed: #{e.message}"
646
+ puts "Please check your API credentials"
647
+
648
+ rescue ZaiPayment::Errors::ApiError => e
649
+ puts "API Error: #{e.message}"
650
+ end
651
+ ```
652
+
653
+ ## Configuration
654
+
655
+ Virtual accounts use the `va_base` endpoint, which is automatically configured based on your environment:
656
+
657
+ ### Prelive Environment
658
+
659
+ ```ruby
660
+ ZaiPayment.configure do |config|
661
+ config.environment = :prelive
662
+ # Uses: https://sandbox.au-0000.api.assemblypay.com
663
+ end
664
+ ```
665
+
666
+ ### Production Environment
667
+
668
+ ```ruby
669
+ ZaiPayment.configure do |config|
670
+ config.environment = :production
671
+ # Uses: https://secure.api.promisepay.com
672
+ end
673
+ ```
674
+
675
+ The VirtualAccount resource automatically uses the correct endpoint based on your configuration.
676
+
677
+ ## Best Practices
678
+
679
+ ### 1. Meaningful Account Names
680
+
681
+ Use descriptive account names that help identify the purpose:
682
+
683
+ ```ruby
684
+ # Good
685
+ account_name: 'Property 123 Main St Trust Account'
686
+ account_name: 'Client Settlement Fund - Smith'
687
+ account_name: 'Rent Collection - Building A'
688
+
689
+ # Avoid
690
+ account_name: 'Account 1'
691
+ account_name: 'Test'
692
+ ```
693
+
694
+ ### 2. Effective AKA Names
695
+
696
+ Add variations that customers might use when searching:
697
+
698
+ ```ruby
699
+ aka_names: [
700
+ 'Smith Real Estate', # Full name
701
+ 'Smith RE', # Abbreviation
702
+ 'Smith Property Management' # Alternative name
703
+ ]
704
+ ```
705
+
706
+ ### 3. Store Virtual Account Details
707
+
708
+ Always store the virtual account details in your database:
709
+
710
+ ```ruby
711
+ response = virtual_accounts.create(wallet_account_id, account_name: name)
712
+
713
+ if response.success?
714
+ virtual_account = response.data
715
+
716
+ # Store in database
717
+ VirtualAccountRecord.create!(
718
+ external_id: virtual_account['id'],
719
+ routing_number: virtual_account['routing_number'],
720
+ account_number: virtual_account['account_number'],
721
+ account_name: virtual_account['account_name'],
722
+ wallet_account_id: virtual_account['wallet_account_id'],
723
+ status: virtual_account['status']
724
+ )
725
+ end
726
+ ```
727
+
728
+ ### 4. Handle Errors Gracefully
729
+
730
+ Always implement proper error handling:
731
+
732
+ ```ruby
733
+ def create_virtual_account_safely(wallet_account_id, params)
734
+ virtual_accounts = ZaiPayment::Resources::VirtualAccount.new
735
+
736
+ begin
737
+ response = virtual_accounts.create(wallet_account_id, **params)
738
+ { success: true, data: response.data }
739
+ rescue ZaiPayment::Errors::ValidationError => e
740
+ { success: false, error: 'validation', message: e.message }
741
+ rescue ZaiPayment::Errors::NotFoundError => e
742
+ { success: false, error: 'not_found', message: e.message }
743
+ rescue ZaiPayment::Errors::ApiError => e
744
+ { success: false, error: 'api_error', message: e.message }
745
+ end
746
+ end
747
+ ```
748
+
749
+ ### 5. Validate Before Creating
750
+
751
+ Pre-validate input to provide better user feedback:
752
+
753
+ ```ruby
754
+ def validate_virtual_account_params(account_name, aka_names)
755
+ errors = []
756
+
757
+ if account_name.nil? || account_name.strip.empty?
758
+ errors << 'Account name is required'
759
+ elsif account_name.length > 140
760
+ errors << 'Account name must be 140 characters or less'
761
+ end
762
+
763
+ if aka_names && !aka_names.is_a?(Array)
764
+ errors << 'AKA names must be an array'
765
+ elsif aka_names && aka_names.length > 3
766
+ errors << 'Maximum 3 AKA names allowed'
767
+ end
768
+
769
+ errors
770
+ end
771
+
772
+ # Usage
773
+ errors = validate_virtual_account_params(account_name, aka_names)
774
+ if errors.empty?
775
+ # Proceed with creation
776
+ else
777
+ puts "Validation errors: #{errors.join(', ')}"
778
+ end
779
+ ```
780
+
781
+ ### 6. Monitor Virtual Account Status
782
+
783
+ After creation, monitor the status of the virtual account:
784
+
785
+ ```ruby
786
+ virtual_account = response.data
787
+
788
+ case virtual_account['status']
789
+ when 'pending_activation'
790
+ puts "Account created, awaiting activation"
791
+ when 'active'
792
+ puts "Account is active and ready to receive funds"
793
+ when 'inactive'
794
+ puts "Account is inactive"
795
+ else
796
+ puts "Unknown status: #{virtual_account['status']}"
797
+ end
798
+ ```
799
+
800
+ ### 7. Secure Banking Details
801
+
802
+ Treat virtual account details like real bank account information:
803
+
804
+ ```ruby
805
+ # Don't log sensitive details in production
806
+ if Rails.env.production?
807
+ logger.info "Virtual account created: #{virtual_account['id']}"
808
+ else
809
+ logger.debug "Virtual account details: #{virtual_account.inspect}"
810
+ end
811
+
812
+ # Use HTTPS for all communications
813
+ # Store securely in your database
814
+ # Limit access to authorized personnel only
815
+ ```
816
+
817
+ ## Testing
818
+
819
+ For testing in prelive environment:
820
+
821
+ ```ruby
822
+ # spec/services/virtual_account_service_spec.rb
823
+ require 'spec_helper'
824
+
825
+ RSpec.describe VirtualAccountService do
826
+ let(:wallet_account_id) { 'test-wallet-id' }
827
+
828
+ describe '#create_virtual_account' do
829
+ it 'creates a virtual account successfully' do
830
+ VCR.use_cassette('virtual_account_create') do
831
+ service = VirtualAccountService.new
832
+ result = service.create_virtual_account(
833
+ wallet_account_id,
834
+ account_name: 'Test Account',
835
+ aka_names: ['Test']
836
+ )
837
+
838
+ expect(result[:success]).to be true
839
+ expect(result[:virtual_account]['id']).to be_present
840
+ expect(result[:virtual_account]['routing_number']).to be_present
841
+ expect(result[:virtual_account]['account_number']).to be_present
842
+ end
843
+ end
844
+ end
845
+ end
846
+ ```
847
+
848
+ ## Troubleshooting
849
+
850
+ ### Issue: ValidationError - "wallet_account_id is required"
851
+
852
+ **Solution**: Ensure you're passing a valid wallet account ID:
853
+
854
+ ```ruby
855
+ # Wrong
856
+ virtual_accounts.create('', account_name: 'Test')
857
+
858
+ # Correct
859
+ virtual_accounts.create('ae07556e-22ef-11eb-adc1-0242ac120002', account_name: 'Test')
860
+ ```
861
+
862
+ ### Issue: NotFoundError - "Wallet account not found"
863
+
864
+ **Solution**: Verify the wallet account exists before creating a virtual account:
865
+
866
+ ```ruby
867
+ # Check wallet account exists first
868
+ wallet_accounts = ZaiPayment::Resources::WalletAccount.new
869
+ begin
870
+ wallet_response = wallet_accounts.show(wallet_account_id)
871
+ if wallet_response.success?
872
+ # Wallet exists, proceed with virtual account creation
873
+ virtual_accounts.create(wallet_account_id, account_name: 'Test')
874
+ end
875
+ rescue ZaiPayment::Errors::NotFoundError
876
+ puts "Wallet account does not exist"
877
+ end
878
+ ```
879
+
880
+ ### Issue: ValidationError - "account_name must be 140 characters or less"
881
+
882
+ **Solution**: Truncate or shorten the account name:
883
+
884
+ ```ruby
885
+ account_name = "Very Long Account Name That Exceeds The Maximum Length"
886
+
887
+ # Truncate to 140 characters
888
+ truncated_name = account_name[0, 140]
889
+
890
+ virtual_accounts.create(wallet_account_id, account_name: truncated_name)
891
+ ```
892
+
893
+ ### Issue: ValidationError - "aka_names must contain between 0 and 3 items"
894
+
895
+ **Solution**: Limit to maximum 3 AKA names:
896
+
897
+ ```ruby
898
+ # Wrong
899
+ aka_names = ['Name 1', 'Name 2', 'Name 3', 'Name 4']
900
+
901
+ # Correct - take first 3
902
+ aka_names = ['Name 1', 'Name 2', 'Name 3']
903
+
904
+ virtual_accounts.create(
905
+ wallet_account_id,
906
+ account_name: 'Test',
907
+ aka_names: aka_names[0, 3] # Ensure max 3 items
908
+ )
909
+ ```
910
+
911
+ ## See Also
912
+
913
+ - [Wallet Accounts Documentation](wallet_accounts.md)
914
+ - [Examples](../examples/virtual_accounts.md)
915
+ - [Zai API Documentation](https://developer.hellozai.com/docs)
916
+