airwallex 0.6.0 → 0.7.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 +57 -0
- data/lib/airwallex/api_operations/delete.rb +3 -3
- data/lib/airwallex/api_operations/update.rb +4 -4
- data/lib/airwallex/client.rb +1 -1
- data/lib/airwallex/configuration.rb +6 -2
- data/lib/airwallex/resources/account_amendment.rb +29 -0
- data/lib/airwallex/resources/beneficiary.rb +96 -0
- data/lib/airwallex/resources/billing_customer.rb +48 -0
- data/lib/airwallex/resources/billing_price.rb +30 -0
- data/lib/airwallex/resources/billing_product.rb +23 -0
- data/lib/airwallex/resources/billing_subscription.rb +93 -0
- data/lib/airwallex/resources/billing_subscription_item.rb +7 -0
- data/lib/airwallex/resources/charge.rb +19 -0
- data/lib/airwallex/resources/connected_account.rb +136 -0
- data/lib/airwallex/resources/conversion.rb +1 -1
- data/lib/airwallex/resources/customer.rb +5 -2
- data/lib/airwallex/resources/dispute.rb +48 -28
- data/lib/airwallex/resources/funds_split.rb +37 -0
- data/lib/airwallex/resources/global_account.rb +137 -0
- data/lib/airwallex/resources/global_account_alias.rb +62 -0
- data/lib/airwallex/resources/global_account_mandate.rb +26 -0
- data/lib/airwallex/resources/global_account_transaction.rb +7 -0
- data/lib/airwallex/resources/payment_consent.rb +68 -0
- data/lib/airwallex/resources/payment_method.rb +13 -5
- data/lib/airwallex/resources/payment_source.rb +52 -0
- data/lib/airwallex/resources/quote.rb +5 -3
- data/lib/airwallex/resources/rate.rb +2 -7
- data/lib/airwallex/version.rb +1 -1
- data/lib/airwallex.rb +15 -0
- metadata +17 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fad10ff0adfb96f6cb5e3a88ec1825a17b840d73658bd6fb6813f18e2746fea1
|
|
4
|
+
data.tar.gz: 9f59e5dfc64382838c7b1485b4ca28b19e9b71248ea51070ff106ad33cb1af32
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: de57501742d42048dd6a9286672c18050a17734ac2e21780f85e2d23e16e856bf406df20842352d55cd46aff22bfb0ade9676e1e6578865a7c966eba833880eb
|
|
7
|
+
data.tar.gz: 809fce3bea1462cecb398388b34863f65edf4ef00a5742ce0c30776c0dcbf942b474daef0551e64cd81b79ddb8a44fb01ef210fd2cec8470dbe844ce4f5968c6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,62 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-08-28
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- GlobalAccount resource (create, retrieve, list, update, `#close`, `#generate_statement_letter`,
|
|
7
|
+
`#transactions`)
|
|
8
|
+
- GlobalAccountAlias nested resource (`#create_alias`, `#alias`, `#aliases`, `#initiate_port`,
|
|
9
|
+
`#submit_verification_code`, `#request_new_verification_code`, `#cancel`)
|
|
10
|
+
- GlobalAccountMandate nested resource (`#mandate`, `#mandates`, `#cancel`)
|
|
11
|
+
- Billing resources: BillingCustomer, BillingProduct, BillingPrice, BillingSubscription (with `#items`/`#item`)
|
|
12
|
+
- PaymentConsent resource (create, retrieve, list, update, `#verify`, `#verify_continue`, `#disable`)
|
|
13
|
+
- PaymentSource resource (create, retrieve, list)
|
|
14
|
+
- ConnectedAccount resource (create, retrieve, list, update, `#submit`, `#agree_to_terms_and_conditions`,
|
|
15
|
+
`#suspend`, `#reactivate`, `.current`, `.wallet_info`, `#legal_entity_id`)
|
|
16
|
+
- AccountAmendment resource (create, retrieve)
|
|
17
|
+
- FundsSplit resource (create, retrieve, list, `#release`)
|
|
18
|
+
- Charge resource (create, retrieve, list)
|
|
19
|
+
- Beneficiary gained `.update`/`#update`, `.validate`, `.verify_account`, `.api_schema`, `.form_schema`, and
|
|
20
|
+
`.supported_financial_institutions`
|
|
21
|
+
- Dispute gained `.update`, `#challenge` (replacing `#submit_evidence`), and `#related_payment_intents`
|
|
22
|
+
- PaymentMethod gained `#disable`
|
|
23
|
+
- New tests covering all of the above (378 total)
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- `APIOperations::Update` now sends `POST #{resource_path}/{id}/update` instead of `PUT #{resource_path}/{id}`
|
|
27
|
+
- `APIOperations::Delete` now sends `POST #{resource_path}/{id}/delete` instead of `DELETE #{resource_path}/{id}`,
|
|
28
|
+
and checks the response instead of always returning `true`
|
|
29
|
+
- `Dispute`'s resource path corrected from `/api/v1/disputes` to `/api/v1/pa/payment_disputes`
|
|
30
|
+
- `Conversion`'s resource path corrected from `/api/v1/conversions` to `/api/v1/fx/conversions`
|
|
31
|
+
- `Configuration#api_version` now defaults to `nil` instead of a hardcoded date, and `x-api-version` is only
|
|
32
|
+
sent when explicitly set
|
|
33
|
+
- `Beneficiary.create`/`.validate`/`.update` payloads corrected: wrapped under a top-level `beneficiary` key,
|
|
34
|
+
with `nickname`/`payer_entity_type`/`transfer_methods`/`transfer_reason` as top-level siblings;
|
|
35
|
+
`entity_type` replaces the nonexistent `beneficiary_type`
|
|
36
|
+
- `Beneficiary#update`/`.update` requires the full payload, not a partial patch
|
|
37
|
+
- `Beneficiary.supported_financial_institutions` requires `account_currency`, `entity_type`,
|
|
38
|
+
`transfer_method`, and `keyword` in addition to `bank_country_code`
|
|
39
|
+
- `Customer.create` requires `merchant_customer_id`
|
|
40
|
+
- `GlobalAccount#generate_statement_letter` requires `account_statement_type` and `registration_info`
|
|
41
|
+
- `ConnectedAccount.create` payload corrected: `account_details` is a required top-level wrapper;
|
|
42
|
+
`legal_entity_type` is `"BUSINESS"`/`"INDIVIDUAL"`, not `"COMPANY"`
|
|
43
|
+
- `BillingCustomer.create` payload corrected to match the real flat schema (`name`/`email`/`type`/
|
|
44
|
+
`default_billing_currency`/`address`/`default_legal_entity_id`)
|
|
45
|
+
- `BillingPrice.create` requires `pricing_model` and `recurring`
|
|
46
|
+
- `BillingSubscription.create` payload corrected: prices attach via an `items:` array, not a flat
|
|
47
|
+
`price_id`; `starts_at` replaces `start_date`; added `legal_entity_id` and `payment_source_id`
|
|
48
|
+
- `AccountAmendment.create` payload corrected to use `target` plus the changed section as a top-level
|
|
49
|
+
sibling, not a generic `changes:` wrapper
|
|
50
|
+
- `PaymentSource.create`'s `external_id` corrected to reference a `PaymentMethod` id, not a `PaymentConsent` id
|
|
51
|
+
- `ConnectedAccount#legal_entity_id` added as the source for `BillingCustomer`/`BillingSubscription`'s
|
|
52
|
+
`legal_entity_id`, rather than a separate resource
|
|
53
|
+
- `Rate`/`Quote` use `sell_currency`/`buy_currency`, not `from_currency`/`to_currency`
|
|
54
|
+
- `Quote.create` requires `validity` (`MIN_1`, `MIN_15`, `MIN_30`, `HR_1`, `HR_4`, `HR_8`, or `HR_24`)
|
|
55
|
+
|
|
56
|
+
### Removed
|
|
57
|
+
- `Rate.list` — the endpoint has no "list all rates" concept, only `.retrieve(sell_currency:, buy_currency:)`
|
|
58
|
+
- `PaymentMethod.delete` and `PaymentMethod#detach` — replaced by `#disable`, the real lifecycle operation
|
|
59
|
+
|
|
3
60
|
## [0.6.0] - 2026-08-28
|
|
4
61
|
|
|
5
62
|
### Fixed
|
|
@@ -4,12 +4,12 @@ module Airwallex
|
|
|
4
4
|
module APIOperations
|
|
5
5
|
module Delete
|
|
6
6
|
def delete(id, opts = {})
|
|
7
|
-
Airwallex.client.
|
|
8
|
-
"#{resource_path}/#{id}",
|
|
7
|
+
response = Airwallex.client.post(
|
|
8
|
+
"#{resource_path}/#{id}/delete",
|
|
9
9
|
{},
|
|
10
10
|
opts[:headers] || {}
|
|
11
11
|
)
|
|
12
|
-
true
|
|
12
|
+
response == true
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
15
|
end
|
|
@@ -9,8 +9,8 @@ module Airwallex
|
|
|
9
9
|
|
|
10
10
|
module ClassMethods
|
|
11
11
|
def update(id, params = {}, opts = {})
|
|
12
|
-
response = Airwallex.client.
|
|
13
|
-
"#{resource_path}/#{id}",
|
|
12
|
+
response = Airwallex.client.post(
|
|
13
|
+
"#{resource_path}/#{id}/update",
|
|
14
14
|
params,
|
|
15
15
|
opts[:headers] || {}
|
|
16
16
|
)
|
|
@@ -20,8 +20,8 @@ module Airwallex
|
|
|
20
20
|
|
|
21
21
|
# Instance methods
|
|
22
22
|
def update(params = {})
|
|
23
|
-
response = Airwallex.client.
|
|
24
|
-
"#{self.class.resource_path}/#{id}",
|
|
23
|
+
response = Airwallex.client.post(
|
|
24
|
+
"#{self.class.resource_path}/#{id}/update",
|
|
25
25
|
params
|
|
26
26
|
)
|
|
27
27
|
refresh_from(response)
|
data/lib/airwallex/client.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Airwallex
|
|
|
25
25
|
|
|
26
26
|
conn.headers["Content-Type"] = "application/json"
|
|
27
27
|
conn.headers["User-Agent"] = user_agent
|
|
28
|
-
conn.headers["x-api-version"] = config.api_version
|
|
28
|
+
conn.headers["x-api-version"] = config.api_version if config.api_version
|
|
29
29
|
|
|
30
30
|
conn.adapter Faraday.default_adapter
|
|
31
31
|
end
|
|
@@ -10,12 +10,16 @@ module Airwallex
|
|
|
10
10
|
SANDBOX_FILES_URL = "https://files-demo.airwallex.com"
|
|
11
11
|
PRODUCTION_FILES_URL = "https://files.airwallex.com"
|
|
12
12
|
|
|
13
|
-
DEFAULT_API_VERSION = "2024-09-27"
|
|
14
13
|
VALID_ENVIRONMENTS = %i[sandbox production].freeze
|
|
15
14
|
|
|
15
|
+
# api_version is nil by default. Airwallex stores a version on your
|
|
16
|
+
# account and applies it automatically; the x-api-version header is only
|
|
17
|
+
# for exceptional per-call overrides (testing/migrating), not a
|
|
18
|
+
# permanent pin — see https://www.airwallex.com/docs/api/versioning.
|
|
19
|
+
# Set this explicitly if you want reproducible, pinned behavior instead.
|
|
16
20
|
def initialize
|
|
17
21
|
@environment = :sandbox
|
|
18
|
-
@api_version =
|
|
22
|
+
@api_version = nil
|
|
19
23
|
@log_level = :info
|
|
20
24
|
end
|
|
21
25
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents an amendment to whichever account the request is
|
|
5
|
+
# authenticated as (the platform's own account, or a connected account via
|
|
6
|
+
# x-on-behalf-of). Not nested under /accounts/{id} — scoped implicitly by
|
|
7
|
+
# auth context, same as ConnectedAccount.current.
|
|
8
|
+
#
|
|
9
|
+
# .create requires Admin-level API key permissions, a separate tier from
|
|
10
|
+
# the normal per-resource Read/Write scopes.
|
|
11
|
+
#
|
|
12
|
+
# @example Submit an amendment
|
|
13
|
+
# # `target` identifies the dotted path being amended; the changed
|
|
14
|
+
# # section is a top-level sibling keyed by its own name (e.g.
|
|
15
|
+
# # store_details), not wrapped in a generic "changes" key.
|
|
16
|
+
# amendment = Airwallex::AccountAmendment.create(
|
|
17
|
+
# target: "account_details.store_details",
|
|
18
|
+
# store_details: { store_name: "New Store Name" }
|
|
19
|
+
# )
|
|
20
|
+
class AccountAmendment < APIResource
|
|
21
|
+
extend APIOperations::Create
|
|
22
|
+
extend APIOperations::Retrieve
|
|
23
|
+
|
|
24
|
+
# @return [String] API resource path for account amendments
|
|
25
|
+
def self.resource_path
|
|
26
|
+
"/api/v1/account/amendments"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -1,14 +1,110 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Airwallex
|
|
4
|
+
# @example Create a beneficiary
|
|
5
|
+
# # nickname/payer_entity_type/transfer_methods are top-level siblings of
|
|
6
|
+
# # `beneficiary`, not nested inside it. request_id is optional (the
|
|
7
|
+
# # gem's Idempotency middleware injects one automatically). Call
|
|
8
|
+
# # .api_schema / .form_schema for the exact required fields per
|
|
9
|
+
# # entity_type + bank_country_code.
|
|
10
|
+
# beneficiary = Airwallex::Beneficiary.create(
|
|
11
|
+
# nickname: "Acme Corp",
|
|
12
|
+
# payer_entity_type: "COMPANY",
|
|
13
|
+
# transfer_methods: ["LOCAL"],
|
|
14
|
+
# beneficiary: {
|
|
15
|
+
# entity_type: "COMPANY",
|
|
16
|
+
# company_name: "Acme Corp",
|
|
17
|
+
# address: {
|
|
18
|
+
# country_code: "AU",
|
|
19
|
+
# city: "Melbourne",
|
|
20
|
+
# state: "VIC",
|
|
21
|
+
# postcode: "3000",
|
|
22
|
+
# street_address: "15 William Street"
|
|
23
|
+
# },
|
|
24
|
+
# bank_details: {
|
|
25
|
+
# account_name: "Acme Corp",
|
|
26
|
+
# account_number: "12750852",
|
|
27
|
+
# account_currency: "AUD",
|
|
28
|
+
# bank_country_code: "AU",
|
|
29
|
+
# bank_name: "National Australia Bank",
|
|
30
|
+
# account_routing_type1: "bsb",
|
|
31
|
+
# account_routing_value1: "083064",
|
|
32
|
+
# local_clearing_system: "BANK_TRANSFER" # country-specific enum
|
|
33
|
+
# }
|
|
34
|
+
# }
|
|
35
|
+
# )
|
|
36
|
+
#
|
|
37
|
+
# @example Update a beneficiary
|
|
38
|
+
# # .update doesn't support a partial patch — resend the full payload
|
|
39
|
+
# # with your change merged in.
|
|
40
|
+
# beneficiary.update(
|
|
41
|
+
# transfer_methods: ["LOCAL"],
|
|
42
|
+
# beneficiary: {
|
|
43
|
+
# entity_type: "COMPANY",
|
|
44
|
+
# company_name: "Acme Corp Ltd",
|
|
45
|
+
# bank_details: { account_currency: "AUD", bank_country_code: "AU", ... }
|
|
46
|
+
# }
|
|
47
|
+
# )
|
|
4
48
|
class Beneficiary < APIResource
|
|
5
49
|
extend APIOperations::Create
|
|
6
50
|
extend APIOperations::Retrieve
|
|
7
51
|
extend APIOperations::List
|
|
8
52
|
extend APIOperations::Delete
|
|
53
|
+
include APIOperations::Update
|
|
54
|
+
|
|
55
|
+
API_SCHEMA_PATH = "/api/v1/beneficiary_api_schemas/generate"
|
|
56
|
+
FORM_SCHEMA_PATH = "/api/v1/beneficiary_form_schemas/generate"
|
|
57
|
+
SUPPORTED_FINANCIAL_INSTITUTIONS_PATH = "/api/v1/beneficiary_form_schemas/supported_financial_institutions"
|
|
9
58
|
|
|
10
59
|
def self.resource_path
|
|
11
60
|
"/api/v1/beneficiaries"
|
|
12
61
|
end
|
|
62
|
+
|
|
63
|
+
# Validate beneficiary details (address, bank details, entity type, transfer
|
|
64
|
+
# method) before attempting to create the beneficiary.
|
|
65
|
+
#
|
|
66
|
+
# @param params [Hash] the same shape of params you'd pass to .create
|
|
67
|
+
# @return [Hash] raw validation result
|
|
68
|
+
def self.validate(params = {})
|
|
69
|
+
Airwallex.client.post("#{resource_path}/validate", params)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Verify ownership of a beneficiary's bank account via Confirmation of
|
|
73
|
+
# Payee (CoP) before creation.
|
|
74
|
+
#
|
|
75
|
+
# @param params [Hash] beneficiary bank account details to verify
|
|
76
|
+
# @return [Hash] raw verification result (status, account_name_match_result)
|
|
77
|
+
def self.verify_account(params = {})
|
|
78
|
+
Airwallex.client.post("#{resource_path}/verify_account", params)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Retrieve the API schema (field validation rules) for beneficiary bank
|
|
82
|
+
# details, keyed by beneficiary type / entity type / bank country.
|
|
83
|
+
#
|
|
84
|
+
# @param params [Hash] e.g. beneficiary_type:, bank_country_code:
|
|
85
|
+
# @return [Hash] raw schema response
|
|
86
|
+
def self.api_schema(params = {})
|
|
87
|
+
Airwallex.client.post(API_SCHEMA_PATH, params)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Retrieve the dynamic form schema used to render beneficiary bank-detail
|
|
91
|
+
# forms in the UI, keyed by beneficiary type / entity type / bank country.
|
|
92
|
+
#
|
|
93
|
+
# @param params [Hash] e.g. beneficiary_type:, bank_country_code:
|
|
94
|
+
# @return [Hash] raw schema response
|
|
95
|
+
def self.form_schema(params = {})
|
|
96
|
+
Airwallex.client.post(FORM_SCHEMA_PATH, params)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Search financial institutions supported for a given country, currency,
|
|
100
|
+
# entity type, and transfer method, by name keyword.
|
|
101
|
+
#
|
|
102
|
+
# @param params [Hash] required: bank_country_code:, account_currency:,
|
|
103
|
+
# entity_type:, transfer_method:, keyword: (a bank-name search term,
|
|
104
|
+
# min 3 chars)
|
|
105
|
+
# @return [Hash] raw response listing supported institutions
|
|
106
|
+
def self.supported_financial_institutions(params = {})
|
|
107
|
+
Airwallex.client.get(SUPPORTED_FINANCIAL_INSTITUTIONS_PATH, params)
|
|
108
|
+
end
|
|
13
109
|
end
|
|
14
110
|
end
|
|
@@ -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] raw bank transfer instructions
|
|
44
|
+
def bank_transfer_instructions
|
|
45
|
+
Airwallex.client.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,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] raw wallet info response
|
|
79
|
+
def self.wallet_info(params = {})
|
|
80
|
+
Airwallex.client.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
|