airwallex 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +93 -0
- data/README.md +274 -9
- data/lib/airwallex/list_object.rb +10 -3
- 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 +135 -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,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Airwallex
|
|
4
|
+
# Represents an Issuing Cardholder.
|
|
5
|
+
#
|
|
6
|
+
# This gem currently only implements the sandbox Simulation endpoint for
|
|
7
|
+
# bypassing a cardholder's RFI review stage; it does not yet implement the
|
|
8
|
+
# live create/retrieve/list endpoints.
|
|
9
|
+
# See https://www.airwallex.com/docs/api/simulation/issuing-cardholders
|
|
10
|
+
#
|
|
11
|
+
# @example Bypass a cardholder's pending review
|
|
12
|
+
# Airwallex::Cardholder.simulate_pass_review("chd_123")
|
|
13
|
+
class Cardholder < APIResource
|
|
14
|
+
# @return [String] API resource path for simulated cardholder actions
|
|
15
|
+
def self.resource_path
|
|
16
|
+
"/api/v1/simulation/issuing/cardholders"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Simulate bypassing this cardholder's RFI review stage
|
|
20
|
+
#
|
|
21
|
+
# @param cardholder_id [String]
|
|
22
|
+
# @return [Cardholder]
|
|
23
|
+
def self.simulate_pass_review(cardholder_id)
|
|
24
|
+
response = Airwallex.client.post("#{resource_path}/#{cardholder_id}/pass_review", {})
|
|
25
|
+
new(response)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -57,11 +57,29 @@ module Airwallex
|
|
|
57
57
|
CURRENT_ACCOUNT_PATH = "/api/v1/account"
|
|
58
58
|
WALLET_INFO_PATH = "/api/v1/account/wallet_info"
|
|
59
59
|
|
|
60
|
+
# Sandbox-only — see https://www.airwallex.com/docs/api/simulation/connected-accounts
|
|
61
|
+
SIMULATION_PATH = "/api/v1/simulation/accounts"
|
|
62
|
+
|
|
60
63
|
# @return [String] API resource path for connected accounts
|
|
61
64
|
def self.resource_path
|
|
62
65
|
"/api/v1/accounts"
|
|
63
66
|
end
|
|
64
67
|
|
|
68
|
+
# Simulate this account's status transitioning. The account must
|
|
69
|
+
# currently be SUBMITTED.
|
|
70
|
+
#
|
|
71
|
+
# @param account_id [String]
|
|
72
|
+
# @param next_status [String] one of "ACTIVE", "SUSPENDED",
|
|
73
|
+
# "ACTION_REQUIRED"
|
|
74
|
+
# @param force [Boolean] defaults to true
|
|
75
|
+
# @return [ConnectedAccount]
|
|
76
|
+
def self.simulate_update_status(account_id, next_status:, force: true)
|
|
77
|
+
response = Airwallex.client.post(
|
|
78
|
+
"#{SIMULATION_PATH}/#{account_id}/update_status", next_status: next_status, force: force
|
|
79
|
+
)
|
|
80
|
+
new(response)
|
|
81
|
+
end
|
|
82
|
+
|
|
65
83
|
# Retrieve whichever account the current request is authenticated as
|
|
66
84
|
#
|
|
67
85
|
# @param params [Hash] additional params
|
|
@@ -132,5 +150,36 @@ module Airwallex
|
|
|
132
150
|
refresh_from(response)
|
|
133
151
|
self
|
|
134
152
|
end
|
|
153
|
+
|
|
154
|
+
# Simulate this account's status transitioning. The account must
|
|
155
|
+
# currently be SUBMITTED.
|
|
156
|
+
#
|
|
157
|
+
# @param next_status [String] one of "ACTIVE", "SUSPENDED",
|
|
158
|
+
# "ACTION_REQUIRED"
|
|
159
|
+
# @param force [Boolean] defaults to true
|
|
160
|
+
# @return [ConnectedAccount] self
|
|
161
|
+
def simulate_update_status(next_status:, force: true)
|
|
162
|
+
response = Airwallex.client.post(
|
|
163
|
+
"#{self.class::SIMULATION_PATH}/#{id}/update_status", next_status: next_status, force: force
|
|
164
|
+
)
|
|
165
|
+
refresh_from(response)
|
|
166
|
+
self
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Simulate a pending offboarding on this account completing
|
|
170
|
+
#
|
|
171
|
+
# @param offboarding_id [String]
|
|
172
|
+
# @return [AccountOffboarding]
|
|
173
|
+
def simulate_complete_offboarding(offboarding_id)
|
|
174
|
+
AccountOffboarding.simulate_complete(id, offboarding_id)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Simulate a pending offboarding on this account being cancelled
|
|
178
|
+
#
|
|
179
|
+
# @param offboarding_id [String]
|
|
180
|
+
# @return [AccountOffboarding]
|
|
181
|
+
def simulate_cancel_offboarding(offboarding_id)
|
|
182
|
+
AccountOffboarding.simulate_cancel(id, offboarding_id)
|
|
183
|
+
end
|
|
135
184
|
end
|
|
136
185
|
end
|
|
@@ -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
|