airwallex 0.6.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/README.md +211 -25
  4. data/lib/airwallex/api_operations/delete.rb +3 -3
  5. data/lib/airwallex/api_operations/update.rb +4 -4
  6. data/lib/airwallex/api_resource.rb +22 -0
  7. data/lib/airwallex/client.rb +1 -1
  8. data/lib/airwallex/configuration.rb +6 -2
  9. data/lib/airwallex/errors.rb +6 -1
  10. data/lib/airwallex/resources/account_amendment.rb +29 -0
  11. data/lib/airwallex/resources/beneficiary.rb +96 -0
  12. data/lib/airwallex/resources/billing_customer.rb +48 -0
  13. data/lib/airwallex/resources/billing_price.rb +30 -0
  14. data/lib/airwallex/resources/billing_product.rb +23 -0
  15. data/lib/airwallex/resources/billing_subscription.rb +93 -0
  16. data/lib/airwallex/resources/billing_subscription_item.rb +7 -0
  17. data/lib/airwallex/resources/charge.rb +19 -0
  18. data/lib/airwallex/resources/connected_account.rb +136 -0
  19. data/lib/airwallex/resources/conversion.rb +1 -1
  20. data/lib/airwallex/resources/customer.rb +5 -2
  21. data/lib/airwallex/resources/dispute.rb +48 -28
  22. data/lib/airwallex/resources/funds_split.rb +37 -0
  23. data/lib/airwallex/resources/global_account.rb +137 -0
  24. data/lib/airwallex/resources/global_account_alias.rb +62 -0
  25. data/lib/airwallex/resources/global_account_mandate.rb +26 -0
  26. data/lib/airwallex/resources/global_account_transaction.rb +7 -0
  27. data/lib/airwallex/resources/payment_consent.rb +68 -0
  28. data/lib/airwallex/resources/payment_method.rb +13 -5
  29. data/lib/airwallex/resources/payment_source.rb +52 -0
  30. data/lib/airwallex/resources/quote.rb +5 -3
  31. data/lib/airwallex/resources/rate.rb +2 -7
  32. data/lib/airwallex/util.rb +8 -5
  33. data/lib/airwallex/version.rb +1 -1
  34. data/lib/airwallex.rb +15 -0
  35. metadata +18 -3
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Represents a Billing customer — the entity a BillingSubscription is
5
+ # scoped to. Distinct from Airwallex::Customer, which belongs to Payment
6
+ # Acceptance (/api/v1/pa/customers), not Billing.
7
+ #
8
+ # @example Create a billing customer
9
+ # # Flat payload, no wrapper key (unlike Beneficiary/ConnectedAccount).
10
+ # # type is "INDIVIDUAL" or "BUSINESS". default_legal_entity_id is not a
11
+ # # separate resource — it's account_details.legal_entity_id from a
12
+ # # ConnectedAccount's own retrieve response (see
13
+ # # ConnectedAccount#legal_entity_id).
14
+ # connected_account = Airwallex::ConnectedAccount.retrieve("acct_123")
15
+ # billing_customer = Airwallex::BillingCustomer.create(
16
+ # name: "Acme Corp",
17
+ # email: "billing@acme.example",
18
+ # type: "BUSINESS",
19
+ # default_billing_currency: "AUD",
20
+ # default_legal_entity_id: connected_account.legal_entity_id,
21
+ # address: {
22
+ # street: "200 Collins Street",
23
+ # city: "Melbourne",
24
+ # state: "VIC",
25
+ # postcode: "3000",
26
+ # country_code: "AU"
27
+ # }
28
+ # )
29
+ class BillingCustomer < APIResource
30
+ extend APIOperations::Create
31
+ extend APIOperations::Retrieve
32
+ extend APIOperations::List
33
+ include APIOperations::Update
34
+
35
+ # @return [String] API resource path for billing customers
36
+ def self.resource_path
37
+ "/api/v1/billing/billing_customers"
38
+ end
39
+
40
+ # Retrieve the bank transfer instructions for funding this customer's
41
+ # subscriptions (e.g. for a BT-funded instalment plan).
42
+ #
43
+ # @return [Hash] symbolized bank transfer instructions
44
+ def bank_transfer_instructions
45
+ self.class.symbolized_get("#{self.class.resource_path}/#{id}/bank_transfer_instructions")
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Represents a Billing price — the amount/currency/cadence attached to a
5
+ # BillingProduct that a BillingSubscription is created against.
6
+ #
7
+ # @example Create a simple per-unit recurring price
8
+ # # Flat payload, no wrapper key. pricing_model is required (PER_UNIT /
9
+ # # FLAT / GRADUATED / VOLUME — GRADUATED uses tiers: instead of
10
+ # # unit_amount:). recurring: is what makes a price usable for a
11
+ # # subscription.
12
+ # price = Airwallex::BillingPrice.create(
13
+ # product_id: product.id,
14
+ # currency: "AUD",
15
+ # pricing_model: "PER_UNIT",
16
+ # unit_amount: 250.00,
17
+ # recurring: { period: 1, period_unit: "MONTH" }
18
+ # )
19
+ class BillingPrice < APIResource
20
+ extend APIOperations::Create
21
+ extend APIOperations::Retrieve
22
+ extend APIOperations::List
23
+ include APIOperations::Update
24
+
25
+ # @return [String] API resource path for billing prices
26
+ def self.resource_path
27
+ "/api/v1/billing/prices"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Represents a Billing product — the thing being sold on a subscription
5
+ # (distinct from Payment Acceptance's Customer/PaymentMethod resources).
6
+ #
7
+ # @example Create a product
8
+ # product = Airwallex::BillingProduct.create(name: "Instalment Plan")
9
+ #
10
+ # @example Update a product
11
+ # product.update(name: "Instalment Plan (v2)")
12
+ class BillingProduct < APIResource
13
+ extend APIOperations::Create
14
+ extend APIOperations::Retrieve
15
+ extend APIOperations::List
16
+ include APIOperations::Update
17
+
18
+ # @return [String] API resource path for billing products
19
+ def self.resource_path
20
+ "/api/v1/billing/products"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Represents a Billing subscription, e.g. a multi-part instalment plan.
5
+ #
6
+ # @example Create a subscription with a deferred start date
7
+ # # Flat payload, no wrapper key. Prices attach via an items: array of
8
+ # # { price_id:, quantity: }, not a flat price_id. legal_entity_id comes
9
+ # # from ConnectedAccount#legal_entity_id, not a separate resource.
10
+ # # payment_source_id references a PaymentSource (see
11
+ # # Airwallex::PaymentSource for the full consent -> source -> subscription
12
+ # # chain), not a PaymentConsent directly.
13
+ # connected_account = Airwallex::ConnectedAccount.retrieve("acct_123")
14
+ # subscription = Airwallex::BillingSubscription.create(
15
+ # billing_customer_id: billing_customer.id,
16
+ # currency: "AUD",
17
+ # collection_method: "AUTO_CHARGE",
18
+ # legal_entity_id: connected_account.legal_entity_id,
19
+ # payment_source_id: payment_source.id,
20
+ # starts_at: "2026-09-01T00:00:00+1000",
21
+ # items: [{ price_id: price.id, quantity: 1 }]
22
+ # )
23
+ # subscription.status #=> "PENDING"
24
+ #
25
+ # @example Cancel a subscription
26
+ # subscription.cancel
27
+ class BillingSubscription < APIResource
28
+ extend APIOperations::Create
29
+ extend APIOperations::Retrieve
30
+ extend APIOperations::List
31
+ include APIOperations::Update
32
+
33
+ # @return [String] API resource path for billing subscriptions
34
+ def self.resource_path
35
+ "/api/v1/billing/subscriptions"
36
+ end
37
+
38
+ # Cancel this subscription
39
+ #
40
+ # @param params [Hash] additional cancellation params
41
+ # @return [BillingSubscription] self
42
+ def cancel(params = {})
43
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/cancel", params)
44
+ refresh_from(response)
45
+ self
46
+ end
47
+
48
+ # List the line items on this subscription
49
+ #
50
+ # @param params [Hash] additional query params (e.g. pagination)
51
+ # @return [ListObject<BillingSubscriptionItem>] list of subscription items
52
+ def items(params = {})
53
+ response = Airwallex.client.get("#{self.class.resource_path}/#{id}/items", params)
54
+
55
+ ListObject.new(
56
+ data: extract_item_rows(response),
57
+ has_more: extract_has_more(response),
58
+ next_cursor: extract_next_cursor(response),
59
+ resource_class: BillingSubscriptionItem,
60
+ params: params
61
+ )
62
+ end
63
+
64
+ # Retrieve a single line item on this subscription
65
+ #
66
+ # @param item_id [String] the subscription item id
67
+ # @return [BillingSubscriptionItem]
68
+ def item(item_id)
69
+ response = Airwallex.client.get("#{self.class.resource_path}/#{id}/items/#{item_id}")
70
+ BillingSubscriptionItem.new(response)
71
+ end
72
+
73
+ private
74
+
75
+ def extract_item_rows(response)
76
+ return response if response.is_a?(Array)
77
+
78
+ response[:items] || response["items"] || response[:data] || response["data"] || []
79
+ end
80
+
81
+ def extract_has_more(response)
82
+ return false unless response.is_a?(Hash)
83
+
84
+ response[:has_more] || response["has_more"] || false
85
+ end
86
+
87
+ def extract_next_cursor(response)
88
+ return nil unless response.is_a?(Hash)
89
+
90
+ response[:next_cursor] || response["next_cursor"]
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Read-only representation of a single Billing subscription line item
5
+ class BillingSubscriptionItem < APIResource
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Represents a Charge (Scale product) — funds attributed to a Connected
5
+ # Account, tracked before payout.
6
+ #
7
+ # @example Retrieve a charge
8
+ # charge = Airwallex::Charge.retrieve("chg_123")
9
+ class Charge < APIResource
10
+ extend APIOperations::Create
11
+ extend APIOperations::Retrieve
12
+ extend APIOperations::List
13
+
14
+ # @return [String] API resource path for charges
15
+ def self.resource_path
16
+ "/api/v1/charges"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Represents a Connected Account (Scale product) — a sub-account container
5
+ # used to onboard and track funds for a platform's connected entities.
6
+ #
7
+ # @example Create a connected account (business entity)
8
+ # # account_details is a required top-level wrapper. customer_agreements,
9
+ # # nickname, and primary_contact are top-level siblings, not nested
10
+ # # inside it. legal_entity_type is "BUSINESS" or "INDIVIDUAL", not
11
+ # # "COMPANY" (business_structure below is the field that uses "COMPANY").
12
+ # # This is a minimal subset of Airwallex's real KYB schema — the full
13
+ # # schema also supports trustee entities and much more optional detail
14
+ # # (identity documents, store details, business person details, etc).
15
+ # account = Airwallex::ConnectedAccount.create(
16
+ # nickname: "Acme Advertiser",
17
+ # primary_contact: { email: "contact@acme.example" },
18
+ # customer_agreements: {
19
+ # agreed_to_terms_and_conditions: true,
20
+ # agreed_to_data_usage: true,
21
+ # terms_and_conditions: { service_agreement_type: "FULL" }
22
+ # },
23
+ # account_details: {
24
+ # legal_entity_type: "BUSINESS",
25
+ # business_details: {
26
+ # business_name: "Acme Corp",
27
+ # business_structure: "COMPANY",
28
+ # business_address: {
29
+ # address_line1: "200 Collins Street",
30
+ # country_code: "AU",
31
+ # postcode: "3000",
32
+ # state: "VIC",
33
+ # suburb: "Melbourne"
34
+ # }
35
+ # }
36
+ # }
37
+ # )
38
+ #
39
+ # @example Trigger KYC/KYB verification
40
+ # account.submit
41
+ #
42
+ # @example Look up whichever account you're currently authenticated as
43
+ # Airwallex::ConnectedAccount.current
44
+ #
45
+ # @example Get the legal_entity_id needed by BillingCustomer/BillingSubscription
46
+ # account = Airwallex::ConnectedAccount.retrieve("acct_123")
47
+ # account.legal_entity_id
48
+ class ConnectedAccount < APIResource
49
+ extend APIOperations::Create
50
+ extend APIOperations::Retrieve
51
+ extend APIOperations::List
52
+ include APIOperations::Update
53
+
54
+ # Not nested under /accounts/{id} — these endpoints are implicitly
55
+ # scoped to whichever account the request is authenticated as (the
56
+ # platform's own account, or a connected account via x-on-behalf-of).
57
+ CURRENT_ACCOUNT_PATH = "/api/v1/account"
58
+ WALLET_INFO_PATH = "/api/v1/account/wallet_info"
59
+
60
+ # @return [String] API resource path for connected accounts
61
+ def self.resource_path
62
+ "/api/v1/accounts"
63
+ end
64
+
65
+ # Retrieve whichever account the current request is authenticated as
66
+ #
67
+ # @param params [Hash] additional params
68
+ # @return [ConnectedAccount]
69
+ def self.current(params = {})
70
+ response = Airwallex.client.get(CURRENT_ACCOUNT_PATH, params)
71
+ new(response)
72
+ end
73
+
74
+ # Retrieve wallet info for whichever account the current request is
75
+ # authenticated as
76
+ #
77
+ # @param params [Hash] additional params
78
+ # @return [Hash] symbolized wallet info response
79
+ def self.wallet_info(params = {})
80
+ symbolized_get(WALLET_INFO_PATH, params)
81
+ end
82
+
83
+ # The legal_entity_id needed by BillingCustomer.create's
84
+ # default_legal_entity_id and BillingSubscription.create's
85
+ # legal_entity_id — nested inside account_details on this account's own
86
+ # response, not a separate resource.
87
+ #
88
+ # @return [String, nil]
89
+ def legal_entity_id
90
+ attributes.dig(:account_details, :legal_entity_id)
91
+ end
92
+
93
+ # Submit the account for KYC/KYB verification
94
+ #
95
+ # @param params [Hash] additional submission params
96
+ # @return [ConnectedAccount] self
97
+ def submit(params = {})
98
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/submit", params)
99
+ refresh_from(response)
100
+ self
101
+ end
102
+
103
+ # Agree to Airwallex's terms and conditions on behalf of this account
104
+ #
105
+ # @param params [Hash] e.g. agreed_at:, service_agreement_type:,
106
+ # device_data: { ip_address:, user_agent: }
107
+ # @return [ConnectedAccount] self
108
+ def agree_to_terms_and_conditions(params = {})
109
+ response = Airwallex.client.post(
110
+ "#{self.class.resource_path}/#{id}/terms_and_conditions/agree", params
111
+ )
112
+ refresh_from(response)
113
+ self
114
+ end
115
+
116
+ # Suspend this account
117
+ #
118
+ # @param params [Hash] e.g. message: "reason for suspension"
119
+ # @return [ConnectedAccount] self
120
+ def suspend(params = {})
121
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/suspend", params)
122
+ refresh_from(response)
123
+ self
124
+ end
125
+
126
+ # Reactivate a suspended account
127
+ #
128
+ # @param params [Hash] e.g. message: "reason for reactivation"
129
+ # @return [ConnectedAccount] self
130
+ def reactivate(params = {})
131
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/reactivate", params)
132
+ refresh_from(response)
133
+ self
134
+ end
135
+ end
136
+ end
@@ -37,7 +37,7 @@ module Airwallex
37
37
  extend APIOperations::List
38
38
 
39
39
  def self.resource_path
40
- "/api/v1/conversions"
40
+ "/api/v1/fx/conversions"
41
41
  end
42
42
  end
43
43
  end
@@ -7,11 +7,15 @@ module Airwallex
7
7
  # for individual users or accounts.
8
8
  #
9
9
  # @example Create a customer
10
+ # # merchant_customer_id is required — your own reference id for this
11
+ # # customer. request_id is optional (the gem's Idempotency middleware
12
+ # # injects one automatically).
10
13
  # customer = Airwallex::Customer.create(
14
+ # merchant_customer_id: SecureRandom.uuid,
11
15
  # email: "john@example.com",
12
16
  # first_name: "John",
13
17
  # last_name: "Doe",
14
- # metadata: { internal_id: "user_789" }
18
+ # phone_number: "+1 1234567890"
15
19
  # )
16
20
  #
17
21
  # @example List payment methods for a customer
@@ -20,7 +24,6 @@ module Airwallex
20
24
  extend APIOperations::Create
21
25
  extend APIOperations::Retrieve
22
26
  extend APIOperations::List
23
- extend APIOperations::Update
24
27
  include APIOperations::Update
25
28
  extend APIOperations::Delete
26
29
 
@@ -4,7 +4,8 @@ module Airwallex
4
4
  # Dispute resource for handling chargebacks and payment disputes
5
5
  #
6
6
  # Disputes represent chargebacks or payment disputes initiated by cardholders.
7
- # Merchants can view disputes, submit evidence to challenge them, or accept them.
7
+ # Merchants can view disputes, challenge them with evidence, or accept them.
8
+ # There is no create — disputes originate from card networks/issuing banks.
8
9
  #
9
10
  # @example List open disputes
10
11
  # disputes = Airwallex::Dispute.list(status: 'OPEN')
@@ -12,57 +13,76 @@ module Airwallex
12
13
  # @example Retrieve a dispute
13
14
  # dispute = Airwallex::Dispute.retrieve('dis_123')
14
15
  #
15
- # @example Submit evidence
16
- # dispute = Airwallex::Dispute.retrieve('dis_123')
17
- # dispute.submit_evidence(
18
- # customer_communication: "Email showing delivery confirmation",
19
- # shipping_tracking_number: "1Z999AA10123456784"
20
- # )
21
- #
22
16
  # @example Accept a dispute
23
- # dispute = Airwallex::Dispute.retrieve('dis_123')
24
17
  # dispute.accept
25
18
  #
19
+ # @example Challenge a dispute
20
+ # dispute.challenge(...)
26
21
  class Dispute < APIResource
27
22
  extend APIOperations::Retrieve
28
23
  extend APIOperations::List
24
+ include APIOperations::Update
29
25
 
30
26
  def self.resource_path
31
- "/api/v1/disputes"
27
+ "/api/v1/pa/payment_disputes"
32
28
  end
33
29
 
34
30
  # Accept a dispute without challenging it
35
31
  #
36
- # @return [Airwallex::Dispute] The updated dispute object
32
+ # @return [Airwallex::Dispute] self
37
33
  def accept
38
- response = Airwallex.client.post("#{resource_path}/#{id}/accept", {})
34
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/accept", {})
39
35
  refresh_from(response)
40
36
  self
41
37
  end
42
38
 
43
- # Submit evidence to challenge a dispute
44
- #
45
- # @param evidence [Hash] Evidence details
46
- # @option evidence [String] :customer_communication Email or chat logs
47
- # @option evidence [String] :shipping_tracking_number Tracking number
48
- # @option evidence [String] :shipping_documentation Proof of shipping
49
- # @option evidence [String] :customer_signature Signed receipt
50
- # @option evidence [String] :receipt Proof of purchase
51
- # @option evidence [String] :refund_policy Refund policy document
52
- # @option evidence [String] :cancellation_policy Cancellation policy
53
- # @option evidence [String] :additional_information Other relevant info
39
+ # Challenge a dispute with evidence
54
40
  #
55
- # @return [Airwallex::Dispute] The updated dispute object
56
- def submit_evidence(evidence)
57
- response = Airwallex.client.post("#{resource_path}/#{id}/evidence", evidence)
41
+ # @param params [Hash] challenge params exact shape unconfirmed, pass
42
+ # through whatever Airwallex's challenge schema requires
43
+ # @return [Airwallex::Dispute] self
44
+ def challenge(params = {})
45
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/challenge", params)
58
46
  refresh_from(response)
59
47
  self
60
48
  end
61
49
 
50
+ # List payment intents related to this dispute
51
+ #
52
+ # @param params [Hash] additional query params (e.g. pagination)
53
+ # @return [ListObject<PaymentIntent>]
54
+ def related_payment_intents(params = {})
55
+ response = Airwallex.client.get(
56
+ "#{self.class.resource_path}/#{id}/related_payment_intents", params
57
+ )
58
+
59
+ ListObject.new(
60
+ data: extract_items(response),
61
+ has_more: extract_has_more(response),
62
+ next_cursor: extract_next_cursor(response),
63
+ resource_class: PaymentIntent,
64
+ params: params
65
+ )
66
+ end
67
+
62
68
  private
63
69
 
64
- def resource_path
65
- self.class.resource_path
70
+ def extract_items(response)
71
+ return response if response.is_a?(Array)
72
+
73
+ response[:items] || response["items"] || response[:data] || response["data"] || []
74
+ end
75
+
76
+ def extract_has_more(response)
77
+ return false unless response.is_a?(Hash)
78
+
79
+ response[:has_more] || response["has_more"] || false
80
+ end
81
+
82
+ def extract_next_cursor(response)
83
+ return nil unless response.is_a?(Hash)
84
+
85
+ response[:next_cursor] || response["next_cursor"]
66
86
  end
67
87
  end
68
88
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
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).
7
+ #
8
+ # @example Create a split
9
+ # split = Airwallex::FundsSplit.create(
10
+ # payment_intent_id: intent.id,
11
+ # splits: [{ account_id: connected_account.id, amount: 50.00 }]
12
+ # )
13
+ #
14
+ # @example Release the split funds
15
+ # split.release
16
+ class FundsSplit < APIResource
17
+ extend APIOperations::Create
18
+ extend APIOperations::Retrieve
19
+ extend APIOperations::List
20
+
21
+ # @return [String] API resource path for funds splits
22
+ def self.resource_path
23
+ "/api/v1/pa/funds_splits"
24
+ end
25
+
26
+ # Release this funds split (make the split amount available to the
27
+ # connected account)
28
+ #
29
+ # @param params [Hash] additional params
30
+ # @return [FundsSplit] self
31
+ def release(params = {})
32
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/release", params)
33
+ refresh_from(response)
34
+ self
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Airwallex
4
+ # Represents a Global Account (Virtual Account Number) used to collect
5
+ # local bank transfers into a specific currency and jurisdiction.
6
+ #
7
+ # @example Provision a VAN
8
+ # account = Airwallex::GlobalAccount.create(
9
+ # country_code: "AU",
10
+ # nick_name: "booking_12345",
11
+ # required_features: [{ transfer_method: "LOCAL" }]
12
+ # )
13
+ #
14
+ # @example Reconcile inbound deposits
15
+ # account.transactions.each { |txn| puts "#{txn.amount} from #{txn.remitter}" }
16
+ #
17
+ # @example Add and verify an alias (e.g. a phone number or email VAN)
18
+ # alias_record = account.create_alias(type: "PAYID_PHONE", value: "+61400000000")
19
+ # alias_record.submit_verification_code(code: "123456")
20
+ class GlobalAccount < APIResource
21
+ extend APIOperations::Create
22
+ extend APIOperations::Retrieve
23
+ extend APIOperations::List
24
+ include APIOperations::Update
25
+
26
+ # @return [String] API resource path for global accounts
27
+ def self.resource_path
28
+ "/api/v1/global_accounts"
29
+ end
30
+
31
+ # Close this global account
32
+ #
33
+ # @param params [Hash] additional closure params
34
+ # @return [GlobalAccount] self
35
+ def close(params = {})
36
+ response = Airwallex.client.post("#{self.class.resource_path}/#{id}/close", params)
37
+ refresh_from(response)
38
+ self
39
+ end
40
+
41
+ # Generate a bank-ownership statement letter for this account — e.g. for
42
+ # marketplace seller verification (known value: "AMAZON"; other
43
+ # marketplace-specific values likely exist).
44
+ #
45
+ # @param params [Hash] account_statement_type: (e.g. "AMAZON"),
46
+ # registration_info: { agreement:, registered_name:, registered_email:,
47
+ # registered_address: { address:, city:, state:, postcode:, country: } }
48
+ # @return [Hash] symbolized response (the generated letter/document reference)
49
+ def generate_statement_letter(params = {})
50
+ self.class.symbolized_post("#{self.class.resource_path}/#{id}/generate_statement_letter", params)
51
+ end
52
+
53
+ # List inbound transactions (deposits) received into this account
54
+ #
55
+ # @param params [Hash] additional query params (e.g. pagination)
56
+ # @return [ListObject<GlobalAccountTransaction>] list of transactions
57
+ def transactions(params = {})
58
+ response = Airwallex.client.get("#{self.class.resource_path}/#{id}/transactions", params)
59
+ build_list(response, GlobalAccountTransaction, params)
60
+ end
61
+
62
+ # Create an alias (e.g. PayID, email, or phone-linked VAN) on this account
63
+ #
64
+ # @param params [Hash] alias attributes
65
+ # @return [GlobalAccountAlias]
66
+ def create_alias(params = {})
67
+ response = Airwallex.client.post("#{GlobalAccountAlias.resource_path(id)}/create", params)
68
+ GlobalAccountAlias.new(response)
69
+ end
70
+
71
+ # Retrieve a single alias on this account
72
+ #
73
+ # @param alias_id [String]
74
+ # @return [GlobalAccountAlias]
75
+ def alias(alias_id)
76
+ response = Airwallex.client.get("#{GlobalAccountAlias.resource_path(id)}/#{alias_id}")
77
+ GlobalAccountAlias.new(response)
78
+ end
79
+
80
+ # List aliases on this account
81
+ #
82
+ # @param params [Hash] additional query params (e.g. pagination)
83
+ # @return [ListObject<GlobalAccountAlias>] list of aliases
84
+ def aliases(params = {})
85
+ response = Airwallex.client.get(GlobalAccountAlias.resource_path(id), params)
86
+ build_list(response, GlobalAccountAlias, params)
87
+ end
88
+
89
+ # Retrieve a single direct debit mandate on this account
90
+ #
91
+ # @param mandate_id [String]
92
+ # @return [GlobalAccountMandate]
93
+ def mandate(mandate_id)
94
+ response = Airwallex.client.get("#{GlobalAccountMandate.resource_path(id)}/#{mandate_id}")
95
+ GlobalAccountMandate.new(response)
96
+ end
97
+
98
+ # List direct debit mandates on this account
99
+ #
100
+ # @param params [Hash] additional query params (e.g. pagination)
101
+ # @return [ListObject<GlobalAccountMandate>] list of mandates
102
+ def mandates(params = {})
103
+ response = Airwallex.client.get(GlobalAccountMandate.resource_path(id), params)
104
+ build_list(response, GlobalAccountMandate, params)
105
+ end
106
+
107
+ private
108
+
109
+ def build_list(response, resource_class, params)
110
+ ListObject.new(
111
+ data: extract_items(response),
112
+ has_more: extract_has_more(response),
113
+ next_cursor: extract_next_cursor(response),
114
+ resource_class: resource_class,
115
+ params: params
116
+ )
117
+ end
118
+
119
+ def extract_items(response)
120
+ return response if response.is_a?(Array)
121
+
122
+ response[:items] || response["items"] || response[:data] || response["data"] || []
123
+ end
124
+
125
+ def extract_has_more(response)
126
+ return false unless response.is_a?(Hash)
127
+
128
+ response[:has_more] || response["has_more"] || false
129
+ end
130
+
131
+ def extract_next_cursor(response)
132
+ return nil unless response.is_a?(Hash)
133
+
134
+ response[:next_cursor] || response["next_cursor"]
135
+ end
136
+ end
137
+ end