airwallex 0.8.0 → 0.10.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 +93 -0
- data/README.md +274 -9
- data/lib/airwallex/list_object.rb +10 -3
- 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 +135 -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: '018417ed2b3091ca1c5eafdf99df79c1a81be1d13ddda2524a917d2bc9a2c3ff'
|
|
4
|
+
data.tar.gz: 0abdb483ebe91136353cc77efb5023cb1d0a05b3fd986c8c3ba59750dfeccfdd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1b48a815ca9ca948b4695cfa28b12e70c5dc21a7c6ff37781d20fe0b2f219b12daf205ec997eaaf51eca19bce49d3ffd0fbb890773cd99f5573c4733680e1ec6
|
|
7
|
+
data.tar.gz: ff66015473fee32cd8ec4bd7cdbb03072b340111d1c354db181c07e0722c936d7b14565210eabd6583f8cd223be9f4c96889b1e329a79b41709ed4626b474427
|
data/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,88 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.10.0] - 2026-09-29
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `RFI.retrieve` (`GET /api/v1/rfis/{id}`) and `RFI.list` (`GET /api/v1/rfis`, filterable by
|
|
8
|
+
`statuses`, `types`, `from_created_at`, `end_created_at`, `page_size`). Both take
|
|
9
|
+
`headers:` for `x-on-behalf-of`
|
|
10
|
+
- `RFI.list` supports bookmark pagination: `has_more`/`next_cursor` come from `page_after`, and
|
|
11
|
+
`next_page`/`auto_paging_each` request `page: <page_after>`
|
|
12
|
+
- `ListObject` accepts `cursor_param:` (defaults to `:next_cursor`) and `opts:`, and forwards
|
|
13
|
+
the original `opts` (e.g. `headers:`) to subsequent pages. Resources that don't pass these
|
|
14
|
+
behave exactly as before
|
|
15
|
+
- `RFI.simulate_create`/`.simulate_close`/`.simulate_follow_up` and the instance
|
|
16
|
+
`#simulate_close`/`#simulate_follow_up` accept an optional trailing `opts` hash with `headers:`
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `RFI.resource_path` is now `/api/v1/rfis`; the simulation path moved to `RFI::SIMULATION_PATH`
|
|
21
|
+
(`/api/v1/simulation/rfis`). Simulation methods hit the same URLs as before
|
|
22
|
+
|
|
23
|
+
## [0.9.0] - 2026-09-09
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Sandbox Simulation API coverage (all sandbox-only, see
|
|
28
|
+
https://www.airwallex.com/docs/api/simulation/):
|
|
29
|
+
- Deposit resource (`.create`/`.retrieve`/`.list` for real Direct Debit deposits,
|
|
30
|
+
`.simulate_create` for a Global Account bank-transfer deposit, `.simulate_settle`,
|
|
31
|
+
`.simulate_reject`, `.simulate_reverse` — the latter three only operate on
|
|
32
|
+
Direct Debit deposits made via `.create`, confirmed against the live sandbox;
|
|
33
|
+
a bank-transfer deposit from `.simulate_create` auto-settles on its own)
|
|
34
|
+
- IssuingTransaction resource (`.simulate_create` with `single_phase`,
|
|
35
|
+
`.simulate_capture`, `.simulate_reverse`, `.simulate_refund`,
|
|
36
|
+
`.simulate_notify_three_ds`) — `.simulate_create`'s response key is
|
|
37
|
+
`transaction_id` (confirmed against the live sandbox; earlier docs research
|
|
38
|
+
suggested `card_transaction_lifecycle_id`, which the API doesn't return)
|
|
39
|
+
- Cardholder resource (`.simulate_pass_review`)
|
|
40
|
+
- LinkedAccount resource (`.simulate_accept_mandate`, `.simulate_reject_mandate`,
|
|
41
|
+
`.simulate_cancel_mandate`, `.simulate_fail_microdeposits`)
|
|
42
|
+
- AccountOffboarding nested resource (`.simulate_complete`, `.simulate_cancel`),
|
|
43
|
+
scoped to a parent ConnectedAccount
|
|
44
|
+
- RFI resource (`.simulate_create`, `.simulate_close`, `.simulate_follow_up`,
|
|
45
|
+
plus instance equivalents)
|
|
46
|
+
- POSTerminal resource (`.simulate_turn_on`, `.simulate_turn_off`,
|
|
47
|
+
`.simulate_generate_activation_code`, `.simulate_confirm_payment_intent`,
|
|
48
|
+
`.simulate_payment_scenarios`)
|
|
49
|
+
- Transfer gained `.simulate_transition`/`#simulate_transition`
|
|
50
|
+
- Dispute gained `.simulate_create`, `.simulate_escalate`/`#simulate_escalate`,
|
|
51
|
+
`.simulate_resolve`/`#simulate_resolve`
|
|
52
|
+
- ConnectedAccount gained `.simulate_update_status`/`#simulate_update_status`,
|
|
53
|
+
`#simulate_complete_offboarding`, `#simulate_cancel_offboarding`
|
|
54
|
+
- AccountAmendment gained `.simulate_approve`/`#simulate_approve`,
|
|
55
|
+
`.simulate_reject`/`#simulate_reject`
|
|
56
|
+
- PaymentIntent gained `#simulate_shopper_pay`, `#simulate_shopper_reject`
|
|
57
|
+
- PaymentConsent gained `#simulate_shopper_verify`
|
|
58
|
+
- New tests covering all of the above (430 total)
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- RFI question `answer.type` corrected to the real uppercase enum (`"TEXT"`,
|
|
63
|
+
not `"text"`) — confirmed against the live sandbox
|
|
64
|
+
|
|
65
|
+
### Verified against the live sandbox
|
|
66
|
+
|
|
67
|
+
- `Deposit.simulate_create`, `Transfer.simulate_transition`,
|
|
68
|
+
`Dispute.simulate_create`, `RFI.simulate_create`/`#simulate_close`,
|
|
69
|
+
`IssuingTransaction.simulate_create`/`.simulate_refund` all confirmed
|
|
70
|
+
working end-to-end
|
|
71
|
+
- `IssuingTransaction.simulate_capture`/`.simulate_reverse` and
|
|
72
|
+
`AccountAmendment.simulate_approve` reach the correct endpoint (accepted,
|
|
73
|
+
not 404) but couldn't be fully exercised on this account — both require
|
|
74
|
+
Admin-level API key permissions this sandbox key doesn't have
|
|
75
|
+
- Found and worked around (in the smoke test only) a pre-existing bug in
|
|
76
|
+
`Client`'s Faraday retry configuration: `retry_options[:exceptions]`
|
|
77
|
+
doesn't include `Faraday::RetriableResponse`, so any POST that gets back
|
|
78
|
+
a `retry_statuses` code (429/500/502/503/504) raises uncaught instead of
|
|
79
|
+
being returned as a normal `Airwallex::Error` — unrelated to the
|
|
80
|
+
Simulation resources, not fixed here
|
|
81
|
+
|
|
3
82
|
## [0.8.0] - 2026-09-02
|
|
4
83
|
|
|
5
84
|
### Fixed
|
|
85
|
+
|
|
6
86
|
- `Util.deep_symbolize_keys` now recurses into `Array`s, matching `ActiveSupport#deep_transform_keys`'s
|
|
7
87
|
behavior. Previously any `Array` value was copied through untouched, so hashes nested inside an array
|
|
8
88
|
(e.g. `beneficiary_form_schemas`'s `fields` list) kept string keys even after "deep" symbolizing.
|
|
@@ -22,6 +102,7 @@
|
|
|
22
102
|
## [0.7.0] - 2026-08-28
|
|
23
103
|
|
|
24
104
|
### Added
|
|
105
|
+
|
|
25
106
|
- GlobalAccount resource (create, retrieve, list, update, `#close`, `#generate_statement_letter`,
|
|
26
107
|
`#transactions`)
|
|
27
108
|
- GlobalAccountAlias nested resource (`#create_alias`, `#alias`, `#aliases`, `#initiate_port`,
|
|
@@ -42,6 +123,7 @@
|
|
|
42
123
|
- New tests covering all of the above (378 total)
|
|
43
124
|
|
|
44
125
|
### Fixed
|
|
126
|
+
|
|
45
127
|
- `APIOperations::Update` now sends `POST #{resource_path}/{id}/update` instead of `PUT #{resource_path}/{id}`
|
|
46
128
|
- `APIOperations::Delete` now sends `POST #{resource_path}/{id}/delete` instead of `DELETE #{resource_path}/{id}`,
|
|
47
129
|
and checks the response instead of always returning `true`
|
|
@@ -73,12 +155,14 @@
|
|
|
73
155
|
- `Quote.create` requires `validity` (`MIN_1`, `MIN_15`, `MIN_30`, `HR_1`, `HR_4`, `HR_8`, or `HR_24`)
|
|
74
156
|
|
|
75
157
|
### Removed
|
|
158
|
+
|
|
76
159
|
- `Rate.list` — the endpoint has no "list all rates" concept, only `.retrieve(sell_currency:, buy_currency:)`
|
|
77
160
|
- `PaymentMethod.delete` and `PaymentMethod#detach` — replaced by `#disable`, the real lifecycle operation
|
|
78
161
|
|
|
79
162
|
## [0.6.0] - 2026-08-28
|
|
80
163
|
|
|
81
164
|
### Fixed
|
|
165
|
+
|
|
82
166
|
- `Webhook::Event` now correctly exposes the event type. Airwallex sends this field as `"name"` in
|
|
83
167
|
the webhook payload, not `"type"` — the previous `Event#type` reader was reading a key that doesn't
|
|
84
168
|
exist and always returned `nil`. The accessor is renamed to `Event#name` to match Airwallex's actual
|
|
@@ -87,6 +171,7 @@
|
|
|
87
171
|
## [0.5.0] - 2026-08-28
|
|
88
172
|
|
|
89
173
|
### Fixed
|
|
174
|
+
|
|
90
175
|
- Webhook signature verification no longer rejects valid webhooks when Airwallex sends
|
|
91
176
|
millisecond-precision timestamps. `verify_timestamp` now detects millisecond values (anything at or
|
|
92
177
|
above `MS_THRESHOLD = 10_000_000_000`, which safely distinguishes seconds from milliseconds until the
|
|
@@ -95,6 +180,7 @@
|
|
|
95
180
|
## [0.4.0] - 2026-08-27
|
|
96
181
|
|
|
97
182
|
### Fixed
|
|
183
|
+
|
|
98
184
|
- `Idempotency` and `AuthRefresh` middleware are now actually registered on the Faraday connection.
|
|
99
185
|
Previously both classes existed but were never wired in, so the "automatic `request_id` generation"
|
|
100
186
|
and "retry once after a 401" behavior documented in the README did not happen at runtime.
|
|
@@ -103,12 +189,14 @@
|
|
|
103
189
|
- CI now triggers on pushes to `main` (previously configured for `master`, so pushes never ran CI).
|
|
104
190
|
|
|
105
191
|
### Changed
|
|
192
|
+
|
|
106
193
|
- `Client#request` no longer manually manages the `Authorization` header or calls
|
|
107
194
|
`ensure_authenticated!` directly; this is now owned by the `AuthRefresh` middleware.
|
|
108
195
|
|
|
109
196
|
## [0.3.0] - 2025-11-25
|
|
110
197
|
|
|
111
198
|
### Added
|
|
199
|
+
|
|
112
200
|
- BatchTransfer resource (create, retrieve, list) for bulk payout operations
|
|
113
201
|
- Dispute resource (retrieve, list, accept, submit_evidence) for chargeback management
|
|
114
202
|
- Foreign Exchange resources:
|
|
@@ -121,15 +209,18 @@
|
|
|
121
209
|
- Comprehensive manual test suite for regression testing
|
|
122
210
|
|
|
123
211
|
### Changed
|
|
212
|
+
|
|
124
213
|
- Refactored List operation for better code quality (reduced complexity from 12 to 7)
|
|
125
214
|
- Balance.retrieve now performs client-side filtering for currency lookup
|
|
126
215
|
|
|
127
216
|
### Fixed
|
|
217
|
+
|
|
128
218
|
- List operation now correctly handles Balance API's direct array response format
|
|
129
219
|
|
|
130
220
|
## [0.2.0] - 2025-11-25
|
|
131
221
|
|
|
132
222
|
### Added
|
|
223
|
+
|
|
133
224
|
- Refund resource (create, retrieve, list)
|
|
134
225
|
- PaymentMethod resource (create, retrieve, list, update, delete, detach)
|
|
135
226
|
- Customer resource (create, retrieve, list, update, delete)
|
|
@@ -139,6 +230,7 @@
|
|
|
139
230
|
## [0.1.0] - 2025-11-25
|
|
140
231
|
|
|
141
232
|
### Added
|
|
233
|
+
|
|
142
234
|
- Core infrastructure: Configuration, Client, Error handling
|
|
143
235
|
- Authentication: Bearer token with automatic refresh
|
|
144
236
|
- API Resources:
|
|
@@ -154,5 +246,6 @@
|
|
|
154
246
|
- Ruby 3.1+ support
|
|
155
247
|
|
|
156
248
|
### Notes
|
|
249
|
+
|
|
157
250
|
- This is an MVP release focusing on core payment acceptance and payout functionality
|
|
158
251
|
- Additional resources (FX, cards, refunds) will be added in future versions
|
data/README.md
CHANGED
|
@@ -16,12 +16,14 @@ 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
|
+
- **Compliance**: Retrieve and list Requests for Information (RFIs), including for connected accounts
|
|
20
|
+
- **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
21
|
- **Idempotency**: Automatic request deduplication for safe retries
|
|
20
22
|
- **Pagination**: Unified interface over cursor-based and offset-based pagination
|
|
21
23
|
- **Webhook Security**: HMAC-SHA256 signature verification with replay protection
|
|
22
24
|
- **Sandbox Support**: Full testing environment for development
|
|
23
25
|
|
|
24
|
-
**Not yet implemented:** Card issuing.
|
|
26
|
+
**Not yet implemented:** live Card issuing resources (Card, Cardholder create/retrieve/list) — only their sandbox Simulation actions are covered today.
|
|
25
27
|
|
|
26
28
|
## Installation
|
|
27
29
|
|
|
@@ -416,6 +418,258 @@ split.release
|
|
|
416
418
|
charge = Airwallex::Charge.retrieve('charge_id')
|
|
417
419
|
```
|
|
418
420
|
|
|
421
|
+
### Requests for Information (RFIs)
|
|
422
|
+
|
|
423
|
+
RFIs are compliance questions Airwallex raises against an account. They use
|
|
424
|
+
bookmark pagination (`page_after`/`page`), which `ListObject` handles for you,
|
|
425
|
+
and connected accounts are addressed with `x-on-behalf-of`, which is kept
|
|
426
|
+
across pages. The API key needs the **Risk → Request for Information (RFI):
|
|
427
|
+
Read** permission, or Airwallex responds with 401 (`Airwallex::AuthenticationError`).
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
headers = { 'x-on-behalf-of' => 'acct_123' }
|
|
431
|
+
|
|
432
|
+
rfi = Airwallex::RFI.retrieve('rfi_123', headers: headers)
|
|
433
|
+
rfi.status # => "ACTION_REQUIRED"
|
|
434
|
+
rfi.active_request[:questions].first # => { id: ..., title: { en: ... }, answer: { type: "ATTACHMENT" }, ... }
|
|
435
|
+
|
|
436
|
+
# statuses/types are comma-separated strings; page_size is 1-2000 (default 100)
|
|
437
|
+
Airwallex::RFI.list({ statuses: 'ACTION_REQUIRED', types: 'KYC,KYC_ONGOING' }, headers: headers)
|
|
438
|
+
.auto_paging_each { |r| puts r.id }
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### Sandbox Simulations
|
|
442
|
+
|
|
443
|
+
Sandbox-only endpoints (`/api/v1/simulation/...`) that stand in for the bank,
|
|
444
|
+
card network, issuing bank, or shopper — for driving async/manual lifecycles
|
|
445
|
+
in tests without waiting on real-world events. See
|
|
446
|
+
https://www.airwallex.com/docs/api/simulation/ for the full reference. All
|
|
447
|
+
of these raise `Airwallex::Error` (same as any other endpoint) if called
|
|
448
|
+
against a production API key.
|
|
449
|
+
|
|
450
|
+
#### Deposits
|
|
451
|
+
|
|
452
|
+
```ruby
|
|
453
|
+
# Simulate an inbound bank transfer landing in a Global Account — auto-settles
|
|
454
|
+
# on its own within a few seconds, same as a real bank transfer
|
|
455
|
+
Airwallex::Deposit.simulate_create(amount: 100.00, global_account_id: 'gacc_123')
|
|
456
|
+
|
|
457
|
+
# .simulate_settle/.simulate_reject/.simulate_reverse are for Direct Debit
|
|
458
|
+
# deposits specifically, made via the real (non-simulation) .create — a real
|
|
459
|
+
# direct debit pull takes days to clear, so the sandbox needs a way to force it
|
|
460
|
+
pending = Airwallex::Deposit.create(funding_source_id: 'la_123', amount: 50.00, currency: 'AUD')
|
|
461
|
+
pending.simulate_settle # -> SETTLED
|
|
462
|
+
# or: pending.simulate_reject -> REJECTED
|
|
463
|
+
|
|
464
|
+
# Reversing only works on an already-SETTLED Direct Debit deposit; it creates
|
|
465
|
+
# an offsetting settled deposit and deactivates the LinkedAccount
|
|
466
|
+
settled = Airwallex::Deposit.create(funding_source_id: 'la_123', amount: 50.00, currency: 'AUD')
|
|
467
|
+
settled.simulate_settle
|
|
468
|
+
settled.simulate_reverse
|
|
469
|
+
|
|
470
|
+
# Class-level equivalents take a deposit_id directly instead of an instance
|
|
471
|
+
Airwallex::Deposit.simulate_settle('dpt_123')
|
|
472
|
+
Airwallex::Deposit.simulate_reject('dpt_123')
|
|
473
|
+
Airwallex::Deposit.simulate_reverse('dpt_123')
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
#### Issuing Transactions
|
|
477
|
+
|
|
478
|
+
```ruby
|
|
479
|
+
# Authorize, then capture in full
|
|
480
|
+
txn = Airwallex::IssuingTransaction.simulate_create(
|
|
481
|
+
card_id: 'card_123',
|
|
482
|
+
transaction_amount: 25.00,
|
|
483
|
+
transaction_currency: 'USD'
|
|
484
|
+
)
|
|
485
|
+
Airwallex::IssuingTransaction.simulate_capture(txn.transaction_id)
|
|
486
|
+
|
|
487
|
+
# Authorize, then capture only part of the authorized amount
|
|
488
|
+
txn = Airwallex::IssuingTransaction.simulate_create(
|
|
489
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD'
|
|
490
|
+
)
|
|
491
|
+
Airwallex::IssuingTransaction.simulate_capture(txn.transaction_id, transaction_amount: 10.00)
|
|
492
|
+
|
|
493
|
+
# Authorize and clear in a single step (no separate capture needed)
|
|
494
|
+
Airwallex::IssuingTransaction.simulate_create(
|
|
495
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD', single_phase: true
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
# Reverse a still-PENDING (uncaptured) authorization instead of capturing it
|
|
499
|
+
txn = Airwallex::IssuingTransaction.simulate_create(
|
|
500
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD'
|
|
501
|
+
)
|
|
502
|
+
Airwallex::IssuingTransaction.simulate_reverse(txn.transaction_id)
|
|
503
|
+
|
|
504
|
+
# Refund a CAPTURED transaction back to the card
|
|
505
|
+
Airwallex::IssuingTransaction.simulate_refund(
|
|
506
|
+
card_id: 'card_123', transaction_amount: 25.00, transaction_currency: 'USD'
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
# Simulate a 3DS delegation-mode notification for a card
|
|
510
|
+
Airwallex::IssuingTransaction.simulate_notify_three_ds(card_number: '4111111111111111')
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
#### Disputes
|
|
514
|
+
|
|
515
|
+
```ruby
|
|
516
|
+
# Full lifecycle: raised, challenged, then resolved in the merchant's favor
|
|
517
|
+
dispute = Airwallex::Dispute.simulate_create(
|
|
518
|
+
payment_intent_id: payment_intent.id,
|
|
519
|
+
reason_code: '4853',
|
|
520
|
+
stage: 'CHARGEBACK',
|
|
521
|
+
due_at: (Time.now + 86_400).iso8601
|
|
522
|
+
)
|
|
523
|
+
dispute.challenge(customer_communication: 'Proof of delivery')
|
|
524
|
+
dispute.simulate_resolve(in_favor_of: 'MERCHANT')
|
|
525
|
+
|
|
526
|
+
# Or accept without challenging, then resolve in the customer's favor
|
|
527
|
+
dispute = Airwallex::Dispute.simulate_create(
|
|
528
|
+
payment_intent_id: payment_intent.id, reason_code: '4853', stage: 'CHARGEBACK',
|
|
529
|
+
due_at: (Time.now + 86_400).iso8601
|
|
530
|
+
)
|
|
531
|
+
dispute.accept
|
|
532
|
+
dispute.simulate_resolve(in_favor_of: 'CUSTOMER')
|
|
533
|
+
|
|
534
|
+
# Escalate — the issuing bank rejects the challenge evidence and advances the
|
|
535
|
+
# dispute to the next stage (e.g. Chargeback -> Pre-arbitration)
|
|
536
|
+
dispute.challenge(customer_communication: 'Proof of delivery')
|
|
537
|
+
dispute.simulate_escalate(due_at: (Time.now + 86_400).iso8601)
|
|
538
|
+
|
|
539
|
+
# Class-level equivalents take a dispute_id directly instead of an instance
|
|
540
|
+
Airwallex::Dispute.simulate_escalate('dis_123', due_at: (Time.now + 86_400).iso8601)
|
|
541
|
+
Airwallex::Dispute.simulate_resolve('dis_123', in_favor_of: 'MERCHANT')
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### Transfers
|
|
545
|
+
|
|
546
|
+
```ruby
|
|
547
|
+
# Advance a transfer's status one step at a time: SCHEDULED -> PROCESSING ->
|
|
548
|
+
# SENT -> PAID
|
|
549
|
+
transfer.simulate_transition(next_status: 'PROCESSING')
|
|
550
|
+
transfer.simulate_transition(next_status: 'SENT')
|
|
551
|
+
transfer.simulate_transition(next_status: 'PAID')
|
|
552
|
+
|
|
553
|
+
# OVERDUE, FAILED, and CANCELLED can be jumped to directly
|
|
554
|
+
transfer.simulate_transition(next_status: 'FAILED', failure_type: 'INSUFFICIENT_FUNDS')
|
|
555
|
+
|
|
556
|
+
# Class-level equivalent takes a transfer_id directly instead of an instance
|
|
557
|
+
Airwallex::Transfer.simulate_transition('tfr_123', next_status: 'CANCELLED')
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### Connected Accounts & Offboarding
|
|
561
|
+
|
|
562
|
+
```ruby
|
|
563
|
+
# Simulate a KYC/KYB review outcome (account must currently be SUBMITTED)
|
|
564
|
+
connected_account.simulate_update_status(next_status: 'ACTIVE')
|
|
565
|
+
# or: connected_account.simulate_update_status(next_status: 'SUSPENDED')
|
|
566
|
+
# or: connected_account.simulate_update_status(next_status: 'ACTION_REQUIRED')
|
|
567
|
+
|
|
568
|
+
# Class-level equivalent takes an account_id directly instead of an instance
|
|
569
|
+
Airwallex::ConnectedAccount.simulate_update_status('acct_123', next_status: 'ACTIVE')
|
|
570
|
+
|
|
571
|
+
# Simulate a pending offboarding completing or being cancelled — via the
|
|
572
|
+
# parent ConnectedAccount instance...
|
|
573
|
+
connected_account.simulate_complete_offboarding('obd_456')
|
|
574
|
+
connected_account.simulate_cancel_offboarding('obd_456')
|
|
575
|
+
|
|
576
|
+
# ...or directly against AccountOffboarding with both ids
|
|
577
|
+
Airwallex::AccountOffboarding.simulate_complete('acct_123', 'obd_456')
|
|
578
|
+
Airwallex::AccountOffboarding.simulate_cancel('acct_123', 'obd_456')
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### Account Amendments
|
|
582
|
+
|
|
583
|
+
```ruby
|
|
584
|
+
amendment = Airwallex::AccountAmendment.create(
|
|
585
|
+
target: 'account_details.store_details',
|
|
586
|
+
store_details: { store_name: 'New Store Name' }
|
|
587
|
+
)
|
|
588
|
+
amendment.simulate_approve
|
|
589
|
+
# or: amendment.simulate_reject
|
|
590
|
+
|
|
591
|
+
# Class-level equivalents take an amendment_id directly instead of an instance
|
|
592
|
+
Airwallex::AccountAmendment.simulate_approve('amd_123')
|
|
593
|
+
Airwallex::AccountAmendment.simulate_reject('amd_123')
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
#### Linked Accounts (Direct Debit Mandates)
|
|
597
|
+
|
|
598
|
+
```ruby
|
|
599
|
+
# Mandate lifecycle: PROCESSING -> ACTIVE, or PROCESSING -> INACTIVE. All
|
|
600
|
+
# four actions return HTTP 200 with an empty body, so the gem returns `true`
|
|
601
|
+
# rather than a resource instance.
|
|
602
|
+
Airwallex::LinkedAccount.simulate_accept_mandate('la_123') # PROCESSING -> ACTIVE
|
|
603
|
+
Airwallex::LinkedAccount.simulate_reject_mandate('la_123') # PROCESSING -> INACTIVE
|
|
604
|
+
Airwallex::LinkedAccount.simulate_cancel_mandate('la_123') # PROCESSING or ACTIVE -> INACTIVE
|
|
605
|
+
|
|
606
|
+
# Simulate a failed micro-deposit verification: REQUIRES_ACTION -> FAILED
|
|
607
|
+
Airwallex::LinkedAccount.simulate_fail_microdeposits('la_123')
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
#### Shopper Actions
|
|
611
|
+
|
|
612
|
+
```ruby
|
|
613
|
+
# Simulate the shopper completing (or abandoning) a redirect/3DS challenge
|
|
614
|
+
# raised during PaymentIntent#confirm, using the url from its next_action
|
|
615
|
+
payment_intent.simulate_shopper_pay(url: next_action_url)
|
|
616
|
+
# or: payment_intent.simulate_shopper_reject(url: next_action_url)
|
|
617
|
+
|
|
618
|
+
# Simulate the shopper completing a redirect/3DS challenge raised during
|
|
619
|
+
# PaymentConsent#verify, using the url from its next_action
|
|
620
|
+
payment_consent.simulate_shopper_verify(url: next_action_url)
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
#### Issuing Cardholders
|
|
624
|
+
|
|
625
|
+
```ruby
|
|
626
|
+
# Bypass a cardholder's pending RFI review stage
|
|
627
|
+
Airwallex::Cardholder.simulate_pass_review('chd_123')
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
#### Requests for Information (RFIs)
|
|
631
|
+
|
|
632
|
+
```ruby
|
|
633
|
+
# Raise a KYC RFI, then close it
|
|
634
|
+
rfi = Airwallex::RFI.simulate_create(type: 'KYC', questions: [{ answer: { type: 'TEXT' } }])
|
|
635
|
+
rfi.simulate_close
|
|
636
|
+
|
|
637
|
+
# Follow up on an RFI — reopen an existing answered question (by id) or
|
|
638
|
+
# append a new one
|
|
639
|
+
rfi = Airwallex::RFI.simulate_create(type: 'KYC', questions: [{ answer: { type: 'TEXT' } }])
|
|
640
|
+
rfi.simulate_follow_up(questions: [{ answer: { type: 'TEXT' } }])
|
|
641
|
+
|
|
642
|
+
# Class-level equivalents take an rfi_id directly instead of an instance
|
|
643
|
+
Airwallex::RFI.simulate_close('rfi_123')
|
|
644
|
+
Airwallex::RFI.simulate_follow_up('rfi_123', questions: [{ answer: { type: 'TEXT' } }])
|
|
645
|
+
|
|
646
|
+
# Raise an RFI against a connected account — params need braces when
|
|
647
|
+
# followed by opts. Fails with BadRequestError ("ongoing RFI case exists")
|
|
648
|
+
# if the account already has an open RFI.
|
|
649
|
+
Airwallex::RFI.simulate_create(
|
|
650
|
+
{ type: 'KYC', questions: [{ answer: { type: 'TEXT' } }] },
|
|
651
|
+
headers: { 'x-on-behalf-of' => 'acct_123' }
|
|
652
|
+
)
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
#### POS Terminals
|
|
656
|
+
|
|
657
|
+
```ruby
|
|
658
|
+
# Turn a terminal on, then have it confirm a PaymentIntent under a named test
|
|
659
|
+
# scenario
|
|
660
|
+
Airwallex::POSTerminal.simulate_turn_on(terminal_id: 'term_123')
|
|
661
|
+
Airwallex::POSTerminal.simulate_confirm_payment_intent(
|
|
662
|
+
terminal_id: 'term_123', payment_scenario_name: 'approve'
|
|
663
|
+
)
|
|
664
|
+
Airwallex::POSTerminal.simulate_turn_off(terminal_id: 'term_123')
|
|
665
|
+
|
|
666
|
+
# List the test scenario names available to simulate_confirm_payment_intent
|
|
667
|
+
Airwallex::POSTerminal.simulate_payment_scenarios
|
|
668
|
+
|
|
669
|
+
# Generate a terminal activation code
|
|
670
|
+
Airwallex::POSTerminal.simulate_generate_activation_code(request_id: 'req_123')
|
|
671
|
+
```
|
|
672
|
+
|
|
419
673
|
## Usage
|
|
420
674
|
|
|
421
675
|
### Authentication
|
|
@@ -454,7 +708,8 @@ transfer = Airwallex::Transfer.create(
|
|
|
454
708
|
|
|
455
709
|
### Pagination
|
|
456
710
|
|
|
457
|
-
Unified interface across
|
|
711
|
+
Unified interface across cursor-based, offset-based, and bookmark-based (RFI) endpoints.
|
|
712
|
+
Any `headers:` passed to `.list` (e.g. `x-on-behalf-of`) are kept on every page:
|
|
458
713
|
|
|
459
714
|
```ruby
|
|
460
715
|
# Auto-pagination with enumerable
|
|
@@ -588,13 +843,13 @@ end
|
|
|
588
843
|
### Currently Implemented Resources
|
|
589
844
|
|
|
590
845
|
- **Payment Acceptance**:
|
|
591
|
-
- PaymentIntent (create, retrieve, list, update, confirm, cancel, capture)
|
|
846
|
+
- PaymentIntent (create, retrieve, list, update, confirm, cancel, capture, simulate_shopper_pay, simulate_shopper_reject)
|
|
592
847
|
- Refund (create, retrieve, list)
|
|
593
848
|
- PaymentMethod (create, retrieve, list, update, disable)
|
|
594
849
|
- Customer (create, retrieve, list, update, delete)
|
|
595
|
-
- Dispute (retrieve, list, accept, challenge, related_payment_intents)
|
|
850
|
+
- Dispute (retrieve, list, accept, challenge, related_payment_intents, simulate_create, simulate_escalate, simulate_resolve)
|
|
596
851
|
- **Payouts**:
|
|
597
|
-
- Transfer (create, retrieve, list, cancel)
|
|
852
|
+
- Transfer (create, retrieve, list, cancel, simulate_transition)
|
|
598
853
|
- Beneficiary (create, retrieve, list, update, delete, validate, verify_account, api_schema, form_schema, supported_financial_institutions)
|
|
599
854
|
- BatchTransfer (create, retrieve, list)
|
|
600
855
|
- **Foreign Exchange & Multi-Currency**:
|
|
@@ -605,6 +860,8 @@ end
|
|
|
605
860
|
- **Global Accounts**:
|
|
606
861
|
- GlobalAccount (create, retrieve, list, update, close, generate_statement_letter, create_alias, aliases, mandate, mandates)
|
|
607
862
|
- GlobalAccountTransaction, GlobalAccountAlias, GlobalAccountMandate (list/lifecycle actions scoped to a parent account)
|
|
863
|
+
- 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)
|
|
864
|
+
- LinkedAccount — sandbox Simulation only (simulate_accept_mandate, simulate_reject_mandate, simulate_cancel_mandate, simulate_fail_microdeposits)
|
|
608
865
|
- **Billing & Subscriptions**:
|
|
609
866
|
- BillingCustomer (create, retrieve, list, update, bank_transfer_instructions)
|
|
610
867
|
- BillingProduct (create, retrieve, list, update)
|
|
@@ -612,18 +869,26 @@ end
|
|
|
612
869
|
- BillingSubscription (create, retrieve, list, update, items)
|
|
613
870
|
- BillingSubscriptionItem (scoped to a parent subscription)
|
|
614
871
|
- **Recurring Payments**:
|
|
615
|
-
- PaymentConsent (create, retrieve, list, update, verify, verify_continue, disable)
|
|
872
|
+
- PaymentConsent (create, retrieve, list, update, verify, verify_continue, disable, simulate_shopper_verify)
|
|
616
873
|
- PaymentSource (create, retrieve, list)
|
|
617
874
|
- **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
|
|
875
|
+
- 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)
|
|
876
|
+
- AccountAmendment (create, retrieve, simulate_approve, simulate_reject) - requires Admin-level API key permissions
|
|
877
|
+
- AccountOffboarding — sandbox Simulation only, scoped to a parent ConnectedAccount (simulate_complete, simulate_cancel)
|
|
620
878
|
- FundsSplit (create, retrieve, list, release)
|
|
621
879
|
- Charge (create, retrieve, list)
|
|
880
|
+
- **Issuing** (sandbox Simulation only — no live Card/Cardholder resources yet):
|
|
881
|
+
- IssuingTransaction (simulate_create with single_phase, simulate_capture, simulate_reverse, simulate_refund, simulate_notify_three_ds)
|
|
882
|
+
- Cardholder (simulate_pass_review)
|
|
883
|
+
- **In-Person Payments** (sandbox Simulation only):
|
|
884
|
+
- POSTerminal (simulate_turn_on, simulate_turn_off, simulate_generate_activation_code, simulate_confirm_payment_intent, simulate_payment_scenarios)
|
|
885
|
+
- **Compliance**:
|
|
886
|
+
- RFI (retrieve, list, plus sandbox simulate_create, simulate_close, simulate_follow_up)
|
|
622
887
|
- **Webhooks**: Event handling, HMAC-SHA256 signature verification
|
|
623
888
|
|
|
624
889
|
### Coming in Future Versions
|
|
625
890
|
|
|
626
|
-
- Card issuing
|
|
891
|
+
- Live Card issuing resources (Card, Cardholder create/retrieve/list) — only their sandbox Simulation actions are implemented today
|
|
627
892
|
|
|
628
893
|
## Environment Support
|
|
629
894
|
|
|
@@ -6,12 +6,19 @@ module Airwallex
|
|
|
6
6
|
|
|
7
7
|
attr_reader :data, :has_more, :next_cursor
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# @param cursor_param [Symbol] query param the next cursor is sent as
|
|
10
|
+
# (e.g. :page for bookmark-paginated endpoints like RFI)
|
|
11
|
+
# @param opts [Hash] the original request opts (e.g. headers:), forwarded
|
|
12
|
+
# to every subsequent page so x-on-behalf-of etc. are kept
|
|
13
|
+
def initialize(data:, has_more:, resource_class:, next_cursor: nil, params: {}, # rubocop:disable Metrics/ParameterLists
|
|
14
|
+
cursor_param: :next_cursor, opts: {})
|
|
10
15
|
@data = data.map { |item| resource_class.new(item) }
|
|
11
16
|
@has_more = has_more
|
|
12
17
|
@next_cursor = next_cursor
|
|
13
18
|
@resource_class = resource_class
|
|
14
19
|
@params = params
|
|
20
|
+
@cursor_param = cursor_param
|
|
21
|
+
@opts = opts
|
|
15
22
|
end
|
|
16
23
|
|
|
17
24
|
def each(&)
|
|
@@ -49,7 +56,7 @@ module Airwallex
|
|
|
49
56
|
|
|
50
57
|
if @next_cursor
|
|
51
58
|
# Cursor-based pagination
|
|
52
|
-
next_params[
|
|
59
|
+
next_params[@cursor_param] = @next_cursor
|
|
53
60
|
else
|
|
54
61
|
# Offset-based pagination
|
|
55
62
|
page_size = @params[:page_size] || @params[:limit] || 20
|
|
@@ -57,7 +64,7 @@ module Airwallex
|
|
|
57
64
|
next_params[:offset] = current_offset + page_size
|
|
58
65
|
end
|
|
59
66
|
|
|
60
|
-
@resource_class.list(next_params)
|
|
67
|
+
@opts.empty? ? @resource_class.list(next_params) : @resource_class.list(next_params, @opts)
|
|
61
68
|
end
|
|
62
69
|
|
|
63
70
|
# Automatically iterate through all pages
|
|
@@ -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
|