airwallex 0.7.0 → 0.8.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: fad10ff0adfb96f6cb5e3a88ec1825a17b840d73658bd6fb6813f18e2746fea1
4
- data.tar.gz: 9f59e5dfc64382838c7b1485b4ca28b19e9b71248ea51070ff106ad33cb1af32
3
+ metadata.gz: be0adbfbafc982fa5c899a9df4230e9c65a68e46d2e7eb248b981e41ccc23bd4
4
+ data.tar.gz: 599bdc078288f934cc5f8b06fced1668605c8e2e6af4763d943a9f227a32e563
5
5
  SHA512:
6
- metadata.gz: de57501742d42048dd6a9286672c18050a17734ac2e21780f85e2d23e16e856bf406df20842352d55cd46aff22bfb0ade9676e1e6578865a7c966eba833880eb
7
- data.tar.gz: 809fce3bea1462cecb398388b34863f65edf4ef00a5742ce0c30776c0dcbf942b474daef0551e64cd81b79ddb8a44fb01ef210fd2cec8470dbe844ce4f5968c6
6
+ metadata.gz: 4cd20d177f642d64bdb07922f673483ab3d86499c0a938c8a7db7854f06fd5dff86b10326c09496dbf9a0bc8099a64834fc0ee4a56ff91d822dd721aff911e7a
7
+ data.tar.gz: 56b071c9ff52ccf48f6526c6f3a8264d55c1d8cfcc5794d869894535baf7685c7642bdebf498f5c4ed01e362631f62a9721d4151bc6f860cf91b574a2a2d54b7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.8.0] - 2026-09-02
4
+
5
+ ### Fixed
6
+ - `Util.deep_symbolize_keys` now recurses into `Array`s, matching `ActiveSupport#deep_transform_keys`'s
7
+ behavior. Previously any `Array` value was copied through untouched, so hashes nested inside an array
8
+ (e.g. `beneficiary_form_schemas`'s `fields` list) kept string keys even after "deep" symbolizing.
9
+ - `Beneficiary.validate`, `.verify_account`, `.api_schema`, `.form_schema`, and
10
+ `.supported_financial_institutions`, `ConnectedAccount.wallet_info`, `BillingCustomer#bank_transfer_instructions`,
11
+ and `GlobalAccount#generate_statement_letter` now return `Util.deep_symbolize_keys`-processed responses,
12
+ via two new shared helpers, `APIResource.symbolized_post`/`.symbolized_get`. Previously these methods
13
+ (unlike `.create`/`.retrieve`/`.update`/`.delete`/`.list`, which already funnel through `APIResource`'s
14
+ symbolization) returned the raw parsed JSON response with string keys, with nothing in the method
15
+ signature or naming to distinguish them from the symbolized methods on the same class.
16
+ - `Airwallex::Error#details` is now symbolized too (via the same `Util.deep_symbolize_keys` fix), instead
17
+ of holding the raw, string-keyed error body.
18
+
19
+ **Breaking change:** any code reading these nine methods'/`#details`' values with string keys
20
+ (`response["field"]`) must switch to symbol keys (`response[:field]`).
21
+
3
22
  ## [0.7.0] - 2026-08-28
4
23
 
5
24
  ### Added
data/README.md CHANGED
@@ -6,17 +6,22 @@ A Ruby client library for the [Airwallex API](https://www.airwallex.com/docs/api
6
6
 
7
7
  This gem provides a Ruby interface to Airwallex's payment infrastructure, designed for Ruby 3.1+ applications. It includes core functionality for authentication management, idempotency guarantees, webhook verification, and multi-environment support.
8
8
 
9
- **Current Features (v0.1.0):**
9
+ **Current Features:**
10
10
 
11
11
  - **Authentication**: Bearer token authentication with automatic refresh
12
- - **Payment Acceptance**: Payment intent creation, confirmation, and management
13
- - **Payouts**: Transfer creation and beneficiary management
12
+ - **Payment Acceptance**: Payment intents, refunds, payment methods, customers, disputes
13
+ - **Payouts**: Transfers, batch transfers, and beneficiary management
14
+ - **Foreign Exchange**: Real-time rates, locked quotes, and currency conversions
15
+ - **Global Accounts**: Virtual account numbers (VANs), inbound transaction reconciliation, aliases, and direct debit mandates
16
+ - **Billing**: Products, prices, billing customers, and subscriptions for recurring/instalment billing
17
+ - **Recurring Payments**: Payment consents and payment sources for merchant-initiated (off-session) charges
18
+ - **Scale**: Connected accounts, funds splits, and charges for platforms onboarding sub-merchants
14
19
  - **Idempotency**: Automatic request deduplication for safe retries
15
20
  - **Pagination**: Unified interface over cursor-based and offset-based pagination
16
21
  - **Webhook Security**: HMAC-SHA256 signature verification with replay protection
17
22
  - **Sandbox Support**: Full testing environment for development
18
23
 
19
- **Note:** This is an initial MVP release. Additional resources (FX, cards, refunds, etc.) will be added in future versions.
24
+ **Not yet implemented:** Card issuing.
20
25
 
21
26
  ## Installation
22
27
 
@@ -82,14 +87,30 @@ payment_intent.confirm(
82
87
  ```ruby
83
88
  # Create a beneficiary
84
89
  beneficiary = Airwallex::Beneficiary.create(
85
- bank_details: {
86
- account_number: '123456789',
87
- account_routing_type1: 'aba',
88
- account_routing_value1: '026009593',
89
- bank_country_code: 'US'
90
- },
91
- beneficiary_type: 'BUSINESS',
92
- company_name: 'Acme Corp'
90
+ nickname: 'Acme Corp',
91
+ payer_entity_type: 'COMPANY',
92
+ transfer_methods: ['LOCAL'],
93
+ beneficiary: {
94
+ entity_type: 'COMPANY',
95
+ company_name: 'Acme Corp',
96
+ address: {
97
+ country_code: 'AU',
98
+ city: 'Melbourne',
99
+ state: 'VIC',
100
+ postcode: '3000',
101
+ street_address: '15 William Street'
102
+ },
103
+ bank_details: {
104
+ account_name: 'Acme Corp',
105
+ account_number: '12750852',
106
+ account_currency: 'AUD',
107
+ bank_country_code: 'AU',
108
+ bank_name: 'National Australia Bank',
109
+ account_routing_type1: 'bsb',
110
+ account_routing_value1: '083064',
111
+ local_clearing_system: 'BANK_TRANSFER'
112
+ }
113
+ }
93
114
  )
94
115
 
95
116
  # Execute transfer
@@ -127,6 +148,7 @@ refunds = Airwallex::Refund.list(payment_intent_id: payment_intent.id)
127
148
  ```ruby
128
149
  # Create a customer
129
150
  customer = Airwallex::Customer.create(
151
+ merchant_customer_id: 'internal_customer_001',
130
152
  email: 'customer@example.com',
131
153
  first_name: 'John',
132
154
  last_name: 'Doe'
@@ -152,6 +174,9 @@ payment_intent.confirm(payment_method_id: payment_method.id)
152
174
 
153
175
  # List customer's payment methods
154
176
  methods = customer.payment_methods
177
+
178
+ # Disable a payment method (PaymentMethods have no delete/detach endpoint)
179
+ payment_method.disable
155
180
  ```
156
181
 
157
182
  ### Batch Transfers
@@ -190,8 +215,8 @@ puts "Dispute amount: #{dispute.amount} #{dispute.currency}"
190
215
  puts "Reason: #{dispute.reason}"
191
216
  puts "Evidence due: #{dispute.evidence_due_by}"
192
217
 
193
- # Submit evidence to challenge
194
- dispute.submit_evidence(
218
+ # Challenge the dispute with evidence
219
+ dispute.challenge(
195
220
  customer_communication: 'Email showing delivery confirmation',
196
221
  shipping_tracking_number: '1Z999AA10123456784',
197
222
  shipping_documentation: 'Proof of delivery with signature'
@@ -199,6 +224,9 @@ dispute.submit_evidence(
199
224
 
200
225
  # Or accept dispute without challenging
201
226
  dispute.accept
227
+
228
+ # List the payment intents related to a dispute
229
+ dispute.related_payment_intents
202
230
  ```
203
231
 
204
232
  ### Foreign Exchange & Multi-Currency
@@ -250,6 +278,144 @@ puts "USD Available: #{usd_balance.available_amount}"
250
278
  puts "USD Total: #{usd_balance.total_amount}"
251
279
  ```
252
280
 
281
+ ### Global Accounts (Virtual Account Numbers)
282
+
283
+ ```ruby
284
+ # Provision a VAN
285
+ account = Airwallex::GlobalAccount.create(
286
+ country_code: 'AU',
287
+ nick_name: 'booking_12345',
288
+ required_features: [{ transfer_method: 'LOCAL' }]
289
+ )
290
+
291
+ # List inbound transactions for reconciliation
292
+ account.transactions.each { |txn| puts "#{txn.amount} from #{txn.remitter}" }
293
+
294
+ # Add and verify an alias (e.g. a phone number or email VAN)
295
+ alias_record = account.create_alias(type: 'PAYID_PHONE', value: '+61400000000')
296
+ alias_record.submit_verification_code(code: '123456')
297
+ account.aliases
298
+
299
+ # Manage direct debit mandates
300
+ account.mandates
301
+ mandate = account.mandate('mandate_id')
302
+ mandate.cancel
303
+ ```
304
+
305
+ ### Billing & Subscriptions
306
+
307
+ ```ruby
308
+ # Create a billing customer
309
+ billing_customer = Airwallex::BillingCustomer.create(
310
+ name: 'Acme Corp',
311
+ email: 'billing@acme.com',
312
+ type: 'BUSINESS',
313
+ default_billing_currency: 'AUD',
314
+ default_legal_entity_id: connected_account.legal_entity_id,
315
+ address: {
316
+ street: '15 William Street',
317
+ city: 'Melbourne',
318
+ state: 'VIC',
319
+ postcode: '3000',
320
+ country_code: 'AU'
321
+ }
322
+ )
323
+
324
+ # Define a product and a recurring price
325
+ product = Airwallex::BillingProduct.create(name: 'Pro Plan')
326
+ price = Airwallex::BillingPrice.create(
327
+ product_id: product.id,
328
+ currency: 'AUD',
329
+ unit_amount: 99.00,
330
+ pricing_model: 'PER_UNIT',
331
+ recurring: { period: 1, period_unit: 'MONTH' }
332
+ )
333
+
334
+ # Subscriptions bill against a PaymentSource, not a PaymentConsent directly
335
+ # (see "Recurring Payments" below for how to obtain one)
336
+ subscription = Airwallex::BillingSubscription.create(
337
+ billing_customer_id: billing_customer.id,
338
+ currency: 'AUD',
339
+ collection_method: 'AUTO_CHARGE',
340
+ legal_entity_id: connected_account.legal_entity_id,
341
+ payment_source_id: payment_source.id,
342
+ starts_at: '2026-09-01T00:00:00+1000',
343
+ items: [{ price_id: price.id, quantity: 1 }]
344
+ )
345
+
346
+ # Inspect subscription line items
347
+ subscription.items
348
+ ```
349
+
350
+ ### Recurring Payments (Payment Consents & Sources)
351
+
352
+ Off-session charges flow through three linked resources: a `PaymentMethod` is
353
+ consented to future charges via a `PaymentConsent`, which then backs a
354
+ `PaymentSource` that `BillingSubscription` (and other billing resources) can
355
+ charge automatically.
356
+
357
+ ```ruby
358
+ # Consent to future merchant-initiated charges on a saved payment method
359
+ consent = Airwallex::PaymentConsent.create(
360
+ customer_id: customer.id,
361
+ payment_method: { id: payment_method.id },
362
+ next_triggered_by: 'merchant',
363
+ merchant_trigger_reason: 'unscheduled'
364
+ )
365
+ consent.verify(payment_method: { card: { cvc: '123' } })
366
+
367
+ # Wrap the consented payment method as a Payment Source for Billing
368
+ # external_id is the PaymentMethod's id, not the PaymentConsent's id
369
+ payment_source = Airwallex::PaymentSource.create(
370
+ billing_customer_id: billing_customer.id,
371
+ external_id: payment_method.id,
372
+ linked_payment_account_id: connected_account.id
373
+ )
374
+ ```
375
+
376
+ ### Scale (Connected Accounts, Funds Splits, Charges)
377
+
378
+ For platforms onboarding sub-merchants:
379
+
380
+ ```ruby
381
+ # Onboard a connected account
382
+ # account_details is a required top-level wrapper; nickname/primary_contact/
383
+ # customer_agreements are top-level siblings, not nested inside it.
384
+ connected_account = Airwallex::ConnectedAccount.create(
385
+ nickname: 'Sub-merchant Co',
386
+ primary_contact: { email: 'contact@submerchant.com' },
387
+ customer_agreements: {
388
+ agreed_to_terms_and_conditions: true,
389
+ agreed_to_data_usage: true,
390
+ terms_and_conditions: { service_agreement_type: 'FULL' }
391
+ },
392
+ account_details: {
393
+ legal_entity_type: 'BUSINESS',
394
+ business_details: {
395
+ business_name: 'Sub-merchant Co',
396
+ business_structure: 'COMPANY'
397
+ }
398
+ }
399
+ )
400
+ connected_account.legal_entity_id
401
+ connected_account.agree_to_terms_and_conditions
402
+ connected_account.submit
403
+
404
+ # Inspect the platform's own account
405
+ Airwallex::ConnectedAccount.current
406
+ Airwallex::ConnectedAccount.wallet_info
407
+
408
+ # Split a charge's funds between the platform and a connected account
409
+ split = Airwallex::FundsSplit.create(
410
+ payment_intent_id: payment_intent.id,
411
+ splits: [{ account_id: connected_account.id, amount: 10.00 }]
412
+ )
413
+ split.release
414
+
415
+ # Charges (create, retrieve, list)
416
+ charge = Airwallex::Charge.retrieve('charge_id')
417
+ ```
418
+
253
419
  ## Usage
254
420
 
255
421
  ### Authentication
@@ -367,10 +533,16 @@ end
367
533
  ```
368
534
  lib/airwallex/
369
535
  ├── api_operations/ # CRUD operation mixins (Create, Retrieve, List, Update, Delete)
370
- ├── resources/ # Implemented resources
371
- ├── payment_intent.rb # Payment acceptance
372
- ├── transfer.rb # Payouts
373
- └── beneficiary.rb # Payout beneficiaries
536
+ ├── resources/ # Implemented resources, grouped by API area:
537
+ │ # payment acceptance - payment_intent, refund, payment_method, customer, dispute
538
+ │ # payouts - transfer, batch_transfer, beneficiary
539
+ # foreign exchange - rate, quote, conversion, balance
540
+ │ │ # global accounts - global_account(+_alias, _mandate, _transaction)
541
+ │ │ # billing - billing_customer, billing_product, billing_price,
542
+ │ │ # billing_subscription(+_item)
543
+ │ │ # recurring payments - payment_consent, payment_source
544
+ │ │ # scale - connected_account, account_amendment, funds_split, charge
545
+ │ └── ... # see lib/airwallex/resources/ for the full, current list
374
546
  ├── api_resource.rb # Base resource class with dynamic attributes
375
547
  ├── list_object.rb # Pagination wrapper
376
548
  ├── errors.rb # Exception hierarchy
@@ -418,26 +590,40 @@ end
418
590
  - **Payment Acceptance**:
419
591
  - PaymentIntent (create, retrieve, list, update, confirm, cancel, capture)
420
592
  - Refund (create, retrieve, list)
421
- - PaymentMethod (create, retrieve, list, update, delete, detach)
593
+ - PaymentMethod (create, retrieve, list, update, disable)
422
594
  - Customer (create, retrieve, list, update, delete)
423
- - Dispute (retrieve, list, accept, submit_evidence)
595
+ - Dispute (retrieve, list, accept, challenge, related_payment_intents)
424
596
  - **Payouts**:
425
597
  - Transfer (create, retrieve, list, cancel)
426
- - Beneficiary (create, retrieve, list, delete)
598
+ - Beneficiary (create, retrieve, list, update, delete, validate, verify_account, api_schema, form_schema, supported_financial_institutions)
427
599
  - BatchTransfer (create, retrieve, list)
428
600
  - **Foreign Exchange & Multi-Currency**:
429
- - Rate (retrieve, list) - Real-time exchange rate queries
601
+ - Rate (retrieve) - Real-time exchange rate queries
430
602
  - Quote (create, retrieve) - Lock exchange rates with expiration tracking
431
603
  - Conversion (create, retrieve, list) - Execute currency conversions
432
604
  - Balance (list, retrieve) - Query account balances across currencies
605
+ - **Global Accounts**:
606
+ - GlobalAccount (create, retrieve, list, update, close, generate_statement_letter, create_alias, aliases, mandate, mandates)
607
+ - GlobalAccountTransaction, GlobalAccountAlias, GlobalAccountMandate (list/lifecycle actions scoped to a parent account)
608
+ - **Billing & Subscriptions**:
609
+ - BillingCustomer (create, retrieve, list, update, bank_transfer_instructions)
610
+ - BillingProduct (create, retrieve, list, update)
611
+ - BillingPrice (create, retrieve, list, update)
612
+ - BillingSubscription (create, retrieve, list, update, items)
613
+ - BillingSubscriptionItem (scoped to a parent subscription)
614
+ - **Recurring Payments**:
615
+ - PaymentConsent (create, retrieve, list, update, verify, verify_continue, disable)
616
+ - PaymentSource (create, retrieve, list)
617
+ - **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
620
+ - FundsSplit (create, retrieve, list, release)
621
+ - Charge (create, retrieve, list)
433
622
  - **Webhooks**: Event handling, HMAC-SHA256 signature verification
434
623
 
435
624
  ### Coming in Future Versions
436
625
 
437
- - Global accounts
438
626
  - Card issuing
439
- - Subscriptions and billing
440
- - Virtual account numbers
441
627
 
442
628
  ## Environment Support
443
629
 
@@ -24,6 +24,28 @@ module Airwallex
24
24
  raise NotImplementedError, "#{self} must implement .resource_path"
25
25
  end
26
26
 
27
+ # Perform a one-off POST that returns a plain, symbolized Hash rather
28
+ # than an APIResource — for actions with no id/refresh lifecycle of
29
+ # their own (e.g. Beneficiary.verify_account, a bank-detail check that
30
+ # never becomes a resource).
31
+ #
32
+ # @param path [String] the request path
33
+ # @param params [Hash] request body
34
+ # @return [Hash] symbolized response
35
+ def self.symbolized_post(path, params = {})
36
+ Util.deep_symbolize_keys(Airwallex.client.post(path, params))
37
+ end
38
+
39
+ # Perform a one-off GET that returns a plain, symbolized Hash rather
40
+ # than an APIResource. See .symbolized_post.
41
+ #
42
+ # @param path [String] the request path
43
+ # @param params [Hash] query params
44
+ # @return [Hash] symbolized response
45
+ def self.symbolized_get(path, params = {})
46
+ Util.deep_symbolize_keys(Airwallex.client.get(path, params))
47
+ end
48
+
27
49
  # Dynamic attribute accessors
28
50
  def method_missing(method_name, *args, &)
29
51
  method_str = method_name.to_s
@@ -2,13 +2,18 @@
2
2
 
3
3
  module Airwallex
4
4
  class Error < StandardError
5
+ # @return [String, nil] error code
6
+ # @return [String, nil] param the offending field path, from the response's `source`
7
+ # @return [Hash, Array, nil] details symbolized field-level error detail, e.g. from a
8
+ # validation_failed response (was raw/string-keyed prior to 0.8.0)
9
+ # @return [Integer, nil] http_status
5
10
  attr_reader :code, :message, :param, :details, :http_status
6
11
 
7
12
  def initialize(message = nil, code: nil, param: nil, details: nil, http_status: nil)
8
13
  @code = code
9
14
  @message = message
10
15
  @param = param
11
- @details = details
16
+ @details = Util.deep_symbolize_keys(details)
12
17
  @http_status = http_status
13
18
  super(message)
14
19
  end
@@ -64,36 +64,36 @@ module Airwallex
64
64
  # method) before attempting to create the beneficiary.
65
65
  #
66
66
  # @param params [Hash] the same shape of params you'd pass to .create
67
- # @return [Hash] raw validation result
67
+ # @return [Hash] symbolized validation result
68
68
  def self.validate(params = {})
69
- Airwallex.client.post("#{resource_path}/validate", params)
69
+ symbolized_post("#{resource_path}/validate", params)
70
70
  end
71
71
 
72
72
  # Verify ownership of a beneficiary's bank account via Confirmation of
73
73
  # Payee (CoP) before creation.
74
74
  #
75
75
  # @param params [Hash] beneficiary bank account details to verify
76
- # @return [Hash] raw verification result (status, account_name_match_result)
76
+ # @return [Hash] symbolized verification result (status, account_name_match_result)
77
77
  def self.verify_account(params = {})
78
- Airwallex.client.post("#{resource_path}/verify_account", params)
78
+ symbolized_post("#{resource_path}/verify_account", params)
79
79
  end
80
80
 
81
81
  # Retrieve the API schema (field validation rules) for beneficiary bank
82
82
  # details, keyed by beneficiary type / entity type / bank country.
83
83
  #
84
84
  # @param params [Hash] e.g. beneficiary_type:, bank_country_code:
85
- # @return [Hash] raw schema response
85
+ # @return [Hash] symbolized schema response
86
86
  def self.api_schema(params = {})
87
- Airwallex.client.post(API_SCHEMA_PATH, params)
87
+ symbolized_post(API_SCHEMA_PATH, params)
88
88
  end
89
89
 
90
90
  # Retrieve the dynamic form schema used to render beneficiary bank-detail
91
91
  # forms in the UI, keyed by beneficiary type / entity type / bank country.
92
92
  #
93
93
  # @param params [Hash] e.g. beneficiary_type:, bank_country_code:
94
- # @return [Hash] raw schema response
94
+ # @return [Hash] symbolized schema response
95
95
  def self.form_schema(params = {})
96
- Airwallex.client.post(FORM_SCHEMA_PATH, params)
96
+ symbolized_post(FORM_SCHEMA_PATH, params)
97
97
  end
98
98
 
99
99
  # Search financial institutions supported for a given country, currency,
@@ -102,9 +102,9 @@ module Airwallex
102
102
  # @param params [Hash] required: bank_country_code:, account_currency:,
103
103
  # entity_type:, transfer_method:, keyword: (a bank-name search term,
104
104
  # min 3 chars)
105
- # @return [Hash] raw response listing supported institutions
105
+ # @return [Hash] symbolized response listing supported institutions
106
106
  def self.supported_financial_institutions(params = {})
107
- Airwallex.client.get(SUPPORTED_FINANCIAL_INSTITUTIONS_PATH, params)
107
+ symbolized_get(SUPPORTED_FINANCIAL_INSTITUTIONS_PATH, params)
108
108
  end
109
109
  end
110
110
  end
@@ -40,9 +40,9 @@ module Airwallex
40
40
  # Retrieve the bank transfer instructions for funding this customer's
41
41
  # subscriptions (e.g. for a BT-funded instalment plan).
42
42
  #
43
- # @return [Hash] raw bank transfer instructions
43
+ # @return [Hash] symbolized bank transfer instructions
44
44
  def bank_transfer_instructions
45
- Airwallex.client.get("#{self.class.resource_path}/#{id}/bank_transfer_instructions")
45
+ self.class.symbolized_get("#{self.class.resource_path}/#{id}/bank_transfer_instructions")
46
46
  end
47
47
  end
48
48
  end
@@ -75,9 +75,9 @@ module Airwallex
75
75
  # authenticated as
76
76
  #
77
77
  # @param params [Hash] additional params
78
- # @return [Hash] raw wallet info response
78
+ # @return [Hash] symbolized wallet info response
79
79
  def self.wallet_info(params = {})
80
- Airwallex.client.get(WALLET_INFO_PATH, params)
80
+ symbolized_get(WALLET_INFO_PATH, params)
81
81
  end
82
82
 
83
83
  # The legal_entity_id needed by BillingCustomer.create's
@@ -45,9 +45,9 @@ module Airwallex
45
45
  # @param params [Hash] account_statement_type: (e.g. "AMAZON"),
46
46
  # registration_info: { agreement:, registered_name:, registered_email:,
47
47
  # registered_address: { address:, city:, state:, postcode:, country: } }
48
- # @return [Hash] raw response (the generated letter/document reference)
48
+ # @return [Hash] symbolized response (the generated letter/document reference)
49
49
  def generate_statement_letter(params = {})
50
- Airwallex.client.post("#{self.class.resource_path}/#{id}/generate_statement_letter", params)
50
+ self.class.symbolized_post("#{self.class.resource_path}/#{id}/generate_statement_letter", params)
51
51
  end
52
52
 
53
53
  # List inbound transactions (deposits) received into this account
@@ -40,11 +40,14 @@ module Airwallex
40
40
  hash.transform_keys(&:to_sym)
41
41
  end
42
42
 
43
- def deep_symbolize_keys(hash)
44
- return hash unless hash.is_a?(Hash)
45
-
46
- hash.each_with_object({}) do |(key, value), result|
47
- result[key.to_sym] = value.is_a?(Hash) ? deep_symbolize_keys(value) : value
43
+ def deep_symbolize_keys(value)
44
+ case value
45
+ when Hash
46
+ value.each_with_object({}) { |(k, v), result| result[k.to_sym] = deep_symbolize_keys(v) }
47
+ when Array
48
+ value.map { |v| deep_symbolize_keys(v) }
49
+ else
50
+ value
48
51
  end
49
52
  end
50
53
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Airwallex
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.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.7.0
4
+ version: 0.8.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-08-28 00:00:00.000000000 Z
11
+ date: 2026-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -137,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  requirements: []
140
- rubygems_version: 3.5.16
140
+ rubygems_version: 3.5.22
141
141
  signing_key:
142
142
  specification_version: 4
143
143
  summary: Production-grade Ruby client for the Airwallex API