airwallex 0.8.0 → 0.9.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 +73 -0
- data/README.md +243 -8
- data/lib/airwallex/resources/account_amendment.rb +39 -0
- data/lib/airwallex/resources/account_offboarding.rb +43 -0
- data/lib/airwallex/resources/cardholder.rb +28 -0
- data/lib/airwallex/resources/connected_account.rb +49 -0
- data/lib/airwallex/resources/deposit.rb +115 -0
- data/lib/airwallex/resources/dispute.rb +83 -1
- data/lib/airwallex/resources/issuing_transaction.rb +100 -0
- data/lib/airwallex/resources/linked_account.rb +64 -0
- data/lib/airwallex/resources/payment_consent.rb +15 -0
- data/lib/airwallex/resources/payment_intent.rb +26 -0
- data/lib/airwallex/resources/pos_terminal.rb +71 -0
- data/lib/airwallex/resources/rfi.rb +77 -0
- data/lib/airwallex/resources/transfer.rb +36 -0
- data/lib/airwallex/version.rb +1 -1
- data/lib/airwallex.rb +7 -0
- metadata +10 -3
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents a Deposit — funds arriving in your Airwallex account, either
|
|
5
|
+
# as an inbound bank transfer into a Global Account (type: BANK_TRANSFER)
|
|
6
|
+
# or a Direct Debit pull from a verified LinkedAccount (type:
|
|
7
|
+
# DIRECT_DEBIT, made via .create).
|
|
8
|
+
#
|
|
9
|
+
# The sandbox Simulation actions split cleanly along that same line:
|
|
10
|
+
# .simulate_create simulates a BANK_TRANSFER deposit landing, and it
|
|
11
|
+
# auto-settles on its own within a few seconds — same as a real bank
|
|
12
|
+
# transfer, there's no PENDING state to force through. .simulate_settle/
|
|
13
|
+
# .simulate_reject/.simulate_reverse only operate on DIRECT_DEBIT
|
|
14
|
+
# deposits made via .create, since a real direct debit pull takes days
|
|
15
|
+
# to clear and the sandbox has no other way to resolve it. Calling
|
|
16
|
+
# settle/reject/reverse on a BANK_TRANSFER deposit's id returns a 404
|
|
17
|
+
# "Deposit does not exist" — confirmed against the real sandbox.
|
|
18
|
+
# See https://www.airwallex.com/docs/api/simulation/deposits
|
|
19
|
+
#
|
|
20
|
+
# @example Simulate an inbound bank transfer landing (settles on its own)
|
|
21
|
+
# Airwallex::Deposit.simulate_create(amount: 100.00, global_account_id: "gacc_123")
|
|
22
|
+
#
|
|
23
|
+
# @example Pull via Direct Debit, then force it through the sandbox lifecycle
|
|
24
|
+
# deposit = Airwallex::Deposit.create(
|
|
25
|
+
# funding_source_id: "la_123", # a verified LinkedAccount id
|
|
26
|
+
# amount: 50.00,
|
|
27
|
+
# currency: "AUD"
|
|
28
|
+
# )
|
|
29
|
+
# deposit.simulate_settle
|
|
30
|
+
class Deposit < APIResource
|
|
31
|
+
extend APIOperations::Create
|
|
32
|
+
extend APIOperations::Retrieve
|
|
33
|
+
extend APIOperations::List
|
|
34
|
+
|
|
35
|
+
# Airwallex's own API is inconsistent here: simulating a bank-transfer
|
|
36
|
+
# deposit landing uses the singular "deposit", while every other
|
|
37
|
+
# simulation action on an existing deposit uses the plural "deposits".
|
|
38
|
+
SIMULATE_CREATE_PATH = "/api/v1/simulation/deposit/create"
|
|
39
|
+
SIMULATION_PATH = "/api/v1/simulation/deposits"
|
|
40
|
+
|
|
41
|
+
# @return [String] API resource path for deposits
|
|
42
|
+
def self.resource_path
|
|
43
|
+
"/api/v1/deposits"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Simulate an inbound bank-transfer deposit landing in a Global
|
|
47
|
+
# Account
|
|
48
|
+
#
|
|
49
|
+
# @param params [Hash] amount:, global_account_id: (required);
|
|
50
|
+
# payer_bankname:, payer_country:, payer_name:, reference:,
|
|
51
|
+
# statement_ref:, status: ("PENDING", "REJECTED", or "SETTLED",
|
|
52
|
+
# defaults to "SETTLED") (optional)
|
|
53
|
+
# @return [Deposit]
|
|
54
|
+
def self.simulate_create(params = {})
|
|
55
|
+
response = Airwallex.client.post(SIMULATE_CREATE_PATH, params)
|
|
56
|
+
new(response)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Simulate a PENDING Direct Debit deposit (made via .create) settling
|
|
60
|
+
#
|
|
61
|
+
# @param deposit_id [String]
|
|
62
|
+
# @return [Deposit]
|
|
63
|
+
def self.simulate_settle(deposit_id)
|
|
64
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{deposit_id}/settle", {})
|
|
65
|
+
new(response)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Simulate a PENDING Direct Debit deposit (made via .create) being
|
|
69
|
+
# rejected
|
|
70
|
+
#
|
|
71
|
+
# @param deposit_id [String]
|
|
72
|
+
# @return [Deposit]
|
|
73
|
+
def self.simulate_reject(deposit_id)
|
|
74
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{deposit_id}/reject", {})
|
|
75
|
+
new(response)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Simulate reversing a SETTLED Direct Debit deposit (creates an
|
|
79
|
+
# offsetting settled deposit and deactivates the LinkedAccount)
|
|
80
|
+
#
|
|
81
|
+
# @param deposit_id [String]
|
|
82
|
+
# @return [Deposit]
|
|
83
|
+
def self.simulate_reverse(deposit_id)
|
|
84
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{deposit_id}/reverse", {})
|
|
85
|
+
new(response)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Simulate this Direct Debit deposit settling
|
|
89
|
+
#
|
|
90
|
+
# @return [Deposit] self
|
|
91
|
+
def simulate_settle
|
|
92
|
+
response = Airwallex.client.post("#{self.class::SIMULATION_PATH}/#{id}/settle", {})
|
|
93
|
+
refresh_from(response)
|
|
94
|
+
self
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Simulate this Direct Debit deposit being rejected
|
|
98
|
+
#
|
|
99
|
+
# @return [Deposit] self
|
|
100
|
+
def simulate_reject
|
|
101
|
+
response = Airwallex.client.post("#{self.class::SIMULATION_PATH}/#{id}/reject", {})
|
|
102
|
+
refresh_from(response)
|
|
103
|
+
self
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Simulate reversing this Direct Debit deposit
|
|
107
|
+
#
|
|
108
|
+
# @return [Deposit] self
|
|
109
|
+
def simulate_reverse
|
|
110
|
+
response = Airwallex.client.post("#{self.class::SIMULATION_PATH}/#{id}/reverse", {})
|
|
111
|
+
refresh_from(response)
|
|
112
|
+
self
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -5,7 +5,11 @@ module Airwallex
|
|
|
5
5
|
#
|
|
6
6
|
# Disputes represent chargebacks or payment disputes initiated by cardholders.
|
|
7
7
|
# Merchants can view disputes, challenge them with evidence, or accept them.
|
|
8
|
-
# There is no create — disputes originate from card
|
|
8
|
+
# There is no live create — real disputes originate from card
|
|
9
|
+
# networks/issuing banks. In the sandbox, `.simulate_create` (and
|
|
10
|
+
# `#simulate_escalate`/`#simulate_resolve`) stand in for the card
|
|
11
|
+
# network/issuing bank to drive the full dispute lifecycle for testing.
|
|
12
|
+
# See https://www.airwallex.com/docs/api/simulation/payment-disputes
|
|
9
13
|
#
|
|
10
14
|
# @example List open disputes
|
|
11
15
|
# disputes = Airwallex::Dispute.list(status: 'OPEN')
|
|
@@ -18,15 +22,70 @@ module Airwallex
|
|
|
18
22
|
#
|
|
19
23
|
# @example Challenge a dispute
|
|
20
24
|
# dispute.challenge(...)
|
|
25
|
+
#
|
|
26
|
+
# @example Simulate the full sandbox lifecycle
|
|
27
|
+
# dispute = Airwallex::Dispute.simulate_create(
|
|
28
|
+
# payment_intent_id: "int_123",
|
|
29
|
+
# reason_code: "4853",
|
|
30
|
+
# stage: "CHARGEBACK",
|
|
31
|
+
# due_at: "2026-12-01T23:59:59Z"
|
|
32
|
+
# )
|
|
33
|
+
# dispute.challenge(customer_communication: "Email thread")
|
|
34
|
+
# dispute.simulate_resolve(in_favor_of: "MERCHANT")
|
|
21
35
|
class Dispute < APIResource
|
|
22
36
|
extend APIOperations::Retrieve
|
|
23
37
|
extend APIOperations::List
|
|
24
38
|
include APIOperations::Update
|
|
25
39
|
|
|
40
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/payment-disputes
|
|
41
|
+
SIMULATION_PATH = "/api/v1/simulation/pa/payment_disputes"
|
|
42
|
+
|
|
26
43
|
def self.resource_path
|
|
27
44
|
"/api/v1/pa/payment_disputes"
|
|
28
45
|
end
|
|
29
46
|
|
|
47
|
+
# Simulate a card network/issuing bank raising a dispute against a
|
|
48
|
+
# PaymentIntent
|
|
49
|
+
#
|
|
50
|
+
# @param params [Hash] payment_intent_id:, reason_code: (card-brand
|
|
51
|
+
# specific, e.g. Mastercard "4853", Visa "10.4"), stage: (one of
|
|
52
|
+
# "RFI", "PRE_CHARGEBACK", "CHARGEBACK", "PRE_ARBITRATION",
|
|
53
|
+
# "ARBITRATION"), due_at: (required); amount:, comment:, documents:
|
|
54
|
+
# (optional)
|
|
55
|
+
# @return [Dispute]
|
|
56
|
+
def self.simulate_create(params = {})
|
|
57
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/create", params)
|
|
58
|
+
new(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Simulate the issuing bank rejecting the merchant's challenge evidence
|
|
62
|
+
# and advancing the dispute to the next stage (e.g. Chargeback ->
|
|
63
|
+
# Pre-arbitration). Not valid while status is REQUIRES_RESPONSE — the
|
|
64
|
+
# merchant must #accept or #challenge first.
|
|
65
|
+
#
|
|
66
|
+
# @param dispute_id [String]
|
|
67
|
+
# @param params [Hash] due_at: (required); amount:, comment:,
|
|
68
|
+
# documents: (optional)
|
|
69
|
+
# @return [Dispute]
|
|
70
|
+
def self.simulate_escalate(dispute_id, params = {})
|
|
71
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{dispute_id}/escalate", params)
|
|
72
|
+
new(response)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Simulate the issuing bank's final decision on a dispute.
|
|
76
|
+
# in_favor_of: "MERCHANT" resolves it WON/REVERSED; "CUSTOMER" resolves
|
|
77
|
+
# it LOST. Not valid while status is REQUIRES_RESPONSE — the merchant
|
|
78
|
+
# must #accept or #challenge first.
|
|
79
|
+
#
|
|
80
|
+
# @param dispute_id [String]
|
|
81
|
+
# @param params [Hash] in_favor_of: ("MERCHANT" or "CUSTOMER",
|
|
82
|
+
# required); amount: (optional, defaults to the full disputed amount)
|
|
83
|
+
# @return [Dispute]
|
|
84
|
+
def self.simulate_resolve(dispute_id, params = {})
|
|
85
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{dispute_id}/resolve", params)
|
|
86
|
+
new(response)
|
|
87
|
+
end
|
|
88
|
+
|
|
30
89
|
# Accept a dispute without challenging it
|
|
31
90
|
#
|
|
32
91
|
# @return [Airwallex::Dispute] self
|
|
@@ -47,6 +106,29 @@ module Airwallex
|
|
|
47
106
|
self
|
|
48
107
|
end
|
|
49
108
|
|
|
109
|
+
# Simulate the issuing bank rejecting this dispute's challenge evidence
|
|
110
|
+
# and advancing it to the next stage
|
|
111
|
+
#
|
|
112
|
+
# @param params [Hash] due_at: (required); amount:, comment:,
|
|
113
|
+
# documents: (optional)
|
|
114
|
+
# @return [Airwallex::Dispute] self
|
|
115
|
+
def simulate_escalate(params = {})
|
|
116
|
+
response = Airwallex.client.post("#{self.class::SIMULATION_PATH}/#{id}/escalate", params)
|
|
117
|
+
refresh_from(response)
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Simulate the issuing bank's final decision on this dispute
|
|
122
|
+
#
|
|
123
|
+
# @param params [Hash] in_favor_of: ("MERCHANT" or "CUSTOMER",
|
|
124
|
+
# required); amount: (optional)
|
|
125
|
+
# @return [Airwallex::Dispute] self
|
|
126
|
+
def simulate_resolve(params = {})
|
|
127
|
+
response = Airwallex.client.post("#{self.class::SIMULATION_PATH}/#{id}/resolve", params)
|
|
128
|
+
refresh_from(response)
|
|
129
|
+
self
|
|
130
|
+
end
|
|
131
|
+
|
|
50
132
|
# List payment intents related to this dispute
|
|
51
133
|
#
|
|
52
134
|
# @param params [Hash] additional query params (e.g. pagination)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents a card (Issuing) transaction event.
|
|
5
|
+
#
|
|
6
|
+
# This gem currently only implements the sandbox Simulation endpoints for
|
|
7
|
+
# issuing transactions — creating an authorization, capturing/reversing a
|
|
8
|
+
# PENDING one, refunding a CAPTURED one, and delivering a 3DS delegation
|
|
9
|
+
# notification. It does not yet implement the live Issuing resources
|
|
10
|
+
# (Card, Cardholder) or the live transaction retrieve/list endpoints.
|
|
11
|
+
# See https://www.airwallex.com/docs/api/simulation/issuing-transactions
|
|
12
|
+
#
|
|
13
|
+
# @example Authorize, then capture in full
|
|
14
|
+
# txn = Airwallex::IssuingTransaction.simulate_create(
|
|
15
|
+
# card_id: "card_123",
|
|
16
|
+
# transaction_amount: 25.00,
|
|
17
|
+
# transaction_currency: "USD"
|
|
18
|
+
# )
|
|
19
|
+
# Airwallex::IssuingTransaction.simulate_capture(txn.transaction_id)
|
|
20
|
+
#
|
|
21
|
+
# @example Authorize and clear in a single step
|
|
22
|
+
# Airwallex::IssuingTransaction.simulate_create(
|
|
23
|
+
# card_id: "card_123",
|
|
24
|
+
# transaction_amount: 25.00,
|
|
25
|
+
# transaction_currency: "USD",
|
|
26
|
+
# single_phase: true
|
|
27
|
+
# )
|
|
28
|
+
class IssuingTransaction < APIResource
|
|
29
|
+
# @return [String] API resource path for simulated issuing transactions
|
|
30
|
+
def self.resource_path
|
|
31
|
+
"/api/v1/simulation/issuing"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Simulate a card authorization (or, with single_phase: true, an
|
|
35
|
+
# authorization cleared in one step)
|
|
36
|
+
#
|
|
37
|
+
# @param params [Hash] card_id: (or card_number:), transaction_amount:,
|
|
38
|
+
# transaction_currency: (required); single_phase:, auth_code:,
|
|
39
|
+
# merchant_category_code:, merchant_info:, transaction_failure_reason:
|
|
40
|
+
# (optional)
|
|
41
|
+
# @return [IssuingTransaction] the resulting transaction — read its
|
|
42
|
+
# `transaction_id` to pass to .simulate_capture/.simulate_reverse
|
|
43
|
+
def self.simulate_create(params = {})
|
|
44
|
+
response = Airwallex.client.post("#{resource_path}/create", params)
|
|
45
|
+
new(response)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Simulate capturing a PENDING transaction
|
|
49
|
+
#
|
|
50
|
+
# @param transaction_id [String] the `transaction_id` from
|
|
51
|
+
# .simulate_create's response
|
|
52
|
+
# @param params [Hash] merchant_info:, transaction_amount: (optional —
|
|
53
|
+
# a partial capture if less than the authorized amount, full amount
|
|
54
|
+
# if omitted)
|
|
55
|
+
# @return [IssuingTransaction]
|
|
56
|
+
def self.simulate_capture(transaction_id, params = {})
|
|
57
|
+
response = Airwallex.client.post(
|
|
58
|
+
"#{resource_path}/card_transaction_lifecycles/#{transaction_id}/capture", params
|
|
59
|
+
)
|
|
60
|
+
new(response)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Simulate reversing a PENDING transaction
|
|
64
|
+
#
|
|
65
|
+
# @param transaction_id [String] the `transaction_id` from
|
|
66
|
+
# .simulate_create's response
|
|
67
|
+
# @param params [Hash] transaction_amount: (optional — a partial
|
|
68
|
+
# reversal if less than the authorized amount, full reversal if
|
|
69
|
+
# omitted)
|
|
70
|
+
# @return [IssuingTransaction]
|
|
71
|
+
def self.simulate_reverse(transaction_id, params = {})
|
|
72
|
+
response = Airwallex.client.post(
|
|
73
|
+
"#{resource_path}/card_transaction_lifecycles/#{transaction_id}/reverse", params
|
|
74
|
+
)
|
|
75
|
+
new(response)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Simulate refunding a CAPTURED, not-fully-refunded transaction back to
|
|
79
|
+
# the card
|
|
80
|
+
#
|
|
81
|
+
# @param params [Hash] card_id: (or card_number:), transaction_amount:,
|
|
82
|
+
# transaction_currency: (required); merchant_category_code:,
|
|
83
|
+
# merchant_info: (optional)
|
|
84
|
+
# @return [IssuingTransaction]
|
|
85
|
+
def self.simulate_refund(params = {})
|
|
86
|
+
response = Airwallex.client.post("#{resource_path}/refund", params)
|
|
87
|
+
new(response)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Simulate a 3DS delegation-mode notification for a card
|
|
91
|
+
#
|
|
92
|
+
# @param params [Hash] card_number: (required); merchant_info: (object,
|
|
93
|
+
# optional: acquirer_id:, merchant_category_code:,
|
|
94
|
+
# merchant_country_code:, merchant_id:, merchant_name:, merchant_url:)
|
|
95
|
+
# @return [Hash] raw response
|
|
96
|
+
def self.simulate_notify_three_ds(params = {})
|
|
97
|
+
Airwallex.client.post("#{resource_path}/threeds/notify", params)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents a Linked Account — a customer's external bank account
|
|
5
|
+
# authorized for Direct Debit pulls.
|
|
6
|
+
#
|
|
7
|
+
# This gem currently only implements the sandbox Simulation endpoints for
|
|
8
|
+
# the mandate and micro-deposit lifecycle; it does not yet implement the
|
|
9
|
+
# live create/retrieve/list endpoints. All four actions below return
|
|
10
|
+
# HTTP 200 with an empty body on success, so they return `true` rather
|
|
11
|
+
# than a resource instance.
|
|
12
|
+
# See https://www.airwallex.com/docs/api/simulation/linked-accounts
|
|
13
|
+
#
|
|
14
|
+
# MANDATE STATUS LIFECYCLE:
|
|
15
|
+
# PROCESSING -> ACTIVE (simulate_accept_mandate)
|
|
16
|
+
# PROCESSING -> INACTIVE (simulate_reject_mandate)
|
|
17
|
+
# PROCESSING or ACTIVE -> INACTIVE (simulate_cancel_mandate)
|
|
18
|
+
#
|
|
19
|
+
# @example Approve a mandate that's awaiting the customer's bank
|
|
20
|
+
# Airwallex::LinkedAccount.simulate_accept_mandate("la_123")
|
|
21
|
+
class LinkedAccount < APIResource
|
|
22
|
+
# @return [String] API resource path for simulated linked account actions
|
|
23
|
+
def self.resource_path
|
|
24
|
+
"/api/v1/simulation/linked_accounts"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Simulate the mandate transitioning PROCESSING -> ACTIVE
|
|
28
|
+
#
|
|
29
|
+
# @param linked_account_id [String]
|
|
30
|
+
# @return [true]
|
|
31
|
+
def self.simulate_accept_mandate(linked_account_id)
|
|
32
|
+
Airwallex.client.post("#{resource_path}/#{linked_account_id}/mandate/accept", {})
|
|
33
|
+
true
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Simulate the mandate transitioning PROCESSING -> INACTIVE
|
|
37
|
+
#
|
|
38
|
+
# @param linked_account_id [String]
|
|
39
|
+
# @return [true]
|
|
40
|
+
def self.simulate_reject_mandate(linked_account_id)
|
|
41
|
+
Airwallex.client.post("#{resource_path}/#{linked_account_id}/mandate/reject", {})
|
|
42
|
+
true
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Simulate the mandate transitioning PROCESSING or ACTIVE -> INACTIVE
|
|
46
|
+
#
|
|
47
|
+
# @param linked_account_id [String]
|
|
48
|
+
# @return [true]
|
|
49
|
+
def self.simulate_cancel_mandate(linked_account_id)
|
|
50
|
+
Airwallex.client.post("#{resource_path}/#{linked_account_id}/mandate/cancel", {})
|
|
51
|
+
true
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Simulate a failed micro-deposit verification, transitioning the
|
|
55
|
+
# linked account itself from REQUIRES_ACTION to FAILED
|
|
56
|
+
#
|
|
57
|
+
# @param linked_account_id [String]
|
|
58
|
+
# @return [true]
|
|
59
|
+
def self.simulate_fail_microdeposits(linked_account_id)
|
|
60
|
+
Airwallex.client.post("#{resource_path}/#{linked_account_id}/fail_microdeposits", {})
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -33,6 +33,9 @@ module Airwallex
|
|
|
33
33
|
"/api/v1/pa/payment_consents"
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/shopper-actions
|
|
37
|
+
SIMULATION_SHOPPER_ACTION_PATH = "/api/v1/simulation/pa/shopper_actions"
|
|
38
|
+
|
|
36
39
|
# Verify this consent (e.g. via a zero/low-value authorization) before
|
|
37
40
|
# it can be used for off-session charges
|
|
38
41
|
#
|
|
@@ -64,5 +67,17 @@ module Airwallex
|
|
|
64
67
|
refresh_from(response)
|
|
65
68
|
self
|
|
66
69
|
end
|
|
70
|
+
|
|
71
|
+
# Simulate the shopper completing a redirect/3DS challenge raised
|
|
72
|
+
# during #verify, using the `url` from the verify response's
|
|
73
|
+
# next_action
|
|
74
|
+
#
|
|
75
|
+
# @param url [String] the redirect URL from #verify's next_action
|
|
76
|
+
# @return [PaymentConsent] self
|
|
77
|
+
def simulate_shopper_verify(url:)
|
|
78
|
+
response = Airwallex.client.post("#{SIMULATION_SHOPPER_ACTION_PATH}/verify", url: url)
|
|
79
|
+
refresh_from(response)
|
|
80
|
+
self
|
|
81
|
+
end
|
|
67
82
|
end
|
|
68
83
|
end
|
|
@@ -7,6 +7,9 @@ module Airwallex
|
|
|
7
7
|
extend APIOperations::List
|
|
8
8
|
include APIOperations::Update
|
|
9
9
|
|
|
10
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/shopper-actions
|
|
11
|
+
SIMULATION_SHOPPER_ACTION_PATH = "/api/v1/simulation/pa/shopper_actions"
|
|
12
|
+
|
|
10
13
|
def self.resource_path
|
|
11
14
|
"/api/v1/pa/payment_intents"
|
|
12
15
|
end
|
|
@@ -40,5 +43,28 @@ module Airwallex
|
|
|
40
43
|
refresh_from(response)
|
|
41
44
|
self
|
|
42
45
|
end
|
|
46
|
+
|
|
47
|
+
# Simulate the shopper completing a redirect/3DS challenge raised during
|
|
48
|
+
# #confirm, using the `url` from the confirm response's next_action
|
|
49
|
+
#
|
|
50
|
+
# @param url [String] the redirect URL from #confirm's next_action
|
|
51
|
+
# @return [PaymentIntent] self
|
|
52
|
+
def simulate_shopper_pay(url:)
|
|
53
|
+
response = Airwallex.client.post("#{SIMULATION_SHOPPER_ACTION_PATH}/pay", url: url)
|
|
54
|
+
refresh_from(response)
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Simulate the shopper abandoning/rejecting a redirect/3DS challenge
|
|
59
|
+
# raised during #confirm, using the `url` from the confirm response's
|
|
60
|
+
# next_action
|
|
61
|
+
#
|
|
62
|
+
# @param url [String] the redirect URL from #confirm's next_action
|
|
63
|
+
# @return [PaymentIntent] self
|
|
64
|
+
def simulate_shopper_reject(url:)
|
|
65
|
+
response = Airwallex.client.post("#{SIMULATION_SHOPPER_ACTION_PATH}/reject", url: url)
|
|
66
|
+
refresh_from(response)
|
|
67
|
+
self
|
|
68
|
+
end
|
|
43
69
|
end
|
|
44
70
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents an in-person (POS) Terminal used for card-present payments.
|
|
5
|
+
#
|
|
6
|
+
# This gem currently only implements the sandbox Simulation endpoints for
|
|
7
|
+
# driving a terminal through a test payment; it does not yet implement the
|
|
8
|
+
# live terminal create/retrieve/list endpoints.
|
|
9
|
+
# See https://www.airwallex.com/docs/api/simulation/pos-terminals
|
|
10
|
+
#
|
|
11
|
+
# @example Simulate a terminal completing a PaymentIntent
|
|
12
|
+
# Airwallex::POSTerminal.simulate_turn_on(terminal_id: "term_123")
|
|
13
|
+
# Airwallex::POSTerminal.simulate_confirm_payment_intent(
|
|
14
|
+
# terminal_id: "term_123",
|
|
15
|
+
# payment_scenario_name: "approve"
|
|
16
|
+
# )
|
|
17
|
+
class POSTerminal < APIResource
|
|
18
|
+
# @return [String] API resource path for simulated POS terminal actions
|
|
19
|
+
def self.resource_path
|
|
20
|
+
"/api/v1/simulation/pa/pos/terminals"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Simulate turning a terminal on
|
|
24
|
+
#
|
|
25
|
+
# @param terminal_id [String]
|
|
26
|
+
# @return [POSTerminal]
|
|
27
|
+
def self.simulate_turn_on(terminal_id:)
|
|
28
|
+
response = Airwallex.client.post("#{resource_path}/turn_on", terminal_id: terminal_id)
|
|
29
|
+
new(response)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Simulate turning a terminal off
|
|
33
|
+
#
|
|
34
|
+
# @param terminal_id [String]
|
|
35
|
+
# @return [POSTerminal]
|
|
36
|
+
def self.simulate_turn_off(terminal_id:)
|
|
37
|
+
response = Airwallex.client.post("#{resource_path}/turn_off", terminal_id: terminal_id)
|
|
38
|
+
new(response)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Simulate generating a terminal activation code
|
|
42
|
+
#
|
|
43
|
+
# @param request_id [String]
|
|
44
|
+
# @return [Hash] raw response (contains the generated activation code)
|
|
45
|
+
def self.simulate_generate_activation_code(request_id:)
|
|
46
|
+
Airwallex.client.post("#{resource_path}/generate_activation_code", request_id: request_id)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Simulate a terminal confirming a PaymentIntent under a named test
|
|
50
|
+
# scenario (see .simulate_payment_scenarios for valid names)
|
|
51
|
+
#
|
|
52
|
+
# @param terminal_id [String]
|
|
53
|
+
# @param payment_scenario_name [String]
|
|
54
|
+
# @return [POSTerminal]
|
|
55
|
+
def self.simulate_confirm_payment_intent(terminal_id:, payment_scenario_name:)
|
|
56
|
+
response = Airwallex.client.post(
|
|
57
|
+
"#{resource_path}/confirm_payment_intent",
|
|
58
|
+
terminal_id: terminal_id,
|
|
59
|
+
payment_scenario_name: payment_scenario_name
|
|
60
|
+
)
|
|
61
|
+
new(response)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# List the test scenario names available to .simulate_confirm_payment_intent
|
|
65
|
+
#
|
|
66
|
+
# @return [Array, Hash] raw response
|
|
67
|
+
def self.simulate_payment_scenarios
|
|
68
|
+
Airwallex.client.get("#{resource_path}/payment_scenarios")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents a Request for Information (RFI) — a compliance question
|
|
5
|
+
# Airwallex raises against your account (KYC, an ongoing KYC review, a
|
|
6
|
+
# cardholder, a transaction, payment enablement, or merchant risk).
|
|
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
|
|
11
|
+
# https://www.airwallex.com/docs/api/simulation/request-for-information
|
|
12
|
+
#
|
|
13
|
+
# @example Raise a KYC RFI, then close it
|
|
14
|
+
# rfi = Airwallex::RFI.simulate_create(
|
|
15
|
+
# type: "KYC",
|
|
16
|
+
# questions: [{ answer: { type: "TEXT" } }]
|
|
17
|
+
# )
|
|
18
|
+
# rfi.simulate_close
|
|
19
|
+
class RFI < APIResource
|
|
20
|
+
# @return [String] API resource path for simulated RFI actions
|
|
21
|
+
def self.resource_path
|
|
22
|
+
"/api/v1/simulation/rfis"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Simulate Airwallex raising an RFI
|
|
26
|
+
#
|
|
27
|
+
# @param params [Hash] type: (required, one of "KYC", "KYC_ONGOING",
|
|
28
|
+
# "CARDHOLDER", "TRANSACTION", "PAYMENT_ENABLEMENT",
|
|
29
|
+
# "MERCHANT_RISK"); questions: (required, array of
|
|
30
|
+
# { answer: { type: }, sources: [] })
|
|
31
|
+
# @return [RFI]
|
|
32
|
+
def self.simulate_create(params = {})
|
|
33
|
+
response = Airwallex.client.post("#{resource_path}/create", params)
|
|
34
|
+
new(response)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Simulate closing an RFI
|
|
38
|
+
#
|
|
39
|
+
# @param rfi_id [String]
|
|
40
|
+
# @return [RFI]
|
|
41
|
+
def self.simulate_close(rfi_id)
|
|
42
|
+
response = Airwallex.client.post("#{resource_path}/#{rfi_id}/close", {})
|
|
43
|
+
new(response)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Simulate a follow-up on an RFI — reopen an existing answered question
|
|
47
|
+
# (by id) or append a new one
|
|
48
|
+
#
|
|
49
|
+
# @param rfi_id [String]
|
|
50
|
+
# @param params [Hash] questions: (required, array of
|
|
51
|
+
# { id: } or { answer: { type: }, sources: [] })
|
|
52
|
+
# @return [RFI]
|
|
53
|
+
def self.simulate_follow_up(rfi_id, params = {})
|
|
54
|
+
response = Airwallex.client.post("#{resource_path}/#{rfi_id}/follow_up", params)
|
|
55
|
+
new(response)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Simulate closing this RFI
|
|
59
|
+
#
|
|
60
|
+
# @return [RFI] self
|
|
61
|
+
def simulate_close
|
|
62
|
+
response = Airwallex.client.post("#{self.class.resource_path}/#{id}/close", {})
|
|
63
|
+
refresh_from(response)
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Simulate a follow-up on this RFI
|
|
68
|
+
#
|
|
69
|
+
# @param params [Hash] questions: (required)
|
|
70
|
+
# @return [RFI] self
|
|
71
|
+
def simulate_follow_up(params = {})
|
|
72
|
+
response = Airwallex.client.post("#{self.class.resource_path}/#{id}/follow_up", params)
|
|
73
|
+
refresh_from(response)
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -6,6 +6,9 @@ module Airwallex
|
|
|
6
6
|
extend APIOperations::Retrieve
|
|
7
7
|
extend APIOperations::List
|
|
8
8
|
|
|
9
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/transfers
|
|
10
|
+
SIMULATION_PATH = "/api/v1/simulation/transfers"
|
|
11
|
+
|
|
9
12
|
def self.resource_path
|
|
10
13
|
"/api/v1/transfers"
|
|
11
14
|
end
|
|
@@ -19,5 +22,38 @@ module Airwallex
|
|
|
19
22
|
refresh_from(response)
|
|
20
23
|
self
|
|
21
24
|
end
|
|
25
|
+
|
|
26
|
+
# Simulate this transfer's status advancing one step. The standard
|
|
27
|
+
# lifecycle is SCHEDULED -> PROCESSING -> SENT -> PAID, progressed one
|
|
28
|
+
# step at a time; OVERDUE, FAILED, and CANCELLED can be jumped to
|
|
29
|
+
# directly.
|
|
30
|
+
#
|
|
31
|
+
# @param next_status [String] one of "OVERDUE", "PROCESSING", "SENT",
|
|
32
|
+
# "PAID", "FAILED", "CANCELLED"
|
|
33
|
+
# @param failure_type [String, nil] failure reason code, only
|
|
34
|
+
# meaningful when next_status is "FAILED"
|
|
35
|
+
# @return [Transfer] self
|
|
36
|
+
def simulate_transition(next_status:, failure_type: nil)
|
|
37
|
+
params = { next_status: next_status }
|
|
38
|
+
params[:failure_type] = failure_type if failure_type
|
|
39
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{id}/transition", params)
|
|
40
|
+
refresh_from(response)
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Simulate a transfer's status advancing one step
|
|
45
|
+
#
|
|
46
|
+
# @param transfer_id [String]
|
|
47
|
+
# @param next_status [String] one of "OVERDUE", "PROCESSING", "SENT",
|
|
48
|
+
# "PAID", "FAILED", "CANCELLED"
|
|
49
|
+
# @param failure_type [String, nil] failure reason code, only
|
|
50
|
+
# meaningful when next_status is "FAILED"
|
|
51
|
+
# @return [Transfer]
|
|
52
|
+
def self.simulate_transition(transfer_id, next_status:, failure_type: nil)
|
|
53
|
+
params = { next_status: next_status }
|
|
54
|
+
params[:failure_type] = failure_type if failure_type
|
|
55
|
+
response = Airwallex.client.post("#{SIMULATION_PATH}/#{transfer_id}/transition", params)
|
|
56
|
+
new(response)
|
|
57
|
+
end
|
|
22
58
|
end
|
|
23
59
|
end
|
data/lib/airwallex/version.rb
CHANGED
data/lib/airwallex.rb
CHANGED
|
@@ -48,6 +48,13 @@ require_relative "airwallex/resources/connected_account"
|
|
|
48
48
|
require_relative "airwallex/resources/account_amendment"
|
|
49
49
|
require_relative "airwallex/resources/funds_split"
|
|
50
50
|
require_relative "airwallex/resources/charge"
|
|
51
|
+
require_relative "airwallex/resources/deposit"
|
|
52
|
+
require_relative "airwallex/resources/issuing_transaction"
|
|
53
|
+
require_relative "airwallex/resources/cardholder"
|
|
54
|
+
require_relative "airwallex/resources/linked_account"
|
|
55
|
+
require_relative "airwallex/resources/account_offboarding"
|
|
56
|
+
require_relative "airwallex/resources/rfi"
|
|
57
|
+
require_relative "airwallex/resources/pos_terminal"
|
|
51
58
|
|
|
52
59
|
module Airwallex
|
|
53
60
|
class << self
|