airwallex 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '018417ed2b3091ca1c5eafdf99df79c1a81be1d13ddda2524a917d2bc9a2c3ff'
4
- data.tar.gz: 0abdb483ebe91136353cc77efb5023cb1d0a05b3fd986c8c3ba59750dfeccfdd
3
+ metadata.gz: f1288d75bff8a6f00a8c0dfffc318b6df1821cb26c945e062930de6035e83822
4
+ data.tar.gz: 5452a4a8aed172764129f05043646e9f074c1c9e62df84246ffe6ff575ba0140
5
5
  SHA512:
6
- metadata.gz: 1b48a815ca9ca948b4695cfa28b12e70c5dc21a7c6ff37781d20fe0b2f219b12daf205ec997eaaf51eca19bce49d3ffd0fbb890773cd99f5573c4733680e1ec6
7
- data.tar.gz: ff66015473fee32cd8ec4bd7cdbb03072b340111d1c354db181c07e0722c936d7b14565210eabd6583f8cd223be9f4c96889b1e329a79b41709ed4626b474427
6
+ metadata.gz: 02e3eea25661113f96af469d3154ede00aae144c671c42fcdf48d2e08d900c74ff2291b9b901ebc2b2072688b840d07d58f6b7eb21bd14aaf2c75d2e4625e6f2
7
+ data.tar.gz: 31bb06a6c4a99f4d433c63b5eb9e209e31deeec63dd3d98a516d259801530e874b66b70d4680d386eba0e569589a55e51c59640af3907fbade0e1da9171b6698
data/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
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
+
3
29
  ## [0.10.0] - 2026-09-29
4
30
 
5
31
  ### Added
data/README.md CHANGED
@@ -375,7 +375,7 @@ payment_source = Airwallex::PaymentSource.create(
375
375
  )
376
376
  ```
377
377
 
378
- ### Scale (Connected Accounts, Funds Splits, Charges)
378
+ ### Scale (Connected Accounts, Funds Splits, Reversals, Connected Account Transfers, Charges)
379
379
 
380
380
  For platforms onboarding sub-merchants:
381
381
 
@@ -407,17 +407,56 @@ connected_account.submit
407
407
  Airwallex::ConnectedAccount.current
408
408
  Airwallex::ConnectedAccount.wallet_info
409
409
 
410
- # Split a charge's funds between the platform and a connected account
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.
411
414
  split = Airwallex::FundsSplit.create(
412
- payment_intent_id: payment_intent.id,
413
- splits: [{ account_id: connected_account.id, amount: 10.00 }]
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
414
432
  )
415
- split.release
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 })
416
452
 
417
453
  # Charges (create, retrieve, list)
418
454
  charge = Airwallex::Charge.retrieve('charge_id')
419
455
  ```
420
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
+
421
460
  ### Requests for Information (RFIs)
422
461
 
423
462
  RFIs are compliance questions Airwallex raises against an account. They use
@@ -555,8 +594,18 @@ transfer.simulate_transition(next_status: 'FAILED', failure_type: 'INSUFFICIENT_
555
594
 
556
595
  # Class-level equivalent takes a transfer_id directly instead of an instance
557
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)
558
604
  ```
559
605
 
606
+ The sandbox rejects PROCESSING -> PAID with a 500 `operation_failed`. Move the
607
+ transfer to SENT first.
608
+
560
609
  #### Connected Accounts & Offboarding
561
610
 
562
611
  ```ruby
@@ -746,6 +795,10 @@ begin
746
795
  handle_successful_payment(event.data)
747
796
  when 'payout.transfer.failed'
748
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)
749
802
  end
750
803
  rescue Airwallex::SignatureVerificationError => e
751
804
  # Invalid signature
@@ -849,7 +902,7 @@ end
849
902
  - Customer (create, retrieve, list, update, delete)
850
903
  - Dispute (retrieve, list, accept, challenge, related_payment_intents, simulate_create, simulate_escalate, simulate_resolve)
851
904
  - **Payouts**:
852
- - 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)
853
906
  - Beneficiary (create, retrieve, list, update, delete, validate, verify_account, api_schema, form_schema, supported_financial_institutions)
854
907
  - BatchTransfer (create, retrieve, list)
855
908
  - **Foreign Exchange & Multi-Currency**:
@@ -876,6 +929,8 @@ end
876
929
  - AccountAmendment (create, retrieve, simulate_approve, simulate_reject) - requires Admin-level API key permissions
877
930
  - AccountOffboarding — sandbox Simulation only, scoped to a parent ConnectedAccount (simulate_complete, simulate_cancel)
878
931
  - FundsSplit (create, retrieve, list, release)
932
+ - FundsSplitReversal (create, retrieve, list)
933
+ - ConnectedAccountTransfer (create, retrieve, list)
879
934
  - Charge (create, retrieve, list)
880
935
  - **Issuing** (sandbox Simulation only — no live Card/Cardholder resources yet):
881
936
  - IssuingTransaction (simulate_create with single_phase, simulate_capture, simulate_reverse, simulate_refund, simulate_notify_three_ds)
@@ -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 — how a single PaymentIntent/inbound transaction
5
- # is divided between the platform and a Connected Account at collection
6
- # time (the Scale-product mechanism for splitting deposits).
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
- # payment_intent_id: intent.id,
11
- # splits: [{ account_id: connected_account.id, amount: 50.00 }]
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
@@ -14,10 +14,14 @@ module Airwallex
14
14
  end
15
15
 
16
16
  # Cancel a pending transfer
17
- def cancel
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; OVERDUE, FAILED, and CANCELLED can be jumped to
29
- # directly.
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Airwallex
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.0"
5
5
  end
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.10.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-09-29 00:00:00.000000000 Z
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.5.16
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