airwallex 0.8.0 → 0.9.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/CHANGELOG.md +73 -0
- data/README.md +243 -8
- data/lib/airwallex/resources/account_amendment.rb +39 -0
- data/lib/airwallex/resources/account_offboarding.rb +43 -0
- data/lib/airwallex/resources/cardholder.rb +28 -0
- data/lib/airwallex/resources/connected_account.rb +49 -0
- data/lib/airwallex/resources/deposit.rb +115 -0
- data/lib/airwallex/resources/dispute.rb +83 -1
- data/lib/airwallex/resources/issuing_transaction.rb +100 -0
- data/lib/airwallex/resources/linked_account.rb +64 -0
- data/lib/airwallex/resources/payment_consent.rb +15 -0
- data/lib/airwallex/resources/payment_intent.rb +26 -0
- data/lib/airwallex/resources/pos_terminal.rb +71 -0
- data/lib/airwallex/resources/rfi.rb +77 -0
- data/lib/airwallex/resources/transfer.rb +36 -0
- data/lib/airwallex/version.rb +1 -1
- data/lib/airwallex.rb +7 -0
- metadata +10 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0fc42ac04f0af0416fc2fe200f747a17ec326ea5b071a40d5965043da1ebfd7c
|
|
4
|
+
data.tar.gz: bfc8c907e1ce9d0e702e124bdf0c85f1fb8827f7c90b0b4382665b5a4f1ade00
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 952b7120270923df264d701e631570a673584fd08f73502dc3ec36ff19bdd47577d6d799928cdae40781813cbd5f881de7ce4dcca3488e10ee5ff4d46f67da76
|
|
7
|
+
data.tar.gz: 440bfe68c53e17f2a5b30856f6de51257fffdc32c38e459ebe82d8604c9b018d58ee6369249bc2e2907701f0c79f43bdd9005c08eaaf19aa9ceb81486a2e9999
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,68 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.9.0] - 2026-09-09
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Sandbox Simulation API coverage (all sandbox-only, see
|
|
8
|
+
https://www.airwallex.com/docs/api/simulation/):
|
|
9
|
+
- Deposit resource (`.create`/`.retrieve`/`.list` for real Direct Debit deposits,
|
|
10
|
+
`.simulate_create` for a Global Account bank-transfer deposit, `.simulate_settle`,
|
|
11
|
+
`.simulate_reject`, `.simulate_reverse` — the latter three only operate on
|
|
12
|
+
Direct Debit deposits made via `.create`, confirmed against the live sandbox;
|
|
13
|
+
a bank-transfer deposit from `.simulate_create` auto-settles on its own)
|
|
14
|
+
- IssuingTransaction resource (`.simulate_create` with `single_phase`,
|
|
15
|
+
`.simulate_capture`, `.simulate_reverse`, `.simulate_refund`,
|
|
16
|
+
`.simulate_notify_three_ds`) — `.simulate_create`'s response key is
|
|
17
|
+
`transaction_id` (confirmed against the live sandbox; earlier docs research
|
|
18
|
+
suggested `card_transaction_lifecycle_id`, which the API doesn't return)
|
|
19
|
+
- Cardholder resource (`.simulate_pass_review`)
|
|
20
|
+
- LinkedAccount resource (`.simulate_accept_mandate`, `.simulate_reject_mandate`,
|
|
21
|
+
`.simulate_cancel_mandate`, `.simulate_fail_microdeposits`)
|
|
22
|
+
- AccountOffboarding nested resource (`.simulate_complete`, `.simulate_cancel`),
|
|
23
|
+
scoped to a parent ConnectedAccount
|
|
24
|
+
- RFI resource (`.simulate_create`, `.simulate_close`, `.simulate_follow_up`,
|
|
25
|
+
plus instance equivalents)
|
|
26
|
+
- POSTerminal resource (`.simulate_turn_on`, `.simulate_turn_off`,
|
|
27
|
+
`.simulate_generate_activation_code`, `.simulate_confirm_payment_intent`,
|
|
28
|
+
`.simulate_payment_scenarios`)
|
|
29
|
+
- Transfer gained `.simulate_transition`/`#simulate_transition`
|
|
30
|
+
- Dispute gained `.simulate_create`, `.simulate_escalate`/`#simulate_escalate`,
|
|
31
|
+
`.simulate_resolve`/`#simulate_resolve`
|
|
32
|
+
- ConnectedAccount gained `.simulate_update_status`/`#simulate_update_status`,
|
|
33
|
+
`#simulate_complete_offboarding`, `#simulate_cancel_offboarding`
|
|
34
|
+
- AccountAmendment gained `.simulate_approve`/`#simulate_approve`,
|
|
35
|
+
`.simulate_reject`/`#simulate_reject`
|
|
36
|
+
- PaymentIntent gained `#simulate_shopper_pay`, `#simulate_shopper_reject`
|
|
37
|
+
- PaymentConsent gained `#simulate_shopper_verify`
|
|
38
|
+
- New tests covering all of the above (430 total)
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- RFI question `answer.type` corrected to the real uppercase enum (`"TEXT"`,
|
|
43
|
+
not `"text"`) — confirmed against the live sandbox
|
|
44
|
+
|
|
45
|
+
### Verified against the live sandbox
|
|
46
|
+
|
|
47
|
+
- `Deposit.simulate_create`, `Transfer.simulate_transition`,
|
|
48
|
+
`Dispute.simulate_create`, `RFI.simulate_create`/`#simulate_close`,
|
|
49
|
+
`IssuingTransaction.simulate_create`/`.simulate_refund` all confirmed
|
|
50
|
+
working end-to-end
|
|
51
|
+
- `IssuingTransaction.simulate_capture`/`.simulate_reverse` and
|
|
52
|
+
`AccountAmendment.simulate_approve` reach the correct endpoint (accepted,
|
|
53
|
+
not 404) but couldn't be fully exercised on this account — both require
|
|
54
|
+
Admin-level API key permissions this sandbox key doesn't have
|
|
55
|
+
- Found and worked around (in the smoke test only) a pre-existing bug in
|
|
56
|
+
`Client`'s Faraday retry configuration: `retry_options[:exceptions]`
|
|
57
|
+
doesn't include `Faraday::RetriableResponse`, so any POST that gets back
|
|
58
|
+
a `retry_statuses` code (429/500/502/503/504) raises uncaught instead of
|
|
59
|
+
being returned as a normal `Airwallex::Error` — unrelated to the
|
|
60
|
+
Simulation resources, not fixed here
|
|
61
|
+
|
|
3
62
|
## [0.8.0] - 2026-09-02
|
|
4
63
|
|
|
5
64
|
### Fixed
|
|
65
|
+
|
|
6
66
|
- `Util.deep_symbolize_keys` now recurses into `Array`s, matching `ActiveSupport#deep_transform_keys`'s
|
|
7
67
|
behavior. Previously any `Array` value was copied through untouched, so hashes nested inside an array
|
|
8
68
|
(e.g. `beneficiary_form_schemas`'s `fields` list) kept string keys even after "deep" symbolizing.
|
|
@@ -22,6 +82,7 @@
|
|
|
22
82
|
## [0.7.0] - 2026-08-28
|
|
23
83
|
|
|
24
84
|
### Added
|
|
85
|
+
|
|
25
86
|
- GlobalAccount resource (create, retrieve, list, update, `#close`, `#generate_statement_letter`,
|
|
26
87
|
`#transactions`)
|
|
27
88
|
- GlobalAccountAlias nested resource (`#create_alias`, `#alias`, `#aliases`, `#initiate_port`,
|
|
@@ -42,6 +103,7 @@
|
|
|
42
103
|
- New tests covering all of the above (378 total)
|
|
43
104
|
|
|
44
105
|
### Fixed
|
|
106
|
+
|
|
45
107
|
- `APIOperations::Update` now sends `POST #{resource_path}/{id}/update` instead of `PUT #{resource_path}/{id}`
|
|
46
108
|
- `APIOperations::Delete` now sends `POST #{resource_path}/{id}/delete` instead of `DELETE #{resource_path}/{id}`,
|
|
47
109
|
and checks the response instead of always returning `true`
|
|
@@ -73,12 +135,14 @@
|
|
|
73
135
|
- `Quote.create` requires `validity` (`MIN_1`, `MIN_15`, `MIN_30`, `HR_1`, `HR_4`, `HR_8`, or `HR_24`)
|
|
74
136
|
|
|
75
137
|
### Removed
|
|
138
|
+
|
|
76
139
|
- `Rate.list` — the endpoint has no "list all rates" concept, only `.retrieve(sell_currency:, buy_currency:)`
|
|
77
140
|
- `PaymentMethod.delete` and `PaymentMethod#detach` — replaced by `#disable`, the real lifecycle operation
|
|
78
141
|
|
|
79
142
|
## [0.6.0] - 2026-08-28
|
|
80
143
|
|
|
81
144
|
### Fixed
|
|
145
|
+
|
|
82
146
|
- `Webhook::Event` now correctly exposes the event type. Airwallex sends this field as `"name"` in
|
|
83
147
|
the webhook payload, not `"type"` — the previous `Event#type` reader was reading a key that doesn't
|
|
84
148
|
exist and always returned `nil`. The accessor is renamed to `Event#name` to match Airwallex's actual
|
|
@@ -87,6 +151,7 @@
|
|
|
87
151
|
## [0.5.0] - 2026-08-28
|
|
88
152
|
|
|
89
153
|
### Fixed
|
|
154
|
+
|
|
90
155
|
- Webhook signature verification no longer rejects valid webhooks when Airwallex sends
|
|
91
156
|
millisecond-precision timestamps. `verify_timestamp` now detects millisecond values (anything at or
|
|
92
157
|
above `MS_THRESHOLD = 10_000_000_000`, which safely distinguishes seconds from milliseconds until the
|
|
@@ -95,6 +160,7 @@
|
|
|
95
160
|
## [0.4.0] - 2026-08-27
|
|
96
161
|
|
|
97
162
|
### Fixed
|
|
163
|
+
|
|
98
164
|
- `Idempotency` and `AuthRefresh` middleware are now actually registered on the Faraday connection.
|
|
99
165
|
Previously both classes existed but were never wired in, so the "automatic `request_id` generation"
|
|
100
166
|
and "retry once after a 401" behavior documented in the README did not happen at runtime.
|
|
@@ -103,12 +169,14 @@
|
|
|
103
169
|
- CI now triggers on pushes to `main` (previously configured for `master`, so pushes never ran CI).
|
|
104
170
|
|
|
105
171
|
### Changed
|
|
172
|
+
|
|
106
173
|
- `Client#request` no longer manually manages the `Authorization` header or calls
|
|
107
174
|
`ensure_authenticated!` directly; this is now owned by the `AuthRefresh` middleware.
|
|
108
175
|
|
|
109
176
|
## [0.3.0] - 2025-11-25
|
|
110
177
|
|
|
111
178
|
### Added
|
|
179
|
+
|
|
112
180
|
- BatchTransfer resource (create, retrieve, list) for bulk payout operations
|
|
113
181
|
- Dispute resource (retrieve, list, accept, submit_evidence) for chargeback management
|
|
114
182
|
- Foreign Exchange resources:
|
|
@@ -121,15 +189,18 @@
|
|
|
121
189
|
- Comprehensive manual test suite for regression testing
|
|
122
190
|
|
|
123
191
|
### Changed
|
|
192
|
+
|
|
124
193
|
- Refactored List operation for better code quality (reduced complexity from 12 to 7)
|
|
125
194
|
- Balance.retrieve now performs client-side filtering for currency lookup
|
|
126
195
|
|
|
127
196
|
### Fixed
|
|
197
|
+
|
|
128
198
|
- List operation now correctly handles Balance API's direct array response format
|
|
129
199
|
|
|
130
200
|
## [0.2.0] - 2025-11-25
|
|
131
201
|
|
|
132
202
|
### Added
|
|
203
|
+
|
|
133
204
|
- Refund resource (create, retrieve, list)
|
|
134
205
|
- PaymentMethod resource (create, retrieve, list, update, delete, detach)
|
|
135
206
|
- Customer resource (create, retrieve, list, update, delete)
|
|
@@ -139,6 +210,7 @@
|
|
|
139
210
|
## [0.1.0] - 2025-11-25
|
|
140
211
|
|
|
141
212
|
### Added
|
|
213
|
+
|
|
142
214
|
- Core infrastructure: Configuration, Client, Error handling
|
|
143
215
|
- Authentication: Bearer token with automatic refresh
|
|
144
216
|
- API Resources:
|
|
@@ -154,5 +226,6 @@
|
|
|
154
226
|
- Ruby 3.1+ support
|
|
155
227
|
|
|
156
228
|
### Notes
|
|
229
|
+
|
|
157
230
|
- This is an MVP release focusing on core payment acceptance and payout functionality
|
|
158
231
|
- Additional resources (FX, cards, refunds) will be added in future versions
|
data/README.md
CHANGED
|
@@ -16,12 +16,13 @@ This gem provides a Ruby interface to Airwallex's payment infrastructure, design
|
|
|
16
16
|
- **Billing**: Products, prices, billing customers, and subscriptions for recurring/instalment billing
|
|
17
17
|
- **Recurring Payments**: Payment consents and payment sources for merchant-initiated (off-session) charges
|
|
18
18
|
- **Scale**: Connected accounts, funds splits, and charges for platforms onboarding sub-merchants
|
|
19
|
+
- **Sandbox Simulations**: Deposits, issuing transactions, disputes, transfer status, Direct Debit mandates, connected account/offboarding review outcomes, RFIs, and POS terminals — see [Sandbox Simulations](#sandbox-simulations)
|
|
19
20
|
- **Idempotency**: Automatic request deduplication for safe retries
|
|
20
21
|
- **Pagination**: Unified interface over cursor-based and offset-based pagination
|
|
21
22
|
- **Webhook Security**: HMAC-SHA256 signature verification with replay protection
|
|
22
23
|
- **Sandbox Support**: Full testing environment for development
|
|
23
24
|
|
|
24
|
-
**Not yet implemented:** Card issuing.
|
|
25
|
+
**Not yet implemented:** live Card issuing resources (Card, Cardholder create/retrieve/list) — only their sandbox Simulation actions are covered today.
|
|
25
26
|
|
|
26
27
|
## Installation
|
|
27
28
|
|
|
@@ -416,6 +417,230 @@ split.release
|
|
|
416
417
|
charge = Airwallex::Charge.retrieve('charge_id')
|
|
417
418
|
```
|
|
418
419
|
|
|
420
|
+
### Sandbox Simulations
|
|
421
|
+
|
|
422
|
+
Sandbox-only endpoints (`/api/v1/simulation/...`) that stand in for the bank,
|
|
423
|
+
card network, issuing bank, or shopper — for driving async/manual lifecycles
|
|
424
|
+
in tests without waiting on real-world events. See
|
|
425
|
+
https://www.airwallex.com/docs/api/simulation/ for the full reference. All
|
|
426
|
+
of these raise `Airwallex::Error` (same as any other endpoint) if called
|
|
427
|
+
against a production API key.
|
|
428
|
+
|
|
429
|
+
#### Deposits
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
# Simulate an inbound bank transfer landing in a Global Account — auto-settles
|
|
433
|
+
# on its own within a few seconds, same as a real bank transfer
|
|
434
|
+
Airwallex::Deposit.simulate_create(amount: 100.00, global_account_id: 'gacc_123')
|
|
435
|
+
|
|
436
|
+
# .simulate_settle/.simulate_reject/.simulate_reverse are for Direct Debit
|
|
437
|
+
# deposits specifically, made via the real (non-simulation) .create — a real
|
|
438
|
+
# direct debit pull takes days to clear, so the sandbox needs a way to force it
|
|
439
|
+
pending = Airwallex::Deposit.create(funding_source_id: 'la_123', amount: 50.00, currency: 'AUD')
|
|
440
|
+
pending.simulate_settle # -> SETTLED
|
|
441
|
+
# or: pending.simulate_reject -> REJECTED
|
|
442
|
+
|
|
443
|
+
# Reversing only works on an already-SETTLED Direct Debit deposit; it creates
|
|
444
|
+
# an offsetting settled deposit and deactivates the LinkedAccount
|
|
445
|
+
settled = Airwallex::Deposit.create(funding_source_id: 'la_123', amount: 50.00, currency: 'AUD')
|
|
446
|
+
settled.simulate_settle
|
|
447
|
+
settled.simulate_reverse
|
|
448
|
+
|
|
449
|
+
# Class-level equivalents take a deposit_id directly instead of an instance
|
|
450
|
+
Airwallex::Deposit.simulate_settle('dpt_123')
|
|
451
|
+
Airwallex::Deposit.simulate_reject('dpt_123')
|
|
452
|
+
Airwallex::Deposit.simulate_reverse('dpt_123')
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### Issuing Transactions
|
|
456
|
+
|
|
457
|
+
```ruby
|
|
458
|
+
# Authorize, then capture in full
|
|
459
|
+
txn = Airwallex::IssuingTransaction.simulate_create(
|
|
460
|
+
card_id: 'card_123',
|
|
461
|
+
transaction_amount: 25.00,
|
|
462
|
+
transaction_currency: 'USD'
|
|
463
|
+
)
|
|
464
|
+
Airwallex::IssuingTransaction.simulate_capture(txn.transaction_id)
|
|
465
|
+
|
|
466
|
+
# Authorize, then capture only part of the authorized amount
|
|
467
|
+
txn = Airwallex::IssuingTransaction.simulate_create(
|
|
468
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD'
|
|
469
|
+
)
|
|
470
|
+
Airwallex::IssuingTransaction.simulate_capture(txn.transaction_id, transaction_amount: 10.00)
|
|
471
|
+
|
|
472
|
+
# Authorize and clear in a single step (no separate capture needed)
|
|
473
|
+
Airwallex::IssuingTransaction.simulate_create(
|
|
474
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD', single_phase: true
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
# Reverse a still-PENDING (uncaptured) authorization instead of capturing it
|
|
478
|
+
txn = Airwallex::IssuingTransaction.simulate_create(
|
|
479
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD'
|
|
480
|
+
)
|
|
481
|
+
Airwallex::IssuingTransaction.simulate_reverse(txn.transaction_id)
|
|
482
|
+
|
|
483
|
+
# Refund a CAPTURED transaction back to the card
|
|
484
|
+
Airwallex::IssuingTransaction.simulate_refund(
|
|
485
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD'
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# Simulate a 3DS delegation-mode notification for a card
|
|
489
|
+
Airwallex::IssuingTransaction.simulate_notify_three_ds(card_number: '4111111111111111')
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
#### Disputes
|
|
493
|
+
|
|
494
|
+
```ruby
|
|
495
|
+
# Full lifecycle: raised, challenged, then resolved in the merchant's favor
|
|
496
|
+
dispute = Airwallex::Dispute.simulate_create(
|
|
497
|
+
payment_intent_id: payment_intent.id,
|
|
498
|
+
reason_code: '4853',
|
|
499
|
+
stage: 'CHARGEBACK',
|
|
500
|
+
due_at: (Time.now + 86_400).iso8601
|
|
501
|
+
)
|
|
502
|
+
dispute.challenge(customer_communication: 'Proof of delivery')
|
|
503
|
+
dispute.simulate_resolve(in_favor_of: 'MERCHANT')
|
|
504
|
+
|
|
505
|
+
# Or accept without challenging, then resolve in the customer's favor
|
|
506
|
+
dispute = Airwallex::Dispute.simulate_create(
|
|
507
|
+
payment_intent_id: payment_intent.id, reason_code: '4853', stage: 'CHARGEBACK',
|
|
508
|
+
due_at: (Time.now + 86_400).iso8601
|
|
509
|
+
)
|
|
510
|
+
dispute.accept
|
|
511
|
+
dispute.simulate_resolve(in_favor_of: 'CUSTOMER')
|
|
512
|
+
|
|
513
|
+
# Escalate — the issuing bank rejects the challenge evidence and advances the
|
|
514
|
+
# dispute to the next stage (e.g. Chargeback -> Pre-arbitration)
|
|
515
|
+
dispute.challenge(customer_communication: 'Proof of delivery')
|
|
516
|
+
dispute.simulate_escalate(due_at: (Time.now + 86_400).iso8601)
|
|
517
|
+
|
|
518
|
+
# Class-level equivalents take a dispute_id directly instead of an instance
|
|
519
|
+
Airwallex::Dispute.simulate_escalate('dis_123', due_at: (Time.now + 86_400).iso8601)
|
|
520
|
+
Airwallex::Dispute.simulate_resolve('dis_123', in_favor_of: 'MERCHANT')
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
#### Transfers
|
|
524
|
+
|
|
525
|
+
```ruby
|
|
526
|
+
# Advance a transfer's status one step at a time: SCHEDULED -> PROCESSING ->
|
|
527
|
+
# SENT -> PAID
|
|
528
|
+
transfer.simulate_transition(next_status: 'PROCESSING')
|
|
529
|
+
transfer.simulate_transition(next_status: 'SENT')
|
|
530
|
+
transfer.simulate_transition(next_status: 'PAID')
|
|
531
|
+
|
|
532
|
+
# OVERDUE, FAILED, and CANCELLED can be jumped to directly
|
|
533
|
+
transfer.simulate_transition(next_status: 'FAILED', failure_type: 'INSUFFICIENT_FUNDS')
|
|
534
|
+
|
|
535
|
+
# Class-level equivalent takes a transfer_id directly instead of an instance
|
|
536
|
+
Airwallex::Transfer.simulate_transition('tfr_123', next_status: 'CANCELLED')
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
#### Connected Accounts & Offboarding
|
|
540
|
+
|
|
541
|
+
```ruby
|
|
542
|
+
# Simulate a KYC/KYB review outcome (account must currently be SUBMITTED)
|
|
543
|
+
connected_account.simulate_update_status(next_status: 'ACTIVE')
|
|
544
|
+
# or: connected_account.simulate_update_status(next_status: 'SUSPENDED')
|
|
545
|
+
# or: connected_account.simulate_update_status(next_status: 'ACTION_REQUIRED')
|
|
546
|
+
|
|
547
|
+
# Class-level equivalent takes an account_id directly instead of an instance
|
|
548
|
+
Airwallex::ConnectedAccount.simulate_update_status('acct_123', next_status: 'ACTIVE')
|
|
549
|
+
|
|
550
|
+
# Simulate a pending offboarding completing or being cancelled — via the
|
|
551
|
+
# parent ConnectedAccount instance...
|
|
552
|
+
connected_account.simulate_complete_offboarding('obd_456')
|
|
553
|
+
connected_account.simulate_cancel_offboarding('obd_456')
|
|
554
|
+
|
|
555
|
+
# ...or directly against AccountOffboarding with both ids
|
|
556
|
+
Airwallex::AccountOffboarding.simulate_complete('acct_123', 'obd_456')
|
|
557
|
+
Airwallex::AccountOffboarding.simulate_cancel('acct_123', 'obd_456')
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Account Amendments
|
|
561
|
+
|
|
562
|
+
```ruby
|
|
563
|
+
amendment = Airwallex::AccountAmendment.create(
|
|
564
|
+
target: 'account_details.store_details',
|
|
565
|
+
store_details: { store_name: 'New Store Name' }
|
|
566
|
+
)
|
|
567
|
+
amendment.simulate_approve
|
|
568
|
+
# or: amendment.simulate_reject
|
|
569
|
+
|
|
570
|
+
# Class-level equivalents take an amendment_id directly instead of an instance
|
|
571
|
+
Airwallex::AccountAmendment.simulate_approve('amd_123')
|
|
572
|
+
Airwallex::AccountAmendment.simulate_reject('amd_123')
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
#### Linked Accounts (Direct Debit Mandates)
|
|
576
|
+
|
|
577
|
+
```ruby
|
|
578
|
+
# Mandate lifecycle: PROCESSING -> ACTIVE, or PROCESSING -> INACTIVE. All
|
|
579
|
+
# four actions return HTTP 200 with an empty body, so the gem returns `true`
|
|
580
|
+
# rather than a resource instance.
|
|
581
|
+
Airwallex::LinkedAccount.simulate_accept_mandate('la_123') # PROCESSING -> ACTIVE
|
|
582
|
+
Airwallex::LinkedAccount.simulate_reject_mandate('la_123') # PROCESSING -> INACTIVE
|
|
583
|
+
Airwallex::LinkedAccount.simulate_cancel_mandate('la_123') # PROCESSING or ACTIVE -> INACTIVE
|
|
584
|
+
|
|
585
|
+
# Simulate a failed micro-deposit verification: REQUIRES_ACTION -> FAILED
|
|
586
|
+
Airwallex::LinkedAccount.simulate_fail_microdeposits('la_123')
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
#### Shopper Actions
|
|
590
|
+
|
|
591
|
+
```ruby
|
|
592
|
+
# Simulate the shopper completing (or abandoning) a redirect/3DS challenge
|
|
593
|
+
# raised during PaymentIntent#confirm, using the url from its next_action
|
|
594
|
+
payment_intent.simulate_shopper_pay(url: next_action_url)
|
|
595
|
+
# or: payment_intent.simulate_shopper_reject(url: next_action_url)
|
|
596
|
+
|
|
597
|
+
# Simulate the shopper completing a redirect/3DS challenge raised during
|
|
598
|
+
# PaymentConsent#verify, using the url from its next_action
|
|
599
|
+
payment_consent.simulate_shopper_verify(url: next_action_url)
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
#### Issuing Cardholders
|
|
603
|
+
|
|
604
|
+
```ruby
|
|
605
|
+
# Bypass a cardholder's pending RFI review stage
|
|
606
|
+
Airwallex::Cardholder.simulate_pass_review('chd_123')
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
#### Requests for Information (RFIs)
|
|
610
|
+
|
|
611
|
+
```ruby
|
|
612
|
+
# Raise a KYC RFI, then close it
|
|
613
|
+
rfi = Airwallex::RFI.simulate_create(type: 'KYC', questions: [{ answer: { type: 'TEXT' } }])
|
|
614
|
+
rfi.simulate_close
|
|
615
|
+
|
|
616
|
+
# Follow up on an RFI — reopen an existing answered question (by id) or
|
|
617
|
+
# append a new one
|
|
618
|
+
rfi = Airwallex::RFI.simulate_create(type: 'KYC', questions: [{ answer: { type: 'TEXT' } }])
|
|
619
|
+
rfi.simulate_follow_up(questions: [{ answer: { type: 'TEXT' } }])
|
|
620
|
+
|
|
621
|
+
# Class-level equivalents take an rfi_id directly instead of an instance
|
|
622
|
+
Airwallex::RFI.simulate_close('rfi_123')
|
|
623
|
+
Airwallex::RFI.simulate_follow_up('rfi_123', questions: [{ answer: { type: 'TEXT' } }])
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
#### POS Terminals
|
|
627
|
+
|
|
628
|
+
```ruby
|
|
629
|
+
# Turn a terminal on, then have it confirm a PaymentIntent under a named test
|
|
630
|
+
# scenario
|
|
631
|
+
Airwallex::POSTerminal.simulate_turn_on(terminal_id: 'term_123')
|
|
632
|
+
Airwallex::POSTerminal.simulate_confirm_payment_intent(
|
|
633
|
+
terminal_id: 'term_123', payment_scenario_name: 'approve'
|
|
634
|
+
)
|
|
635
|
+
Airwallex::POSTerminal.simulate_turn_off(terminal_id: 'term_123')
|
|
636
|
+
|
|
637
|
+
# List the test scenario names available to simulate_confirm_payment_intent
|
|
638
|
+
Airwallex::POSTerminal.simulate_payment_scenarios
|
|
639
|
+
|
|
640
|
+
# Generate a terminal activation code
|
|
641
|
+
Airwallex::POSTerminal.simulate_generate_activation_code(request_id: 'req_123')
|
|
642
|
+
```
|
|
643
|
+
|
|
419
644
|
## Usage
|
|
420
645
|
|
|
421
646
|
### Authentication
|
|
@@ -588,13 +813,13 @@ end
|
|
|
588
813
|
### Currently Implemented Resources
|
|
589
814
|
|
|
590
815
|
- **Payment Acceptance**:
|
|
591
|
-
- PaymentIntent (create, retrieve, list, update, confirm, cancel, capture)
|
|
816
|
+
- PaymentIntent (create, retrieve, list, update, confirm, cancel, capture, simulate_shopper_pay, simulate_shopper_reject)
|
|
592
817
|
- Refund (create, retrieve, list)
|
|
593
818
|
- PaymentMethod (create, retrieve, list, update, disable)
|
|
594
819
|
- Customer (create, retrieve, list, update, delete)
|
|
595
|
-
- Dispute (retrieve, list, accept, challenge, related_payment_intents)
|
|
820
|
+
- Dispute (retrieve, list, accept, challenge, related_payment_intents, simulate_create, simulate_escalate, simulate_resolve)
|
|
596
821
|
- **Payouts**:
|
|
597
|
-
- Transfer (create, retrieve, list, cancel)
|
|
822
|
+
- Transfer (create, retrieve, list, cancel, simulate_transition)
|
|
598
823
|
- Beneficiary (create, retrieve, list, update, delete, validate, verify_account, api_schema, form_schema, supported_financial_institutions)
|
|
599
824
|
- BatchTransfer (create, retrieve, list)
|
|
600
825
|
- **Foreign Exchange & Multi-Currency**:
|
|
@@ -605,6 +830,8 @@ end
|
|
|
605
830
|
- **Global Accounts**:
|
|
606
831
|
- GlobalAccount (create, retrieve, list, update, close, generate_statement_letter, create_alias, aliases, mandate, mandates)
|
|
607
832
|
- GlobalAccountTransaction, GlobalAccountAlias, GlobalAccountMandate (list/lifecycle actions scoped to a parent account)
|
|
833
|
+
- Deposit (create, retrieve, list — Direct Debit only; simulate_create for a Global Account bank-transfer deposit, simulate_settle/simulate_reject/simulate_reverse for Direct Debit deposits made via create)
|
|
834
|
+
- LinkedAccount — sandbox Simulation only (simulate_accept_mandate, simulate_reject_mandate, simulate_cancel_mandate, simulate_fail_microdeposits)
|
|
608
835
|
- **Billing & Subscriptions**:
|
|
609
836
|
- BillingCustomer (create, retrieve, list, update, bank_transfer_instructions)
|
|
610
837
|
- BillingProduct (create, retrieve, list, update)
|
|
@@ -612,18 +839,26 @@ end
|
|
|
612
839
|
- BillingSubscription (create, retrieve, list, update, items)
|
|
613
840
|
- BillingSubscriptionItem (scoped to a parent subscription)
|
|
614
841
|
- **Recurring Payments**:
|
|
615
|
-
- PaymentConsent (create, retrieve, list, update, verify, verify_continue, disable)
|
|
842
|
+
- PaymentConsent (create, retrieve, list, update, verify, verify_continue, disable, simulate_shopper_verify)
|
|
616
843
|
- PaymentSource (create, retrieve, list)
|
|
617
844
|
- **Scale**:
|
|
618
|
-
- ConnectedAccount (create, retrieve, list, update, current, wallet_info, submit, agree_to_terms_and_conditions, suspend, reactivate)
|
|
619
|
-
- AccountAmendment (create, retrieve) - requires Admin-level API key permissions
|
|
845
|
+
- ConnectedAccount (create, retrieve, list, update, current, wallet_info, submit, agree_to_terms_and_conditions, suspend, reactivate, simulate_update_status, simulate_complete_offboarding, simulate_cancel_offboarding)
|
|
846
|
+
- AccountAmendment (create, retrieve, simulate_approve, simulate_reject) - requires Admin-level API key permissions
|
|
847
|
+
- AccountOffboarding — sandbox Simulation only, scoped to a parent ConnectedAccount (simulate_complete, simulate_cancel)
|
|
620
848
|
- FundsSplit (create, retrieve, list, release)
|
|
621
849
|
- Charge (create, retrieve, list)
|
|
850
|
+
- **Issuing** (sandbox Simulation only — no live Card/Cardholder resources yet):
|
|
851
|
+
- IssuingTransaction (simulate_create with single_phase, simulate_capture, simulate_reverse, simulate_refund, simulate_notify_three_ds)
|
|
852
|
+
- Cardholder (simulate_pass_review)
|
|
853
|
+
- **In-Person Payments** (sandbox Simulation only):
|
|
854
|
+
- POSTerminal (simulate_turn_on, simulate_turn_off, simulate_generate_activation_code, simulate_confirm_payment_intent, simulate_payment_scenarios)
|
|
855
|
+
- **Compliance** (sandbox Simulation only):
|
|
856
|
+
- RFI (simulate_create, simulate_close, simulate_follow_up)
|
|
622
857
|
- **Webhooks**: Event handling, HMAC-SHA256 signature verification
|
|
623
858
|
|
|
624
859
|
### Coming in Future Versions
|
|
625
860
|
|
|
626
|
-
- Card issuing
|
|
861
|
+
- Live Card issuing resources (Card, Cardholder create/retrieve/list) — only their sandbox Simulation actions are implemented today
|
|
627
862
|
|
|
628
863
|
## Environment Support
|
|
629
864
|
|
|
@@ -21,9 +21,48 @@ module Airwallex
|
|
|
21
21
|
extend APIOperations::Create
|
|
22
22
|
extend APIOperations::Retrieve
|
|
23
23
|
|
|
24
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/account-amendments
|
|
25
|
+
SIMULATION_PATH = "/api/v1/simulation/account/amendments"
|
|
26
|
+
|
|
24
27
|
# @return [String] API resource path for account amendments
|
|
25
28
|
def self.resource_path
|
|
26
29
|
"/api/v1/account/amendments"
|
|
27
30
|
end
|
|
31
|
+
|
|
32
|
+
# Simulate approving a PENDING amendment
|
|
33
|
+
#
|
|
34
|
+
# @param amendment_id [String]
|
|
35
|
+
# @return [AccountAmendment]
|
|
36
|
+
def self.simulate_approve(amendment_id)
|
|
37
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{amendment_id}/approve", {})
|
|
38
|
+
new(response)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Simulate rejecting a PENDING amendment
|
|
42
|
+
#
|
|
43
|
+
# @param amendment_id [String]
|
|
44
|
+
# @return [AccountAmendment]
|
|
45
|
+
def self.simulate_reject(amendment_id)
|
|
46
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{amendment_id}/reject", {})
|
|
47
|
+
new(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Simulate approving this amendment
|
|
51
|
+
#
|
|
52
|
+
# @return [AccountAmendment] self
|
|
53
|
+
def simulate_approve
|
|
54
|
+
response = Airwallex.client.post("#{self.class::SIMULATION_PATH}/#{id}/approve", {})
|
|
55
|
+
refresh_from(response)
|
|
56
|
+
self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Simulate rejecting this amendment
|
|
60
|
+
#
|
|
61
|
+
# @return [AccountAmendment] self
|
|
62
|
+
def simulate_reject
|
|
63
|
+
response = Airwallex.client.post("#{self.class::SIMULATION_PATH}/#{id}/reject", {})
|
|
64
|
+
refresh_from(response)
|
|
65
|
+
self
|
|
66
|
+
end
|
|
28
67
|
end
|
|
29
68
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents an offboarding request for a Connected Account. Always
|
|
5
|
+
# accessed through its parent ConnectedAccount, since its API path is
|
|
6
|
+
# scoped by account_id.
|
|
7
|
+
#
|
|
8
|
+
# This gem currently only implements the sandbox Simulation endpoints for
|
|
9
|
+
# completing/cancelling an offboarding; it does not yet implement the live
|
|
10
|
+
# create/retrieve/list endpoints (`/api/v1/accounts/{account_id}/offboardings`).
|
|
11
|
+
# See https://www.airwallex.com/docs/api/simulation/connected-account-offboarding
|
|
12
|
+
#
|
|
13
|
+
# @example Simulate an offboarding finishing
|
|
14
|
+
# Airwallex::AccountOffboarding.simulate_complete("acct_123", "obd_456")
|
|
15
|
+
class AccountOffboarding < APIResource
|
|
16
|
+
# @param account_id [String] the parent ConnectedAccount's id
|
|
17
|
+
# @return [String] API resource path for this account's simulated
|
|
18
|
+
# offboarding actions
|
|
19
|
+
def self.resource_path(account_id)
|
|
20
|
+
"/api/v1/simulation/accounts/#{account_id}/offboardings"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Simulate an offboarding completing
|
|
24
|
+
#
|
|
25
|
+
# @param account_id [String]
|
|
26
|
+
# @param offboarding_id [String]
|
|
27
|
+
# @return [AccountOffboarding]
|
|
28
|
+
def self.simulate_complete(account_id, offboarding_id)
|
|
29
|
+
response = Airwallex.client.post("#{resource_path(account_id)}/#{offboarding_id}/complete", {})
|
|
30
|
+
new(response)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Simulate an offboarding being cancelled
|
|
34
|
+
#
|
|
35
|
+
# @param account_id [String]
|
|
36
|
+
# @param offboarding_id [String]
|
|
37
|
+
# @return [AccountOffboarding]
|
|
38
|
+
def self.simulate_cancel(account_id, offboarding_id)
|
|
39
|
+
response = Airwallex.client.post("#{resource_path(account_id)}/#{offboarding_id}/cancel", {})
|
|
40
|
+
new(response)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents an Issuing Cardholder.
|
|
5
|
+
#
|
|
6
|
+
# This gem currently only implements the sandbox Simulation endpoint for
|
|
7
|
+
# bypassing a cardholder's RFI review stage; it does not yet implement the
|
|
8
|
+
# live create/retrieve/list endpoints.
|
|
9
|
+
# See https://www.airwallex.com/docs/api/simulation/issuing-cardholders
|
|
10
|
+
#
|
|
11
|
+
# @example Bypass a cardholder's pending review
|
|
12
|
+
# Airwallex::Cardholder.simulate_pass_review("chd_123")
|
|
13
|
+
class Cardholder < APIResource
|
|
14
|
+
# @return [String] API resource path for simulated cardholder actions
|
|
15
|
+
def self.resource_path
|
|
16
|
+
"/api/v1/simulation/issuing/cardholders"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Simulate bypassing this cardholder's RFI review stage
|
|
20
|
+
#
|
|
21
|
+
# @param cardholder_id [String]
|
|
22
|
+
# @return [Cardholder]
|
|
23
|
+
def self.simulate_pass_review(cardholder_id)
|
|
24
|
+
response = Airwallex.client.post("#{resource_path}/#{cardholder_id}/pass_review", {})
|
|
25
|
+
new(response)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -57,11 +57,29 @@ module Airwallex
|
|
|
57
57
|
CURRENT_ACCOUNT_PATH = "/api/v1/account"
|
|
58
58
|
WALLET_INFO_PATH = "/api/v1/account/wallet_info"
|
|
59
59
|
|
|
60
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/connected-accounts
|
|
61
|
+
SIMULATION_PATH = "/api/v1/simulation/accounts"
|
|
62
|
+
|
|
60
63
|
# @return [String] API resource path for connected accounts
|
|
61
64
|
def self.resource_path
|
|
62
65
|
"/api/v1/accounts"
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
# Simulate this account's status transitioning. The account must
|
|
69
|
+
# currently be SUBMITTED.
|
|
70
|
+
#
|
|
71
|
+
# @param account_id [String]
|
|
72
|
+
# @param next_status [String] one of "ACTIVE", "SUSPENDED",
|
|
73
|
+
# "ACTION_REQUIRED"
|
|
74
|
+
# @param force [Boolean] defaults to true
|
|
75
|
+
# @return [ConnectedAccount]
|
|
76
|
+
def self.simulate_update_status(account_id, next_status:, force: true)
|
|
77
|
+
response = Airwallex.client.post(
|
|
78
|
+
"#{SIMULATION_PATH}/#{account_id}/update_status", next_status: next_status, force: force
|
|
79
|
+
)
|
|
80
|
+
new(response)
|
|
81
|
+
end
|
|
82
|
+
|
|
65
83
|
# Retrieve whichever account the current request is authenticated as
|
|
66
84
|
#
|
|
67
85
|
# @param params [Hash] additional params
|
|
@@ -132,5 +150,36 @@ module Airwallex
|
|
|
132
150
|
refresh_from(response)
|
|
133
151
|
self
|
|
134
152
|
end
|
|
153
|
+
|
|
154
|
+
# Simulate this account's status transitioning. The account must
|
|
155
|
+
# currently be SUBMITTED.
|
|
156
|
+
#
|
|
157
|
+
# @param next_status [String] one of "ACTIVE", "SUSPENDED",
|
|
158
|
+
# "ACTION_REQUIRED"
|
|
159
|
+
# @param force [Boolean] defaults to true
|
|
160
|
+
# @return [ConnectedAccount] self
|
|
161
|
+
def simulate_update_status(next_status:, force: true)
|
|
162
|
+
response = Airwallex.client.post(
|
|
163
|
+
"#{self.class::SIMULATION_PATH}/#{id}/update_status", next_status: next_status, force: force
|
|
164
|
+
)
|
|
165
|
+
refresh_from(response)
|
|
166
|
+
self
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Simulate a pending offboarding on this account completing
|
|
170
|
+
#
|
|
171
|
+
# @param offboarding_id [String]
|
|
172
|
+
# @return [AccountOffboarding]
|
|
173
|
+
def simulate_complete_offboarding(offboarding_id)
|
|
174
|
+
AccountOffboarding.simulate_complete(id, offboarding_id)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Simulate a pending offboarding on this account being cancelled
|
|
178
|
+
#
|
|
179
|
+
# @param offboarding_id [String]
|
|
180
|
+
# @return [AccountOffboarding]
|
|
181
|
+
def simulate_cancel_offboarding(offboarding_id)
|
|
182
|
+
AccountOffboarding.simulate_cancel(id, offboarding_id)
|
|
183
|
+
end
|
|
135
184
|
end
|
|
136
185
|
end
|