airwallex 0.9.0 → 0.11.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 +46 -0
- data/README.md +94 -9
- data/lib/airwallex/list_object.rb +10 -3
- data/lib/airwallex/resources/connected_account_transfer.rb +33 -0
- data/lib/airwallex/resources/funds_split.rb +20 -6
- data/lib/airwallex/resources/funds_split_reversal.rb +31 -0
- data/lib/airwallex/resources/rfi.rb +73 -15
- data/lib/airwallex/resources/transfer.rb +17 -8
- data/lib/airwallex/version.rb +1 -1
- data/lib/airwallex.rb +2 -0
- metadata +5 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f1288d75bff8a6f00a8c0dfffc318b6df1821cb26c945e062930de6035e83822
|
|
4
|
+
data.tar.gz: 5452a4a8aed172764129f05043646e9f074c1c9e62df84246ffe6ff575ba0140
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02e3eea25661113f96af469d3154ede00aae144c671c42fcdf48d2e08d900c74ff2291b9b901ebc2b2072688b840d07d58f6b7eb21bd14aaf2c75d2e4625e6f2
|
|
7
|
+
data.tar.gz: 31bb06a6c4a99f4d433c63b5eb9e209e31deeec63dd3d98a516d259801530e874b66b70d4680d386eba0e569589a55e51c59640af3907fbade0e1da9171b6698
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,51 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-10-01
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- `FundsSplitReversal` resource (`.create`, `.retrieve`, `.list`) for
|
|
8
|
+
`/api/v1/pa/funds_split_reversals`, which moves split funds back from a
|
|
9
|
+
connected account to the platform. `.list` takes the required `funds_split_id:`.
|
|
10
|
+
Partial reversals are allowed until the full split is reversed
|
|
11
|
+
- `ConnectedAccountTransfer` resource (`.create`, `.retrieve`, `.list`) for
|
|
12
|
+
`/api/v1/connected_account_transfers`, which moves funds between the platform
|
|
13
|
+
and a connected account. Pass `headers: { "x-on-behalf-of" => ... }` to
|
|
14
|
+
debit a connected account instead of the platform
|
|
15
|
+
- `Transfer.simulate_transition`, `Transfer#simulate_transition` and
|
|
16
|
+
`Transfer#cancel` accept `headers:`. A transfer created `x-on-behalf-of` a
|
|
17
|
+
connected account can only be found with that same header
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- `FundsSplit` docs (README and class comment) now show the real create body:
|
|
22
|
+
`request_id`, `source_id`, `source_type: "PAYMENT_INTENT"`, `amount` (string),
|
|
23
|
+
`destination`, `auto_release`, `metadata`, with one split per destination.
|
|
24
|
+
The docs previously showed `payment_intent_id:` and `splits: [...]`
|
|
25
|
+
- Documented that `funds_split.*` webhook payloads use `split_id` (not `id`)
|
|
26
|
+
and carry no `request_id`, and that the sandbox rejects a PROCESSING -> PAID
|
|
27
|
+
transfer transition (go through SENT)
|
|
28
|
+
|
|
29
|
+
## [0.10.0] - 2026-09-29
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
|
|
33
|
+
- `RFI.retrieve` (`GET /api/v1/rfis/{id}`) and `RFI.list` (`GET /api/v1/rfis`, filterable by
|
|
34
|
+
`statuses`, `types`, `from_created_at`, `end_created_at`, `page_size`). Both take
|
|
35
|
+
`headers:` for `x-on-behalf-of`
|
|
36
|
+
- `RFI.list` supports bookmark pagination: `has_more`/`next_cursor` come from `page_after`, and
|
|
37
|
+
`next_page`/`auto_paging_each` request `page: <page_after>`
|
|
38
|
+
- `ListObject` accepts `cursor_param:` (defaults to `:next_cursor`) and `opts:`, and forwards
|
|
39
|
+
the original `opts` (e.g. `headers:`) to subsequent pages. Resources that don't pass these
|
|
40
|
+
behave exactly as before
|
|
41
|
+
- `RFI.simulate_create`/`.simulate_close`/`.simulate_follow_up` and the instance
|
|
42
|
+
`#simulate_close`/`#simulate_follow_up` accept an optional trailing `opts` hash with `headers:`
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- `RFI.resource_path` is now `/api/v1/rfis`; the simulation path moved to `RFI::SIMULATION_PATH`
|
|
47
|
+
(`/api/v1/simulation/rfis`). Simulation methods hit the same URLs as before
|
|
48
|
+
|
|
3
49
|
## [0.9.0] - 2026-09-09
|
|
4
50
|
|
|
5
51
|
### Added
|
data/README.md
CHANGED
|
@@ -16,6 +16,7 @@ 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
|
|
19
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)
|
|
20
21
|
- **Idempotency**: Automatic request deduplication for safe retries
|
|
21
22
|
- **Pagination**: Unified interface over cursor-based and offset-based pagination
|
|
@@ -374,7 +375,7 @@ payment_source = Airwallex::PaymentSource.create(
|
|
|
374
375
|
)
|
|
375
376
|
```
|
|
376
377
|
|
|
377
|
-
### Scale (Connected Accounts, Funds Splits, Charges)
|
|
378
|
+
### Scale (Connected Accounts, Funds Splits, Reversals, Connected Account Transfers, Charges)
|
|
378
379
|
|
|
379
380
|
For platforms onboarding sub-merchants:
|
|
380
381
|
|
|
@@ -406,17 +407,76 @@ connected_account.submit
|
|
|
406
407
|
Airwallex::ConnectedAccount.current
|
|
407
408
|
Airwallex::ConnectedAccount.wallet_info
|
|
408
409
|
|
|
409
|
-
# Split a
|
|
410
|
+
# Split part of a PaymentIntent's funds to a connected account. One split per
|
|
411
|
+
# destination: create several splits to pay several connected accounts.
|
|
412
|
+
# A reused request_id raises BadRequestError (duplicate_request); splitting
|
|
413
|
+
# more than the PaymentIntent amount raises amount_above_limit.
|
|
410
414
|
split = Airwallex::FundsSplit.create(
|
|
411
|
-
|
|
412
|
-
|
|
415
|
+
request_id: 'split-order-123', # required, max 64
|
|
416
|
+
source_id: payment_intent.id, # required
|
|
417
|
+
source_type: 'PAYMENT_INTENT', # required
|
|
418
|
+
amount: '10.00', # required, string
|
|
419
|
+
destination: connected_account.id,
|
|
420
|
+
auto_release: false, # optional, default true
|
|
421
|
+
metadata: { order_id: '123' } # optional
|
|
422
|
+
)
|
|
423
|
+
split.release # only needed when auto_release: false
|
|
424
|
+
|
|
425
|
+
# Reverse split funds back to the platform. Partial reversals are allowed
|
|
426
|
+
# until the full split is reversed. Statuses: CREATED, RELEASED, SETTLED
|
|
427
|
+
reversal = Airwallex::FundsSplitReversal.create(
|
|
428
|
+
request_id: 'rev-order-123', # required, max 64
|
|
429
|
+
funds_split_id: split.id, # required
|
|
430
|
+
amount: '5.00', # required, string
|
|
431
|
+
metadata: { reason: 'refund' } # optional
|
|
413
432
|
)
|
|
414
|
-
|
|
433
|
+
Airwallex::FundsSplitReversal.retrieve(reversal.id)
|
|
434
|
+
Airwallex::FundsSplitReversal.list(funds_split_id: split.id) # funds_split_id is required
|
|
435
|
+
|
|
436
|
+
# Move funds between the platform and a connected account's wallet. Pass
|
|
437
|
+
# x-on-behalf-of to debit a connected account instead of the platform.
|
|
438
|
+
# Statuses: NEW, PENDING, SETTLED, SUSPENDED, FAILED
|
|
439
|
+
cat = Airwallex::ConnectedAccountTransfer.create(
|
|
440
|
+
request_id: 'top-up-123', # required, 1-50
|
|
441
|
+
amount: '100.00',
|
|
442
|
+
currency: 'AUD',
|
|
443
|
+
destination: connected_account.id,
|
|
444
|
+
reason: 'transfer_to_own_account',
|
|
445
|
+
reference: 'Ledger top-up' # required, 1-140
|
|
446
|
+
)
|
|
447
|
+
Airwallex::ConnectedAccountTransfer.retrieve(cat.id)
|
|
448
|
+
Airwallex::ConnectedAccountTransfer.list(destination: connected_account.id, status: 'SETTLED')
|
|
449
|
+
|
|
450
|
+
# Pay out from a connected account: create the Transfer on its behalf
|
|
451
|
+
Airwallex::Transfer.create(transfer_params, headers: { 'x-on-behalf-of' => connected_account.id })
|
|
415
452
|
|
|
416
453
|
# Charges (create, retrieve, list)
|
|
417
454
|
charge = Airwallex::Charge.retrieve('charge_id')
|
|
418
455
|
```
|
|
419
456
|
|
|
457
|
+
`funds_split.*` webhook payloads name the split `split_id`, not `id`, and carry
|
|
458
|
+
no `request_id`: `{ split_id, status, amount, currency, source_id, source_type, destination, ... }`.
|
|
459
|
+
|
|
460
|
+
### Requests for Information (RFIs)
|
|
461
|
+
|
|
462
|
+
RFIs are compliance questions Airwallex raises against an account. They use
|
|
463
|
+
bookmark pagination (`page_after`/`page`), which `ListObject` handles for you,
|
|
464
|
+
and connected accounts are addressed with `x-on-behalf-of`, which is kept
|
|
465
|
+
across pages. The API key needs the **Risk → Request for Information (RFI):
|
|
466
|
+
Read** permission, or Airwallex responds with 401 (`Airwallex::AuthenticationError`).
|
|
467
|
+
|
|
468
|
+
```ruby
|
|
469
|
+
headers = { 'x-on-behalf-of' => 'acct_123' }
|
|
470
|
+
|
|
471
|
+
rfi = Airwallex::RFI.retrieve('rfi_123', headers: headers)
|
|
472
|
+
rfi.status # => "ACTION_REQUIRED"
|
|
473
|
+
rfi.active_request[:questions].first # => { id: ..., title: { en: ... }, answer: { type: "ATTACHMENT" }, ... }
|
|
474
|
+
|
|
475
|
+
# statuses/types are comma-separated strings; page_size is 1-2000 (default 100)
|
|
476
|
+
Airwallex::RFI.list({ statuses: 'ACTION_REQUIRED', types: 'KYC,KYC_ONGOING' }, headers: headers)
|
|
477
|
+
.auto_paging_each { |r| puts r.id }
|
|
478
|
+
```
|
|
479
|
+
|
|
420
480
|
### Sandbox Simulations
|
|
421
481
|
|
|
422
482
|
Sandbox-only endpoints (`/api/v1/simulation/...`) that stand in for the bank,
|
|
@@ -534,8 +594,18 @@ transfer.simulate_transition(next_status: 'FAILED', failure_type: 'INSUFFICIENT_
|
|
|
534
594
|
|
|
535
595
|
# Class-level equivalent takes a transfer_id directly instead of an instance
|
|
536
596
|
Airwallex::Transfer.simulate_transition('tfr_123', next_status: 'CANCELLED')
|
|
597
|
+
|
|
598
|
+
# A transfer created x-on-behalf-of a connected account can only be found
|
|
599
|
+
# with the same header (cancel takes it too)
|
|
600
|
+
headers = { 'x-on-behalf-of' => 'acct_123' }
|
|
601
|
+
transfer.simulate_transition(next_status: 'SENT', headers: headers)
|
|
602
|
+
Airwallex::Transfer.simulate_transition('tfr_123', next_status: 'PAID', headers: headers)
|
|
603
|
+
transfer.cancel(headers: headers)
|
|
537
604
|
```
|
|
538
605
|
|
|
606
|
+
The sandbox rejects PROCESSING -> PAID with a 500 `operation_failed`. Move the
|
|
607
|
+
transfer to SENT first.
|
|
608
|
+
|
|
539
609
|
#### Connected Accounts & Offboarding
|
|
540
610
|
|
|
541
611
|
```ruby
|
|
@@ -621,6 +691,14 @@ rfi.simulate_follow_up(questions: [{ answer: { type: 'TEXT' } }])
|
|
|
621
691
|
# Class-level equivalents take an rfi_id directly instead of an instance
|
|
622
692
|
Airwallex::RFI.simulate_close('rfi_123')
|
|
623
693
|
Airwallex::RFI.simulate_follow_up('rfi_123', questions: [{ answer: { type: 'TEXT' } }])
|
|
694
|
+
|
|
695
|
+
# Raise an RFI against a connected account — params need braces when
|
|
696
|
+
# followed by opts. Fails with BadRequestError ("ongoing RFI case exists")
|
|
697
|
+
# if the account already has an open RFI.
|
|
698
|
+
Airwallex::RFI.simulate_create(
|
|
699
|
+
{ type: 'KYC', questions: [{ answer: { type: 'TEXT' } }] },
|
|
700
|
+
headers: { 'x-on-behalf-of' => 'acct_123' }
|
|
701
|
+
)
|
|
624
702
|
```
|
|
625
703
|
|
|
626
704
|
#### POS Terminals
|
|
@@ -679,7 +757,8 @@ transfer = Airwallex::Transfer.create(
|
|
|
679
757
|
|
|
680
758
|
### Pagination
|
|
681
759
|
|
|
682
|
-
Unified interface across
|
|
760
|
+
Unified interface across cursor-based, offset-based, and bookmark-based (RFI) endpoints.
|
|
761
|
+
Any `headers:` passed to `.list` (e.g. `x-on-behalf-of`) are kept on every page:
|
|
683
762
|
|
|
684
763
|
```ruby
|
|
685
764
|
# Auto-pagination with enumerable
|
|
@@ -716,6 +795,10 @@ begin
|
|
|
716
795
|
handle_successful_payment(event.data)
|
|
717
796
|
when 'payout.transfer.failed'
|
|
718
797
|
handle_failed_payout(event.data)
|
|
798
|
+
when 'funds_split.settled'
|
|
799
|
+
# funds_split.* payloads identify the split by split_id (not id) and
|
|
800
|
+
# carry no request_id — see the Scale section
|
|
801
|
+
handle_settled_split(event.data)
|
|
719
802
|
end
|
|
720
803
|
rescue Airwallex::SignatureVerificationError => e
|
|
721
804
|
# Invalid signature
|
|
@@ -819,7 +902,7 @@ end
|
|
|
819
902
|
- Customer (create, retrieve, list, update, delete)
|
|
820
903
|
- Dispute (retrieve, list, accept, challenge, related_payment_intents, simulate_create, simulate_escalate, simulate_resolve)
|
|
821
904
|
- **Payouts**:
|
|
822
|
-
- Transfer (create, retrieve, list, cancel, simulate_transition)
|
|
905
|
+
- Transfer (create, retrieve, list, cancel, simulate_transition; cancel/simulate_transition take headers: for x-on-behalf-of)
|
|
823
906
|
- Beneficiary (create, retrieve, list, update, delete, validate, verify_account, api_schema, form_schema, supported_financial_institutions)
|
|
824
907
|
- BatchTransfer (create, retrieve, list)
|
|
825
908
|
- **Foreign Exchange & Multi-Currency**:
|
|
@@ -846,14 +929,16 @@ end
|
|
|
846
929
|
- AccountAmendment (create, retrieve, simulate_approve, simulate_reject) - requires Admin-level API key permissions
|
|
847
930
|
- AccountOffboarding — sandbox Simulation only, scoped to a parent ConnectedAccount (simulate_complete, simulate_cancel)
|
|
848
931
|
- FundsSplit (create, retrieve, list, release)
|
|
932
|
+
- FundsSplitReversal (create, retrieve, list)
|
|
933
|
+
- ConnectedAccountTransfer (create, retrieve, list)
|
|
849
934
|
- Charge (create, retrieve, list)
|
|
850
935
|
- **Issuing** (sandbox Simulation only — no live Card/Cardholder resources yet):
|
|
851
936
|
- IssuingTransaction (simulate_create with single_phase, simulate_capture, simulate_reverse, simulate_refund, simulate_notify_three_ds)
|
|
852
937
|
- Cardholder (simulate_pass_review)
|
|
853
938
|
- **In-Person Payments** (sandbox Simulation only):
|
|
854
939
|
- POSTerminal (simulate_turn_on, simulate_turn_off, simulate_generate_activation_code, simulate_confirm_payment_intent, simulate_payment_scenarios)
|
|
855
|
-
- **Compliance
|
|
856
|
-
- RFI (simulate_create, simulate_close, simulate_follow_up)
|
|
940
|
+
- **Compliance**:
|
|
941
|
+
- RFI (retrieve, list, plus sandbox simulate_create, simulate_close, simulate_follow_up)
|
|
857
942
|
- **Webhooks**: Event handling, HMAC-SHA256 signature verification
|
|
858
943
|
|
|
859
944
|
### Coming in Future Versions
|
|
@@ -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
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents a Connected Account Transfer — moves funds between the platform
|
|
5
|
+
# account and a connected account's wallet. Send x-on-behalf-of to debit a
|
|
6
|
+
# connected account instead of the platform.
|
|
7
|
+
#
|
|
8
|
+
# Statuses: NEW, PENDING, SETTLED, SUSPENDED, FAILED. Create errors include
|
|
9
|
+
# insufficient_fund and request_id_duplicate.
|
|
10
|
+
#
|
|
11
|
+
# @example Fund a connected account from the platform
|
|
12
|
+
# transfer = Airwallex::ConnectedAccountTransfer.create(
|
|
13
|
+
# request_id: "cat-req-123", # required, 1-50
|
|
14
|
+
# amount: "100.00",
|
|
15
|
+
# currency: "AUD",
|
|
16
|
+
# destination: "acct_123", # connected account id
|
|
17
|
+
# reason: "transfer_to_own_account",
|
|
18
|
+
# reference: "Ledger top-up" # required, 1-140
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# @example List settled transfers to an account
|
|
22
|
+
# Airwallex::ConnectedAccountTransfer.list(destination: "acct_123", status: "SETTLED")
|
|
23
|
+
class ConnectedAccountTransfer < APIResource
|
|
24
|
+
extend APIOperations::Create
|
|
25
|
+
extend APIOperations::Retrieve
|
|
26
|
+
extend APIOperations::List
|
|
27
|
+
|
|
28
|
+
# @return [String] API resource path for connected account transfers
|
|
29
|
+
def self.resource_path
|
|
30
|
+
"/api/v1/connected_account_transfers"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Airwallex
|
|
4
|
-
# Represents a Funds Split —
|
|
5
|
-
#
|
|
6
|
-
#
|
|
4
|
+
# Represents a Funds Split — moves part of a PaymentIntent's funds from the
|
|
5
|
+
# platform account to a Connected Account. Each split has exactly one
|
|
6
|
+
# destination; create one split per connected account.
|
|
7
|
+
#
|
|
8
|
+
# Create errors include duplicate_request (reused request_id) and
|
|
9
|
+
# amount_above_limit (splits exceed the PaymentIntent amount).
|
|
10
|
+
#
|
|
11
|
+
# funds_split.* webhook payloads name the split split_id, not id, and carry
|
|
12
|
+
# no request_id: { split_id:, status:, amount:, currency:, source_id:,
|
|
13
|
+
# source_type:, destination:, ... }.
|
|
7
14
|
#
|
|
8
15
|
# @example Create a split
|
|
9
16
|
# split = Airwallex::FundsSplit.create(
|
|
10
|
-
#
|
|
11
|
-
#
|
|
17
|
+
# request_id: "split-req-123", # required, max 64
|
|
18
|
+
# source_id: intent.id, # required, PaymentIntent id
|
|
19
|
+
# source_type: "PAYMENT_INTENT", # required
|
|
20
|
+
# amount: "50.00", # required, string
|
|
21
|
+
# destination: connected_account.id,
|
|
22
|
+
# auto_release: true, # optional, default true
|
|
23
|
+
# metadata: { order_id: "123" } # optional
|
|
12
24
|
# )
|
|
13
25
|
#
|
|
14
|
-
# @example Release the split funds
|
|
26
|
+
# @example Release the split funds (when created with auto_release: false)
|
|
15
27
|
# split.release
|
|
28
|
+
#
|
|
29
|
+
# @see FundsSplitReversal to move split funds back to the platform
|
|
16
30
|
class FundsSplit < APIResource
|
|
17
31
|
extend APIOperations::Create
|
|
18
32
|
extend APIOperations::Retrieve
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents a Funds Split Reversal — moves previously split funds back from
|
|
5
|
+
# the connected account (the split's destination) to the platform account.
|
|
6
|
+
# Partial reversals are allowed until the full split amount is reversed.
|
|
7
|
+
#
|
|
8
|
+
# Statuses: CREATED, RELEASED, SETTLED.
|
|
9
|
+
#
|
|
10
|
+
# @example Reverse part of a split
|
|
11
|
+
# reversal = Airwallex::FundsSplitReversal.create(
|
|
12
|
+
# request_id: "rev-req-123", # required, max 64
|
|
13
|
+
# funds_split_id: split.id, # required
|
|
14
|
+
# amount: "5.00", # required, string
|
|
15
|
+
# metadata: { reason: "refund" }
|
|
16
|
+
# )
|
|
17
|
+
# reversal.status # => "CREATED"
|
|
18
|
+
#
|
|
19
|
+
# @example List the reversals of a split
|
|
20
|
+
# Airwallex::FundsSplitReversal.list(funds_split_id: split.id)
|
|
21
|
+
class FundsSplitReversal < APIResource
|
|
22
|
+
extend APIOperations::Create
|
|
23
|
+
extend APIOperations::Retrieve
|
|
24
|
+
extend APIOperations::List
|
|
25
|
+
|
|
26
|
+
# @return [String] API resource path for funds split reversals
|
|
27
|
+
def self.resource_path
|
|
28
|
+
"/api/v1/pa/funds_split_reversals"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -5,11 +5,31 @@ module Airwallex
|
|
|
5
5
|
# Airwallex raises against your account (KYC, an ongoing KYC review, a
|
|
6
6
|
# cardholder, a transaction, payment enablement, or merchant risk).
|
|
7
7
|
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
8
|
+
# Supports retrieving and listing real RFIs, plus the sandbox Simulation
|
|
9
|
+
# endpoints for raising, closing and following up on them (there is no
|
|
10
|
+
# live create — real RFIs originate from Airwallex's own compliance
|
|
11
|
+
# review, same as Dispute). See
|
|
12
|
+
# https://www.airwallex.com/docs/api/risk/request_for_information_rfi and
|
|
11
13
|
# https://www.airwallex.com/docs/api/simulation/request-for-information
|
|
12
14
|
#
|
|
15
|
+
# Statuses: ACTION_REQUIRED, ANSWERED, CLOSED. Types: KYC, KYC_ONGOING,
|
|
16
|
+
# CARDHOLDER, TRANSACTION, PAYMENT_ENABLEMENT, MERCHANT_RISK.
|
|
17
|
+
#
|
|
18
|
+
# Reading RFIs requires the API key's "Risk → Request for Information
|
|
19
|
+
# (RFI): Read" permission — without it Airwallex returns 401
|
|
20
|
+
# (AuthenticationError).
|
|
21
|
+
#
|
|
22
|
+
# @example Retrieve an RFI for a connected account
|
|
23
|
+
# rfi = Airwallex::RFI.retrieve("rfi_123", headers: { "x-on-behalf-of" => "acct_123" })
|
|
24
|
+
# rfi.status # => "ACTION_REQUIRED"
|
|
25
|
+
# rfi.active_request[:questions].first # => { id: ..., title: { en: ... }, ... }
|
|
26
|
+
#
|
|
27
|
+
# @example List open RFIs, following every page
|
|
28
|
+
# Airwallex::RFI.list(
|
|
29
|
+
# { statuses: "ACTION_REQUIRED" },
|
|
30
|
+
# headers: { "x-on-behalf-of" => "acct_123" }
|
|
31
|
+
# ).auto_paging_each { |rfi| puts rfi.id }
|
|
32
|
+
#
|
|
13
33
|
# @example Raise a KYC RFI, then close it
|
|
14
34
|
# rfi = Airwallex::RFI.simulate_create(
|
|
15
35
|
# type: "KYC",
|
|
@@ -17,9 +37,42 @@ module Airwallex
|
|
|
17
37
|
# )
|
|
18
38
|
# rfi.simulate_close
|
|
19
39
|
class RFI < APIResource
|
|
20
|
-
|
|
40
|
+
extend APIOperations::Retrieve
|
|
41
|
+
extend APIOperations::List
|
|
42
|
+
|
|
43
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/request-for-information
|
|
44
|
+
SIMULATION_PATH = "/api/v1/simulation/rfis"
|
|
45
|
+
|
|
46
|
+
# @return [String] API resource path for RFIs
|
|
21
47
|
def self.resource_path
|
|
22
|
-
"/api/v1/
|
|
48
|
+
"/api/v1/rfis"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# List RFIs. Unlike most resources, RFIs use bookmark pagination: the
|
|
52
|
+
# response carries page_after/page_before instead of has_more/next_cursor,
|
|
53
|
+
# and the next page is requested with page: <page_after>.
|
|
54
|
+
#
|
|
55
|
+
# @param params [Hash] page: (bookmark), page_size: (1-2000, default
|
|
56
|
+
# 100), statuses: / types: (comma-separated strings, e.g.
|
|
57
|
+
# "ACTION_REQUIRED,ANSWERED"), from_created_at: / end_created_at:
|
|
58
|
+
# (ISO8601)
|
|
59
|
+
# @param opts [Hash] headers: (e.g. { "x-on-behalf-of" => account_id }),
|
|
60
|
+
# kept on every subsequent page
|
|
61
|
+
# @return [ListObject<RFI>]
|
|
62
|
+
def self.list(params = {}, opts = {})
|
|
63
|
+
response = Airwallex.client.get(resource_path, params, opts[:headers] || {})
|
|
64
|
+
items = extract_data(response)
|
|
65
|
+
page_after = response.is_a?(Hash) ? response[:page_after] || response["page_after"] : nil
|
|
66
|
+
|
|
67
|
+
ListObject.new(
|
|
68
|
+
data: items,
|
|
69
|
+
has_more: !page_after.nil? && !items.empty?,
|
|
70
|
+
next_cursor: page_after,
|
|
71
|
+
cursor_param: :page,
|
|
72
|
+
resource_class: self,
|
|
73
|
+
params: params,
|
|
74
|
+
opts: opts
|
|
75
|
+
)
|
|
23
76
|
end
|
|
24
77
|
|
|
25
78
|
# Simulate Airwallex raising an RFI
|
|
@@ -28,18 +81,20 @@ module Airwallex
|
|
|
28
81
|
# "CARDHOLDER", "TRANSACTION", "PAYMENT_ENABLEMENT",
|
|
29
82
|
# "MERCHANT_RISK"); questions: (required, array of
|
|
30
83
|
# { answer: { type: }, sources: [] })
|
|
84
|
+
# @param opts [Hash] headers: (e.g. { "x-on-behalf-of" => account_id })
|
|
31
85
|
# @return [RFI]
|
|
32
|
-
def self.simulate_create(params = {})
|
|
33
|
-
response = Airwallex.client.post("#{
|
|
86
|
+
def self.simulate_create(params = {}, opts = {})
|
|
87
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/create", params, opts[:headers] || {})
|
|
34
88
|
new(response)
|
|
35
89
|
end
|
|
36
90
|
|
|
37
91
|
# Simulate closing an RFI
|
|
38
92
|
#
|
|
39
93
|
# @param rfi_id [String]
|
|
94
|
+
# @param opts [Hash] headers: (e.g. { "x-on-behalf-of" => account_id })
|
|
40
95
|
# @return [RFI]
|
|
41
|
-
def self.simulate_close(rfi_id)
|
|
42
|
-
response = Airwallex.client.post("#{
|
|
96
|
+
def self.simulate_close(rfi_id, opts = {})
|
|
97
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{rfi_id}/close", {}, opts[:headers] || {})
|
|
43
98
|
new(response)
|
|
44
99
|
end
|
|
45
100
|
|
|
@@ -49,17 +104,19 @@ module Airwallex
|
|
|
49
104
|
# @param rfi_id [String]
|
|
50
105
|
# @param params [Hash] questions: (required, array of
|
|
51
106
|
# { id: } or { answer: { type: }, sources: [] })
|
|
107
|
+
# @param opts [Hash] headers: (e.g. { "x-on-behalf-of" => account_id })
|
|
52
108
|
# @return [RFI]
|
|
53
|
-
def self.simulate_follow_up(rfi_id, params = {})
|
|
54
|
-
response = Airwallex.client.post("#{
|
|
109
|
+
def self.simulate_follow_up(rfi_id, params = {}, opts = {})
|
|
110
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{rfi_id}/follow_up", params, opts[:headers] || {})
|
|
55
111
|
new(response)
|
|
56
112
|
end
|
|
57
113
|
|
|
58
114
|
# Simulate closing this RFI
|
|
59
115
|
#
|
|
116
|
+
# @param opts [Hash] headers: (e.g. { "x-on-behalf-of" => account_id })
|
|
60
117
|
# @return [RFI] self
|
|
61
|
-
def simulate_close
|
|
62
|
-
response = Airwallex.client.post("#{
|
|
118
|
+
def simulate_close(opts = {})
|
|
119
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{id}/close", {}, opts[:headers] || {})
|
|
63
120
|
refresh_from(response)
|
|
64
121
|
self
|
|
65
122
|
end
|
|
@@ -67,9 +124,10 @@ module Airwallex
|
|
|
67
124
|
# Simulate a follow-up on this RFI
|
|
68
125
|
#
|
|
69
126
|
# @param params [Hash] questions: (required)
|
|
127
|
+
# @param opts [Hash] headers: (e.g. { "x-on-behalf-of" => account_id })
|
|
70
128
|
# @return [RFI] self
|
|
71
|
-
def simulate_follow_up(params = {})
|
|
72
|
-
response = Airwallex.client.post("#{
|
|
129
|
+
def simulate_follow_up(params = {}, opts = {})
|
|
130
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{id}/follow_up", params, opts[:headers] || {})
|
|
73
131
|
refresh_from(response)
|
|
74
132
|
self
|
|
75
133
|
end
|
|
@@ -14,10 +14,14 @@ module Airwallex
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
# Cancel a pending transfer
|
|
17
|
-
|
|
17
|
+
#
|
|
18
|
+
# @param opts [Hash] headers: (e.g. { "x-on-behalf-of" => account_id })
|
|
19
|
+
# @return [Transfer] self
|
|
20
|
+
def cancel(opts = {})
|
|
18
21
|
response = Airwallex.client.post(
|
|
19
22
|
"#{self.class.resource_path}/#{id}/cancel",
|
|
20
|
-
{}
|
|
23
|
+
{},
|
|
24
|
+
opts[:headers] || {}
|
|
21
25
|
)
|
|
22
26
|
refresh_from(response)
|
|
23
27
|
self
|
|
@@ -25,18 +29,21 @@ module Airwallex
|
|
|
25
29
|
|
|
26
30
|
# Simulate this transfer's status advancing one step. The standard
|
|
27
31
|
# lifecycle is SCHEDULED -> PROCESSING -> SENT -> PAID, progressed one
|
|
28
|
-
# step at a time
|
|
29
|
-
#
|
|
32
|
+
# step at a time (the sandbox rejects PROCESSING -> PAID with a 500
|
|
33
|
+
# operation_failed; go through SENT); OVERDUE, FAILED, and CANCELLED can
|
|
34
|
+
# be jumped to directly.
|
|
30
35
|
#
|
|
31
36
|
# @param next_status [String] one of "OVERDUE", "PROCESSING", "SENT",
|
|
32
37
|
# "PAID", "FAILED", "CANCELLED"
|
|
33
38
|
# @param failure_type [String, nil] failure reason code, only
|
|
34
39
|
# meaningful when next_status is "FAILED"
|
|
40
|
+
# @param headers [Hash] e.g. { "x-on-behalf-of" => account_id } — required
|
|
41
|
+
# for a transfer created on behalf of a connected account
|
|
35
42
|
# @return [Transfer] self
|
|
36
|
-
def simulate_transition(next_status:, failure_type: nil)
|
|
43
|
+
def simulate_transition(next_status:, failure_type: nil, headers: {})
|
|
37
44
|
params = { next_status: next_status }
|
|
38
45
|
params[:failure_type] = failure_type if failure_type
|
|
39
|
-
response = Airwallex.client.post("#{SIMULATION_PATH}/#{id}/transition", params)
|
|
46
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{id}/transition", params, headers)
|
|
40
47
|
refresh_from(response)
|
|
41
48
|
self
|
|
42
49
|
end
|
|
@@ -48,11 +55,13 @@ module Airwallex
|
|
|
48
55
|
# "PAID", "FAILED", "CANCELLED"
|
|
49
56
|
# @param failure_type [String, nil] failure reason code, only
|
|
50
57
|
# meaningful when next_status is "FAILED"
|
|
58
|
+
# @param headers [Hash] e.g. { "x-on-behalf-of" => account_id } — required
|
|
59
|
+
# for a transfer created on behalf of a connected account
|
|
51
60
|
# @return [Transfer]
|
|
52
|
-
def self.simulate_transition(transfer_id, next_status:, failure_type: nil)
|
|
61
|
+
def self.simulate_transition(transfer_id, next_status:, failure_type: nil, headers: {})
|
|
53
62
|
params = { next_status: next_status }
|
|
54
63
|
params[:failure_type] = failure_type if failure_type
|
|
55
|
-
response = Airwallex.client.post("#{SIMULATION_PATH}/#{transfer_id}/transition", params)
|
|
64
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{transfer_id}/transition", params, headers)
|
|
56
65
|
new(response)
|
|
57
66
|
end
|
|
58
67
|
end
|
data/lib/airwallex/version.rb
CHANGED
data/lib/airwallex.rb
CHANGED
|
@@ -47,6 +47,8 @@ require_relative "airwallex/resources/payment_source"
|
|
|
47
47
|
require_relative "airwallex/resources/connected_account"
|
|
48
48
|
require_relative "airwallex/resources/account_amendment"
|
|
49
49
|
require_relative "airwallex/resources/funds_split"
|
|
50
|
+
require_relative "airwallex/resources/funds_split_reversal"
|
|
51
|
+
require_relative "airwallex/resources/connected_account_transfer"
|
|
50
52
|
require_relative "airwallex/resources/charge"
|
|
51
53
|
require_relative "airwallex/resources/deposit"
|
|
52
54
|
require_relative "airwallex/resources/issuing_transaction"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: airwallex
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chayut Orapinpatipat
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -93,11 +93,13 @@ files:
|
|
|
93
93
|
- lib/airwallex/resources/cardholder.rb
|
|
94
94
|
- lib/airwallex/resources/charge.rb
|
|
95
95
|
- lib/airwallex/resources/connected_account.rb
|
|
96
|
+
- lib/airwallex/resources/connected_account_transfer.rb
|
|
96
97
|
- lib/airwallex/resources/conversion.rb
|
|
97
98
|
- lib/airwallex/resources/customer.rb
|
|
98
99
|
- lib/airwallex/resources/deposit.rb
|
|
99
100
|
- lib/airwallex/resources/dispute.rb
|
|
100
101
|
- lib/airwallex/resources/funds_split.rb
|
|
102
|
+
- lib/airwallex/resources/funds_split_reversal.rb
|
|
101
103
|
- lib/airwallex/resources/global_account.rb
|
|
102
104
|
- lib/airwallex/resources/global_account_alias.rb
|
|
103
105
|
- lib/airwallex/resources/global_account_mandate.rb
|
|
@@ -144,7 +146,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
144
146
|
- !ruby/object:Gem::Version
|
|
145
147
|
version: '0'
|
|
146
148
|
requirements: []
|
|
147
|
-
rubygems_version: 3.
|
|
149
|
+
rubygems_version: 3.5.22
|
|
148
150
|
signing_key:
|
|
149
151
|
specification_version: 4
|
|
150
152
|
summary: Production-grade Ruby client for the Airwallex API
|