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.
- checksums.yaml +4 -4
- data/badges/coverage.json +1 -1
- data/changelog.md +75 -0
- data/docs/pay_ids.md +777 -0
- data/docs/virtual_accounts.md +916 -0
- data/examples/pay_ids.md +871 -0
- data/examples/virtual_accounts.md +1530 -0
- data/lib/zai_payment/resources/pay_id.rb +197 -0
- data/lib/zai_payment/resources/virtual_account.rb +212 -0
- data/lib/zai_payment/response.rb +1 -1
- data/lib/zai_payment/version.rb +1 -1
- data/lib/zai_payment.rb +12 -0
- data/readme.md +25 -1
- metadata +7 -1
|
@@ -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
|
+
|