airwallex 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0fc42ac04f0af0416fc2fe200f747a17ec326ea5b071a40d5965043da1ebfd7c
4
- data.tar.gz: bfc8c907e1ce9d0e702e124bdf0c85f1fb8827f7c90b0b4382665b5a4f1ade00
3
+ metadata.gz: '018417ed2b3091ca1c5eafdf99df79c1a81be1d13ddda2524a917d2bc9a2c3ff'
4
+ data.tar.gz: 0abdb483ebe91136353cc77efb5023cb1d0a05b3fd986c8c3ba59750dfeccfdd
5
5
  SHA512:
6
- metadata.gz: 952b7120270923df264d701e631570a673584fd08f73502dc3ec36ff19bdd47577d6d799928cdae40781813cbd5f881de7ce4dcca3488e10ee5ff4d46f67da76
7
- data.tar.gz: 440bfe68c53e17f2a5b30856f6de51257fffdc32c38e459ebe82d8604c9b018d58ee6369249bc2e2907701f0c79f43bdd9005c08eaaf19aa9ceb81486a2e9999
6
+ metadata.gz: 1b48a815ca9ca948b4695cfa28b12e70c5dc21a7c6ff37781d20fe0b2f219b12daf205ec997eaaf51eca19bce49d3ffd0fbb890773cd99f5573c4733680e1ec6
7
+ data.tar.gz: ff66015473fee32cd8ec4bd7cdbb03072b340111d1c354db181c07e0722c936d7b14565210eabd6583f8cd223be9f4c96889b1e329a79b41709ed4626b474427
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
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
+
3
23
  ## [0.9.0] - 2026-09-09
4
24
 
5
25
  ### 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
@@ -417,6 +418,26 @@ split.release
417
418
  charge = Airwallex::Charge.retrieve('charge_id')
418
419
  ```
419
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
+
420
441
  ### Sandbox Simulations
421
442
 
422
443
  Sandbox-only endpoints (`/api/v1/simulation/...`) that stand in for the bank,
@@ -621,6 +642,14 @@ rfi.simulate_follow_up(questions: [{ answer: { type: 'TEXT' } }])
621
642
  # Class-level equivalents take an rfi_id directly instead of an instance
622
643
  Airwallex::RFI.simulate_close('rfi_123')
623
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
+ )
624
653
  ```
625
654
 
626
655
  #### POS Terminals
@@ -679,7 +708,8 @@ transfer = Airwallex::Transfer.create(
679
708
 
680
709
  ### Pagination
681
710
 
682
- Unified interface across both cursor-based and offset-based endpoints:
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:
683
713
 
684
714
  ```ruby
685
715
  # Auto-pagination with enumerable
@@ -852,8 +882,8 @@ end
852
882
  - Cardholder (simulate_pass_review)
853
883
  - **In-Person Payments** (sandbox Simulation only):
854
884
  - 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)
885
+ - **Compliance**:
886
+ - RFI (retrieve, list, plus sandbox simulate_create, simulate_close, simulate_follow_up)
857
887
  - **Webhooks**: Event handling, HMAC-SHA256 signature verification
858
888
 
859
889
  ### Coming in Future Versions
@@ -6,12 +6,19 @@ module Airwallex
6
6
 
7
7
  attr_reader :data, :has_more, :next_cursor
8
8
 
9
- def initialize(data:, has_more:, resource_class:, next_cursor: nil, params: {})
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[:next_cursor] = @next_cursor
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
@@ -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
- # This gem currently only implements the sandbox Simulation endpoints
9
- # (there is no live create — real RFIs originate from Airwallex's own
10
- # compliance review, same as Dispute). See
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
- # @return [String] API resource path for simulated RFI actions
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/simulation/rfis"
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("#{resource_path}/create", params)
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("#{resource_path}/#{rfi_id}/close", {})
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("#{resource_path}/#{rfi_id}/follow_up", params)
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("#{self.class.resource_path}/#{id}/close", {})
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("#{self.class.resource_path}/#{id}/follow_up", params)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Airwallex
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0"
5
5
  end
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.9.0
4
+ version: 0.10.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-09 00:00:00.000000000 Z
11
+ date: 2026-09-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -144,7 +144,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
144
  - !ruby/object:Gem::Version
145
145
  version: '0'
146
146
  requirements: []
147
- rubygems_version: 3.4.10
147
+ rubygems_version: 3.5.16
148
148
  signing_key:
149
149
  specification_version: 4
150
150
  summary: Production-grade Ruby client for the Airwallex API