@agent-cards/checkout 0.19.0 → 0.21.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.
- package/README.md +6 -753
- package/cdp.d.ts +1 -0
- package/cdp.js +2 -0
- package/index.d.ts +1 -0
- package/index.js +2 -0
- package/package.json +33 -33
- package/playwright.d.ts +1 -0
- package/playwright.js +2 -0
- package/preflight.d.ts +1 -0
- package/preflight.js +2 -0
- package/CHANGELOG.md +0 -132
- package/PREFLIGHT.md +0 -312
- package/dist/adyen-merchant-hosted.generated.d.ts +0 -277
- package/dist/adyen-merchant-hosted.generated.js +0 -1902
- package/dist/adyen.generated.d.ts +0 -24
- package/dist/adyen.generated.js +0 -64
- package/dist/attachment.d.ts +0 -11
- package/dist/attachment.js +0 -50
- package/dist/braintree.d.ts +0 -2
- package/dist/braintree.generated.d.ts +0 -10
- package/dist/braintree.generated.js +0 -302
- package/dist/braintree.js +0 -2
- package/dist/builtin-registry.generated.d.ts +0 -2
- package/dist/builtin-registry.generated.js +0 -1
- package/dist/card-fields.generated.d.ts +0 -3
- package/dist/card-fields.generated.js +0 -46
- package/dist/cdp.d.ts +0 -192
- package/dist/cdp.js +0 -2393
- package/dist/checkout-com.generated.d.ts +0 -4
- package/dist/checkout-com.generated.js +0 -183
- package/dist/client.d.ts +0 -849
- package/dist/client.js +0 -1754
- package/dist/cse-body.d.ts +0 -25
- package/dist/cse-body.js +0 -41
- package/dist/fiserv.d.ts +0 -65
- package/dist/fiserv.generated.d.ts +0 -73
- package/dist/fiserv.generated.js +0 -830
- package/dist/fiserv.js +0 -104
- package/dist/hosted-form.d.ts +0 -44
- package/dist/hosted-form.js +0 -78
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -10
- package/dist/lifecycle.d.ts +0 -179
- package/dist/lifecycle.js +0 -395
- package/dist/mercado-checkout.d.ts +0 -20
- package/dist/mercado-checkout.generated.d.ts +0 -52
- package/dist/mercado-checkout.generated.js +0 -198
- package/dist/mercado-checkout.js +0 -108
- package/dist/merchant-handoff.d.ts +0 -54
- package/dist/merchant-handoff.js +0 -100
- package/dist/merchant-hosted.d.ts +0 -140
- package/dist/merchant-hosted.js +0 -170
- package/dist/merchant-total-watch.d.ts +0 -115
- package/dist/merchant-total-watch.js +0 -268
- package/dist/merchant-total.d.ts +0 -257
- package/dist/merchant-total.js +0 -383
- package/dist/owned-shop.generated.d.ts +0 -24
- package/dist/owned-shop.generated.js +0 -108
- package/dist/paysafe.generated.d.ts +0 -12
- package/dist/paysafe.generated.js +0 -87
- package/dist/playwright.d.ts +0 -3
- package/dist/playwright.js +0 -3
- package/dist/pre-claim.d.ts +0 -123
- package/dist/pre-claim.js +0 -386
- package/dist/preflight-capabilities.generated.d.ts +0 -1253
- package/dist/preflight-capabilities.generated.js +0 -1929
- package/dist/preflight-catalog.json +0 -4727
- package/dist/preflight-playwright.d.ts +0 -34
- package/dist/preflight-playwright.js +0 -355
- package/dist/preflight-schemas.json +0 -1122
- package/dist/preflight.d.ts +0 -1
- package/dist/preflight.generated.d.ts +0 -1965
- package/dist/preflight.generated.js +0 -570
- package/dist/preflight.js +0 -2
- package/dist/preparation.d.ts +0 -38
- package/dist/preparation.js +0 -191
- package/dist/prepared-processor.d.ts +0 -43
- package/dist/prepared-processor.js +0 -172
- package/dist/recurly.generated.d.ts +0 -1
- package/dist/recurly.generated.js +0 -87
- package/dist/registry.d.ts +0 -121
- package/dist/registry.js +0 -310
- package/dist/spreedly.generated.d.ts +0 -10
- package/dist/spreedly.generated.js +0 -332
- package/dist/stripe-checkout.d.ts +0 -81
- package/dist/stripe-checkout.generated.d.ts +0 -82
- package/dist/stripe-checkout.generated.js +0 -1093
- package/dist/stripe-checkout.js +0 -140
- package/dist/substitute.d.ts +0 -38
- package/dist/substitute.js +0 -23
- package/dist/substitutions.generated.d.ts +0 -11
- package/dist/substitutions.generated.js +0 -818
- package/examples/existing-browser.mjs +0 -63
- package/examples/preflight/classify-direct.mjs +0 -21
- package/examples/preflight/classify-kernel.mjs +0 -30
- package/examples/preflight/inspect-browser.mjs +0 -44
- package/examples/preflight/kernel-native/README.md +0 -112
- package/examples/preflight/kernel-native/documented-adapters.json +0 -113
- package/examples/preflight/kernel-native/inventory.json +0 -233
- package/examples/preflight/kernel-native/qualification.mjs +0 -182
- package/examples/preflight/kernel-profile.empty.json +0 -11
- package/examples/preflight/mollie-hosted.observations.json +0 -23
- package/examples/preflight/mollie-hosted.result.json +0 -103
- package/examples/preflight/stripe-script.direct.result.json +0 -92
- package/examples/preflight/stripe-script.observations.json +0 -16
- package/examples/preflight/stripe-script.result.json +0 -87
package/dist/lifecycle.js
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { AdyenTestPlatformRefusedError, ApprovalDeclinedError, ApprovalTimeoutError, CheckoutCancelledError, CheckoutPreparationError, IntentNotConfirmableError, PaymentOutcomeUnknownError, ProcessorRefusedError } from './client.js';
|
|
2
|
-
import { parseOwnedShopReceipt, OWNED_SHOP_SKU, OWNED_SHOP_AMOUNT_CENTS, OWNED_SHOP_CURRENCY } from './owned-shop.generated.js';
|
|
3
|
-
/** How many times one approval's card-free Stripe confirmation may be checked before it is simply held. */
|
|
4
|
-
export const MAX_STRIPE_CONTINUATION_CHECKS = 3;
|
|
5
|
-
/** Shared by the raw CDP and Playwright transports. No browser ownership or payment execution lives here. */
|
|
6
|
-
export class CheckoutLifecycle {
|
|
7
|
-
options;
|
|
8
|
-
state = { status: 'idle', authorizationId: null };
|
|
9
|
-
held = false;
|
|
10
|
-
active = false;
|
|
11
|
-
cancelled = false;
|
|
12
|
-
merchantAborted = false;
|
|
13
|
-
unboundStripeToken = false;
|
|
14
|
-
// The page's own confirmation paying with that token: see claimStripeContinuation.
|
|
15
|
-
stripeTokenPrepared = false;
|
|
16
|
-
stripeContinuation = 'none';
|
|
17
|
-
stripeContinuationChecks = 0;
|
|
18
|
-
ownedShopOrderId = null;
|
|
19
|
-
reconciliation = null;
|
|
20
|
-
preparationHandler;
|
|
21
|
-
preparationUsed = false;
|
|
22
|
-
abort = new AbortController();
|
|
23
|
-
constructor(options) {
|
|
24
|
-
this.options = options;
|
|
25
|
-
}
|
|
26
|
-
getState() {
|
|
27
|
-
return { ...this.state, ...(this.state.confirmation ? { confirmation: { ...this.state.confirmation } } : {}) };
|
|
28
|
-
}
|
|
29
|
-
setPreparationHandler(handler) { this.preparationHandler = handler; }
|
|
30
|
-
prepare(options) {
|
|
31
|
-
if (!this.preparationHandler)
|
|
32
|
-
return Promise.reject(new CheckoutPreparationError(null, 'transport_unavailable'));
|
|
33
|
-
return this.preparationHandler(options);
|
|
34
|
-
}
|
|
35
|
-
preparing() {
|
|
36
|
-
this.preparationUsed = true;
|
|
37
|
-
this.set({ status: 'awaiting_approval', authorizationId: null });
|
|
38
|
-
}
|
|
39
|
-
preparationCreated(preparationId) { this.set({ ...this.state, preparationId }); }
|
|
40
|
-
prepared(preparation) {
|
|
41
|
-
this.set({ status: 'ready_to_submit', authorizationId: null, preparationId: preparation.id });
|
|
42
|
-
}
|
|
43
|
-
preparationFailed(error) {
|
|
44
|
-
this.held = true;
|
|
45
|
-
if (this.cancelled)
|
|
46
|
-
return;
|
|
47
|
-
this.set({ ...this.state, status: error.reason === 'expired' ? 'timed_out' : error.reason === 'cancelled' ? 'cancelled' : 'failed', reason: error.reason });
|
|
48
|
-
}
|
|
49
|
-
isBlocked() { return this.held || this.cancelled; }
|
|
50
|
-
isCancelled() { return this.cancelled; }
|
|
51
|
-
begin() {
|
|
52
|
-
this.active = true;
|
|
53
|
-
this.set({ status: 'awaiting_approval', authorizationId: null, ...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}) });
|
|
54
|
-
}
|
|
55
|
-
end() { this.active = false; }
|
|
56
|
-
set(state) {
|
|
57
|
-
this.state = state;
|
|
58
|
-
// Application telemetry must never interrupt a paused payment after it reaches the processor.
|
|
59
|
-
try {
|
|
60
|
-
Promise.resolve(this.options.onStateChange?.(this.getState())).catch(() => { });
|
|
61
|
-
}
|
|
62
|
-
catch { /* observer only */ }
|
|
63
|
-
}
|
|
64
|
-
approvalCreated(authorizationId) {
|
|
65
|
-
this.set({ ...this.state, authorizationId });
|
|
66
|
-
}
|
|
67
|
-
approvalUrl(approvalUrl) {
|
|
68
|
-
this.notify({ reason: 'approval', authorizationId: this.state.authorizationId, approvalUrl });
|
|
69
|
-
}
|
|
70
|
-
notify(action) {
|
|
71
|
-
try {
|
|
72
|
-
Promise.resolve(this.options.onUserAction?.(action)).catch(() => { });
|
|
73
|
-
}
|
|
74
|
-
catch { /* observer only */ }
|
|
75
|
-
}
|
|
76
|
-
cancel() {
|
|
77
|
-
this.cancelled = true;
|
|
78
|
-
this.held = true;
|
|
79
|
-
this.abort.abort();
|
|
80
|
-
this.set({ ...this.state, status: this.active || this.state.authorizationId ? 'outcome_unknown' : 'cancelled', reason: 'local_cancel' });
|
|
81
|
-
}
|
|
82
|
-
/** The exact browser request is gone; a late approval cannot reopen it. */
|
|
83
|
-
merchantRequestAborted() {
|
|
84
|
-
this.merchantAborted = true;
|
|
85
|
-
this.held = true;
|
|
86
|
-
this.set({ ...this.state, status: 'outcome_unknown', reason: 'merchant_request_aborted' });
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* The page abandoned its card request (its own script timed out) while
|
|
90
|
-
* the cardholder is still deciding. The approval stays pending and the
|
|
91
|
-
* page's next matching request will be answered from it, so the status
|
|
92
|
-
* stays `awaiting_approval`; the reason says the page has to ask again.
|
|
93
|
-
* Not a hold: nothing is refused, and no card or token has moved.
|
|
94
|
-
*/
|
|
95
|
-
merchantRequestLost() {
|
|
96
|
-
this.set({ ...this.state, reason: 'merchant_request_lost' });
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* The cardholder approved, and there is no live page request to hand the
|
|
100
|
-
* answer to: the page gave up on its own request while they decided.
|
|
101
|
-
* `ready_to_submit` is the state a prepared checkout uses for "consent in
|
|
102
|
-
* hand, click Pay": the application's next Pay action produces the request
|
|
103
|
-
* this approval answers, with no second prompt on the phone.
|
|
104
|
-
*/
|
|
105
|
-
awaitingMerchantRetry(authorizationId) {
|
|
106
|
-
this.set({ status: 'ready_to_submit', authorizationId, reason: 'awaiting_merchant_retry',
|
|
107
|
-
...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}) });
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* The page never asked again inside the retry wait and the API confirmed
|
|
111
|
-
* the approval is retired: `declined`, like any other approval that ended
|
|
112
|
-
* with nothing charged, and not held, because the next card request is a
|
|
113
|
-
* new question (the cardholder is told to ask their agent to try again).
|
|
114
|
-
* A retirement the API refused or did not confirm is an unknown outcome and
|
|
115
|
-
* is reported through failed() instead, which holds.
|
|
116
|
-
*/
|
|
117
|
-
merchantNeverRetried(authorizationId) {
|
|
118
|
-
if (this.cancelled)
|
|
119
|
-
return;
|
|
120
|
-
this.set({ ...this.state, authorizationId, status: 'declined', reason: 'merchant_never_retried' });
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* A card request this SDK cannot pay was aborted. `reason`: an unrecognized
|
|
124
|
-
* declared payment endpoint (the default), a merchant profile Agentcard has not
|
|
125
|
-
* turned on (merchant_profile_observe), or a card request the profile cannot
|
|
126
|
-
* finish (merchant_request_unsupported).
|
|
127
|
-
*/
|
|
128
|
-
unsupported(reason = 'unrecognized_payment_endpoint') {
|
|
129
|
-
this.held = true;
|
|
130
|
-
if (this.state.authorizationId)
|
|
131
|
-
return; // do not hide an outstanding/completed payment behind a later unsupported request
|
|
132
|
-
this.set({ ...this.state, status: 'unsupported', reason });
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* A card request that only a prepared checkout pays (an Adyen merchant
|
|
136
|
-
* profile's, or a Fiserv card capture) arrived with no preparation on this
|
|
137
|
-
* attachment and was aborted before any pause, prompt or authorization.
|
|
138
|
-
* Nothing is outstanding, so nothing is held; prepare() stays refused here, as
|
|
139
|
-
* after any first card request.
|
|
140
|
-
*/
|
|
141
|
-
preparationRequired() {
|
|
142
|
-
if (this.cancelled || this.state.authorizationId)
|
|
143
|
-
return;
|
|
144
|
-
this.set({ ...this.state, status: 'failed', reason: 'preparation_required' });
|
|
145
|
-
}
|
|
146
|
-
prepareHandoff(replay, requestUrl) {
|
|
147
|
-
if (replay.mode === 'token' && replay.executionMode === 'autopilot' && replay.shopOrderId) {
|
|
148
|
-
let receipt;
|
|
149
|
-
try {
|
|
150
|
-
receipt = parseOwnedShopReceipt(JSON.parse(replay.body), {
|
|
151
|
-
order_id: replay.shopOrderId, sku: OWNED_SHOP_SKU, amount: OWNED_SHOP_AMOUNT_CENTS, currency: OWNED_SHOP_CURRENCY,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
catch { /* An unrecognized response remains subject to the token continuation guard. */ }
|
|
155
|
-
if (receipt) {
|
|
156
|
-
this.ownedShopOrderId = receipt.order_id;
|
|
157
|
-
this.held = true;
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
// A tokenization approval has no authoritative PaymentIntent or amount
|
|
162
|
-
// binding. Neither the first observed confirm nor the returned opaque
|
|
163
|
-
// token can supply one. Hold even in compatibility mode: this attachment
|
|
164
|
-
// must never turn that token into an unreviewed payment continuation.
|
|
165
|
-
if (!replay.mode || replay.mode === 'token') {
|
|
166
|
-
try {
|
|
167
|
-
const url = new URL(requestUrl);
|
|
168
|
-
if (url.origin === 'https://api.stripe.com' && ['/v1/payment_methods', '/v1/tokens', '/v1/sources', '/v1/confirmation_tokens'].includes(url.pathname)) {
|
|
169
|
-
// Set before delivery: a lost browser acknowledgement must not reset
|
|
170
|
-
// an already-issued token into a retryable authorization.
|
|
171
|
-
this.unboundStripeToken = true;
|
|
172
|
-
this.stripeTokenPrepared = true;
|
|
173
|
-
this.held = true;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
catch { /* unknown URL cannot establish a token binding */ }
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* The approval a page's card-free Stripe confirmation may continue, or null.
|
|
181
|
-
* Only once a Stripe tokenization is being handed to the page, and while
|
|
182
|
-
* the checkout still awaits it (the page can send its confirmation before
|
|
183
|
-
* the browser acknowledges the delivery) or the merchant; for one check at
|
|
184
|
-
* a time and at most MAX_STRIPE_CONTINUATION_CHECKS checks. Reserves the check synchronously,
|
|
185
|
-
* so two confirmations can never both continue: the caller settles it with
|
|
186
|
-
* stripeContinuationChecked(). The hold stays on throughout.
|
|
187
|
-
*/
|
|
188
|
-
claimStripeContinuation() {
|
|
189
|
-
if (!this.stripeContinuationOpen() || this.stripeContinuation !== 'none'
|
|
190
|
-
|| this.stripeContinuationChecks >= MAX_STRIPE_CONTINUATION_CHECKS)
|
|
191
|
-
return null;
|
|
192
|
-
this.stripeContinuation = 'checking';
|
|
193
|
-
this.stripeContinuationChecks++;
|
|
194
|
-
return this.state.authorizationId;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Settle a claimed check. True only when the API allowed it and the checkout
|
|
198
|
-
* still awaits the merchant (nothing cancelled, aborted or reconciled it
|
|
199
|
-
* while the API answered): the page's confirmation continues, and no later
|
|
200
|
-
* one will.
|
|
201
|
-
*/
|
|
202
|
-
stripeContinuationChecked(allowed) {
|
|
203
|
-
if (this.stripeContinuation !== 'checking')
|
|
204
|
-
return false;
|
|
205
|
-
const proceed = allowed && this.stripeContinuationOpen();
|
|
206
|
-
this.stripeContinuation = proceed ? 'continued' : 'none';
|
|
207
|
-
if (proceed)
|
|
208
|
-
this.set({ ...this.state, reason: 'stripe_payment_continued' });
|
|
209
|
-
return proceed;
|
|
210
|
-
}
|
|
211
|
-
stripeContinuationOpen() {
|
|
212
|
-
return this.stripeTokenPrepared && !this.cancelled && !this.merchantAborted && !!this.state.authorizationId
|
|
213
|
-
&& (this.state.status === 'awaiting_approval' || this.state.status === 'awaiting_merchant');
|
|
214
|
-
}
|
|
215
|
-
handedOff(replay) {
|
|
216
|
-
if (this.merchantAborted)
|
|
217
|
-
return;
|
|
218
|
-
if (this.ownedShopOrderId) {
|
|
219
|
-
this.held = true;
|
|
220
|
-
this.set({ status: 'completed', authorizationId: replay.authorizationId, mode: 'token', orderId: this.ownedShopOrderId });
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
const mismatch = (!replay.mode || replay.mode === 'token') && replay.amountVerified === false;
|
|
224
|
-
this.held = !!this.options.requireMerchantResult || replay.mode === 'hosted_form' || mismatch || this.unboundStripeToken;
|
|
225
|
-
if (mismatch)
|
|
226
|
-
this.stripeTokenPrepared = false;
|
|
227
|
-
this.set({ status: 'awaiting_merchant', authorizationId: replay.authorizationId, mode: replay.mode ?? 'token',
|
|
228
|
-
...(this.state.preparationId ? { preparationId: this.state.preparationId } : {}),
|
|
229
|
-
...(mismatch ? { reason: 'charged_amount_mismatch' }
|
|
230
|
-
: this.stripeContinuation === 'continued' ? { reason: 'stripe_payment_continued' }
|
|
231
|
-
: this.unboundStripeToken ? { reason: 'stripe_tokenization_unbound' } : {}) });
|
|
232
|
-
}
|
|
233
|
-
failed(error, handoffStarted = false) {
|
|
234
|
-
if (this.cancelled)
|
|
235
|
-
return;
|
|
236
|
-
if (this.merchantAborted) {
|
|
237
|
-
this.merchantRequestAborted();
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const authorizationId = error instanceof PaymentOutcomeUnknownError || error instanceof ProcessorRefusedError ? error.authorizationId : this.state.authorizationId;
|
|
241
|
-
if (!handoffStarted && error instanceof PaymentOutcomeUnknownError && error.reason === 'autopilot_action_required') {
|
|
242
|
-
this.held = true;
|
|
243
|
-
this.set({ ...this.state, authorizationId, status: 'requires_user_action', reason: 'other' });
|
|
244
|
-
this.notify({ reason: 'other', authorizationId });
|
|
245
|
-
}
|
|
246
|
-
else if (handoffStarted || error instanceof PaymentOutcomeUnknownError || error instanceof IntentNotConfirmableError) {
|
|
247
|
-
this.held = true;
|
|
248
|
-
this.set({ ...this.state, authorizationId, status: 'outcome_unknown', reason: error instanceof PaymentOutcomeUnknownError ? error.reason : error instanceof IntentNotConfirmableError ? 'intent_not_confirmable' : 'browser_handoff_failed' });
|
|
249
|
-
}
|
|
250
|
-
else if (error instanceof CheckoutCancelledError) {
|
|
251
|
-
this.cancel();
|
|
252
|
-
}
|
|
253
|
-
else if (error instanceof CheckoutPreparationError) {
|
|
254
|
-
this.preparationFailed(error);
|
|
255
|
-
}
|
|
256
|
-
else if (error instanceof ApprovalTimeoutError) {
|
|
257
|
-
this.set({ ...this.state, status: 'timed_out', reason: 'approval_expired' });
|
|
258
|
-
}
|
|
259
|
-
else if (error instanceof ProcessorRefusedError) {
|
|
260
|
-
// A rejected processor request is not proof that the merchant cannot
|
|
261
|
-
// collect this payment. Keep native retries held until the application
|
|
262
|
-
// checks the merchant and explicitly starts another attempt.
|
|
263
|
-
this.held = true;
|
|
264
|
-
this.set({ ...this.state, authorizationId, status: 'declined', reason: 'processor_refused' });
|
|
265
|
-
}
|
|
266
|
-
else if (error instanceof AdyenTestPlatformRefusedError) {
|
|
267
|
-
// Agentcard's own rule, not a person's answer: refused at create before
|
|
268
|
-
// anyone was asked, the checkout failed as it did before the error was
|
|
269
|
-
// typed; declined before the card was sent, the authorization is declined.
|
|
270
|
-
// Either way the reason names the rule.
|
|
271
|
-
this.set({ ...this.state, status: error.stage === 'create' ? 'failed' : 'declined', reason: error.code });
|
|
272
|
-
}
|
|
273
|
-
else if (error instanceof ApprovalDeclinedError) {
|
|
274
|
-
this.set({ ...this.state, status: 'declined', reason: 'approval_declined' });
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
this.set({ ...this.state, status: 'failed', reason: 'checkout_failed' });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
async requestUserAction(reason) {
|
|
281
|
-
if (this.state.status === 'completed')
|
|
282
|
-
return;
|
|
283
|
-
this.held = true;
|
|
284
|
-
this.set({ ...this.state, status: 'requires_user_action', reason });
|
|
285
|
-
// Hooks deliver a link/live view owned by the integrator; the SDK does not invent challenge URLs.
|
|
286
|
-
try {
|
|
287
|
-
await this.options.onUserAction?.({ reason, authorizationId: this.state.authorizationId });
|
|
288
|
-
}
|
|
289
|
-
catch { /* observer only */ }
|
|
290
|
-
}
|
|
291
|
-
reconcile() {
|
|
292
|
-
if (this.state.status === 'completed')
|
|
293
|
-
return Promise.resolve(this.getState());
|
|
294
|
-
if (this.reconciliation)
|
|
295
|
-
return this.reconciliation;
|
|
296
|
-
if (!this.options.resolveMerchantResult)
|
|
297
|
-
return Promise.resolve(this.getState());
|
|
298
|
-
if (this.active)
|
|
299
|
-
throw new Error('Wait for the paused request to finish before reconciling the merchant order.');
|
|
300
|
-
this.reconciliation = this.resolve().finally(() => { this.reconciliation = null; });
|
|
301
|
-
return this.reconciliation;
|
|
302
|
-
}
|
|
303
|
-
async resolve() {
|
|
304
|
-
let result;
|
|
305
|
-
try {
|
|
306
|
-
result = await this.options.resolveMerchantResult(this.getState());
|
|
307
|
-
}
|
|
308
|
-
catch {
|
|
309
|
-
result = { status: 'unknown' };
|
|
310
|
-
}
|
|
311
|
-
if (!result || typeof result !== 'object' || Array.isArray(result))
|
|
312
|
-
result = { status: 'unknown' };
|
|
313
|
-
// The resolver is trusted application code: it must check the original
|
|
314
|
-
// request, amount, currency, selected card and authoritative merchant
|
|
315
|
-
// result. The SDK binds its explicit confirmation to this authorization;
|
|
316
|
-
// it does not turn an HTTP response or a success URL into payment proof.
|
|
317
|
-
const orderConfirmed = result.status === 'completed' && result.confirmation === undefined
|
|
318
|
-
&& typeof result.orderId === 'string' && result.orderId.length > 0;
|
|
319
|
-
const paymentConfirmed = result.status === 'completed' && result.orderId === undefined
|
|
320
|
-
&& result.confirmation && typeof result.confirmation === 'object' && !Array.isArray(result.confirmation)
|
|
321
|
-
&& result.confirmation.kind === 'merchant_payment'
|
|
322
|
-
&& typeof this.state.authorizationId === 'string' && this.state.authorizationId.length > 0
|
|
323
|
-
&& result.confirmation.authorizationId === this.state.authorizationId;
|
|
324
|
-
if (result.status === 'completed' && (orderConfirmed || paymentConfirmed)) {
|
|
325
|
-
this.held = true;
|
|
326
|
-
// Completion supersedes an earlier failure/challenge, while retaining
|
|
327
|
-
// preparation and authorization context. Never copy arbitrary evidence
|
|
328
|
-
// into public state or release the hold on additional payment requests.
|
|
329
|
-
const { reason: _previousReason, orderId: _previousOrder, confirmation: _previousConfirmation, ...context } = this.state;
|
|
330
|
-
this.set({ ...context, status: 'completed', ...(orderConfirmed
|
|
331
|
-
? { orderId: result.orderId }
|
|
332
|
-
: { confirmation: { kind: 'merchant_payment', authorizationId: this.state.authorizationId } }) });
|
|
333
|
-
}
|
|
334
|
-
else if (result.status === 'failed') {
|
|
335
|
-
// Keep the guard armed until the application deliberately starts another attempt.
|
|
336
|
-
this.held = true;
|
|
337
|
-
this.set({ ...this.state, status: 'failed', reason: 'merchant_confirmed_failure' });
|
|
338
|
-
}
|
|
339
|
-
else if (result.status === 'requires_user_action') {
|
|
340
|
-
await this.requestUserAction(result.reason);
|
|
341
|
-
}
|
|
342
|
-
else {
|
|
343
|
-
this.held = true;
|
|
344
|
-
this.set({ ...this.state, status: result.status === 'pending' ? 'awaiting_merchant' : 'outcome_unknown', reason: 'merchant_not_confirmed' });
|
|
345
|
-
}
|
|
346
|
-
return this.getState();
|
|
347
|
-
}
|
|
348
|
-
retryAfterMerchantFailure(result) {
|
|
349
|
-
if (this.ownedShopOrderId)
|
|
350
|
-
throw new Error('This shop payment already has a receipt; another payment is not permitted.');
|
|
351
|
-
if (this.preparationUsed)
|
|
352
|
-
throw new Error('Prepared checkouts are single use. Reconcile this attempt and create a new attachment.');
|
|
353
|
-
if (this.active || this.reconciliation)
|
|
354
|
-
throw new Error('Cannot start another attempt while payment or reconciliation is in progress.');
|
|
355
|
-
if (this.cancelled)
|
|
356
|
-
throw new Error('This attachment was cancelled; attach again after reconciling the merchant order.');
|
|
357
|
-
if (result?.status !== 'failed')
|
|
358
|
-
throw new Error('A merchant-confirmed failure is required before retrying.');
|
|
359
|
-
if (this.state.status === 'completed')
|
|
360
|
-
throw new Error('This order is already complete. Use a new attachment for another order.');
|
|
361
|
-
if (this.unboundStripeToken)
|
|
362
|
-
throw new Error('This attachment delivered an unbound Stripe token; reconcile the merchant order and use a separately validated checkout flow.');
|
|
363
|
-
this.held = false;
|
|
364
|
-
this.merchantAborted = false;
|
|
365
|
-
this.set({ status: 'idle', authorizationId: null });
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
export function paymentEndpointGuards(input = []) {
|
|
369
|
-
const guards = input.map((guard) => {
|
|
370
|
-
const origin = new URL(guard.origin);
|
|
371
|
-
if (!['http:', 'https:'].includes(origin.protocol) || origin.origin !== guard.origin
|
|
372
|
-
|| !guard.pathname.startsWith('/') || /[?#*]/.test(guard.pathname)) {
|
|
373
|
-
throw new Error('Payment endpoint guards require a canonical http(s) origin and an exact path without wildcards or query strings.');
|
|
374
|
-
}
|
|
375
|
-
const methods = (guard.methods ?? ['POST', 'PUT', 'PATCH']).map((method) => method.toUpperCase());
|
|
376
|
-
if (!methods.length || methods.some((method) => !['POST', 'PUT', 'PATCH', 'DELETE'].includes(method))) {
|
|
377
|
-
throw new Error('Payment endpoint guards can only block explicit mutation methods.');
|
|
378
|
-
}
|
|
379
|
-
return { ...guard, methods };
|
|
380
|
-
});
|
|
381
|
-
return {
|
|
382
|
-
patterns: guards.map((guard) => `${guard.origin}${guard.pathname}*`),
|
|
383
|
-
matches(raw, method) {
|
|
384
|
-
let url;
|
|
385
|
-
try {
|
|
386
|
-
url = new URL(raw);
|
|
387
|
-
}
|
|
388
|
-
catch {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
return guards.some((guard) => url.origin === guard.origin && url.pathname === guard.pathname
|
|
392
|
-
&& (method === undefined || guard.methods.includes(method.toUpperCase())));
|
|
393
|
-
},
|
|
394
|
-
};
|
|
395
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { ReplayResponse } from './client.js';
|
|
2
|
-
/** Capture only the native Checkout Pro configuration. A late configuration,
|
|
3
|
-
* navigation or repeated token cannot replace the context of an issued token.
|
|
4
|
-
* Final payment requests retain the attachment's existing payment guards.
|
|
5
|
-
*/
|
|
6
|
-
export declare class MercadoCheckoutGate {
|
|
7
|
-
private context;
|
|
8
|
-
private started;
|
|
9
|
-
private stopped;
|
|
10
|
-
private sequence;
|
|
11
|
-
private associatedFlow;
|
|
12
|
-
private association;
|
|
13
|
-
matches(url: string): boolean;
|
|
14
|
-
requiresDocument(url: string, method: string): boolean;
|
|
15
|
-
invalidate(): void;
|
|
16
|
-
configuration(url: string, method: string, body: string, readDocument: () => Promise<string>): Promise<boolean>;
|
|
17
|
-
claimToken(url: string, method: string, documentUrl: string): unknown;
|
|
18
|
-
arm(replay: ReplayResponse, tokenUrl: string): Promise<void>;
|
|
19
|
-
associationBody(url: string, method: string, body: string, documentUrl: string): string | undefined;
|
|
20
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
declare var MERCADO_CHECKOUT_ORIGIN: string;
|
|
2
|
-
declare var MERCADO_CONFIGURATION_URL: string;
|
|
3
|
-
declare var MERCADO_CHECKOUT_PATTERN: string;
|
|
4
|
-
declare function mercadoCheckoutDocument(raw: any): {
|
|
5
|
-
flowId: string;
|
|
6
|
-
preferenceId: string | null;
|
|
7
|
-
query: string;
|
|
8
|
-
};
|
|
9
|
-
declare function parseMercadoCheckoutContext(value: any): any;
|
|
10
|
-
declare function captureMercadoCheckoutContext(url: any, method: any, body: any, documentUrl: any): any;
|
|
11
|
-
declare function isMercadoTokenRequest(url: any, method: any): boolean;
|
|
12
|
-
declare function mercadoDigest(value: any): Promise<string>;
|
|
13
|
-
declare function mercadoContextDigest(context: any, tokenUrl: any): Promise<string>;
|
|
14
|
-
declare function prepareMercadoConfiguration(context: any, card: any): {
|
|
15
|
-
url: string;
|
|
16
|
-
body: string;
|
|
17
|
-
};
|
|
18
|
-
declare function parseMercadoConfiguration(value: any): {
|
|
19
|
-
paymentMethodId: any;
|
|
20
|
-
paymentTypeId: any;
|
|
21
|
-
paymentMethodName: any;
|
|
22
|
-
issuerId: string;
|
|
23
|
-
issuerName: any;
|
|
24
|
-
};
|
|
25
|
-
declare function createMercadoProcessorContext(configuration: any, context: any, tokenUrl: any, tokenBody: any): Promise<{
|
|
26
|
-
paymentMethodId: any;
|
|
27
|
-
paymentTypeId: any;
|
|
28
|
-
paymentMethodName: any;
|
|
29
|
-
issuerId: string;
|
|
30
|
-
issuerName: any;
|
|
31
|
-
version: number;
|
|
32
|
-
psp: string;
|
|
33
|
-
source: string;
|
|
34
|
-
contextDigest: string;
|
|
35
|
-
tokenDigest: string;
|
|
36
|
-
}>;
|
|
37
|
-
declare function validateMercadoProcessorContext(value: any, context: any, tokenUrl: any, tokenBody: any): Promise<any>;
|
|
38
|
-
declare function createMercadoAssociationGate({ context, tokenUrl, tokenBody, processorContext }: {
|
|
39
|
-
context: any;
|
|
40
|
-
tokenUrl: any;
|
|
41
|
-
tokenBody: any;
|
|
42
|
-
processorContext: any;
|
|
43
|
-
}): Promise<{
|
|
44
|
-
take({ url, method, body, documentUrl }: {
|
|
45
|
-
url: any;
|
|
46
|
-
method: any;
|
|
47
|
-
body: any;
|
|
48
|
-
documentUrl: any;
|
|
49
|
-
}): string;
|
|
50
|
-
readonly claimed: boolean;
|
|
51
|
-
}>;
|
|
52
|
-
export { MERCADO_CHECKOUT_ORIGIN, MERCADO_CHECKOUT_PATTERN, MERCADO_CONFIGURATION_URL, captureMercadoCheckoutContext, createMercadoAssociationGate, createMercadoProcessorContext, isMercadoTokenRequest, mercadoCheckoutDocument, mercadoContextDigest, mercadoDigest, parseMercadoCheckoutContext, parseMercadoConfiguration, prepareMercadoConfiguration, validateMercadoProcessorContext };
|