@agent-cards/checkout 0.19.0 → 0.22.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/pre-claim.js
DELETED
|
@@ -1,386 +0,0 @@
|
|
|
1
|
-
// One pre-claim gate registry, shared by the CDP and Playwright adapters.
|
|
2
|
-
//
|
|
3
|
-
// Before a paused request is claimed for the vault, a small set of processor
|
|
4
|
-
// gates inspect it: Mercado Pago's native Checkout Pro configuration and
|
|
5
|
-
// association, Braintree's shared /graphql endpoint, native Stripe Checkout,
|
|
6
|
-
// and Fiserv's card capture. The first three used to be written inline in
|
|
7
|
-
// Fetch.requestPaused and again in the page.route handler, so a decision could
|
|
8
|
-
// drift between the two adapters and a new family had to be added in both places.
|
|
9
|
-
//
|
|
10
|
-
// dispatchPreClaim runs the gates in order and resolves the request to one of:
|
|
11
|
-
// - continue: let the request through untouched, or with a body the gate
|
|
12
|
-
// rewrote (Mercado's corrected association);
|
|
13
|
-
// - abort: fail the request, so a dummy card never reaches a live processor;
|
|
14
|
-
// - answer: fulfill locally with a synthetic response (Stripe's tokenization
|
|
15
|
-
// stub);
|
|
16
|
-
// - pause: hand the request to the vault claim flow, carrying the native
|
|
17
|
-
// Stripe step when one was claimed.
|
|
18
|
-
//
|
|
19
|
-
// A gate performs its own continue/answer/abort through the adapter effects so
|
|
20
|
-
// that failure handling stays byte-for-byte with the inline code: Stripe's
|
|
21
|
-
// fulfill and Mercado's continue run inside the gate's own try, so a transport
|
|
22
|
-
// failure there is recovered exactly as before (Stripe reports stub_response,
|
|
23
|
-
// Mercado stops the native checkout). The effects encapsulate each adapter's
|
|
24
|
-
// mechanics (CDP Fetch.* with base64 bodies and its own catch; Playwright
|
|
25
|
-
// route.*), and the state accessors are re-read after every await, as the
|
|
26
|
-
// inline gates did. Adding a new family is one entry in PSP_PRE_CLAIM_GATES: a
|
|
27
|
-
// non-card body on a recognized host continues, and an unsupported card body
|
|
28
|
-
// aborts.
|
|
29
|
-
//
|
|
30
|
-
// The dispatch is synchronous unless a gate must await. The inline code reached
|
|
31
|
-
// the vault claim flow for an ordinary card request without a single await, and
|
|
32
|
-
// worker attribution and setup races depend on that: activeRequest is set in
|
|
33
|
-
// the same microtask the request paused in. So every gate returns its decision
|
|
34
|
-
// synchronously when it does no I/O (the common card request defers through
|
|
35
|
-
// every gate and pauses with no await), and hands back a promise only when it must
|
|
36
|
-
// read the document or talk to the browser (Mercado's continuation, Stripe once
|
|
37
|
-
// a step is claimed, and the continue/abort effects).
|
|
38
|
-
import { PaymentOutcomeUnknownError, redactUrl } from './client.js';
|
|
39
|
-
import { classifyBraintreeRequest } from './braintree.js';
|
|
40
|
-
import { StripeCheckoutClaimConflictError, stripeCheckoutReadinessError } from './stripe-checkout.js';
|
|
41
|
-
import { classifyProfileRequest, merchantBodyKeyPathSha256, profileTemplatedUrl } from './merchant-hosted.js';
|
|
42
|
-
import { isFiservCaptureHost, isFiservPausableBody } from './fiserv.js';
|
|
43
|
-
/**
|
|
44
|
-
* The URL an event may name for a request. For an armed merchant profile's
|
|
45
|
-
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
46
|
-
* profile's own query (merchantProfileTemplatedUrl), so a user id, cart id or
|
|
47
|
-
* email in the merchant's path or query never reaches telemetry. For anything
|
|
48
|
-
* else: origin and path (redactUrl), as before.
|
|
49
|
-
*/
|
|
50
|
-
export function eventUrl(vault, url) {
|
|
51
|
-
const profile = typeof vault?.merchantProfileOf === 'function' ? vault.merchantProfileOf(url) : null;
|
|
52
|
-
if (profile) {
|
|
53
|
-
try {
|
|
54
|
-
return profileTemplatedUrl(profile, url);
|
|
55
|
-
}
|
|
56
|
-
catch { /* not a profile this build reviewed */ }
|
|
57
|
-
}
|
|
58
|
-
return redactUrl(url);
|
|
59
|
-
}
|
|
60
|
-
// Mercado Pago's native Checkout Pro shares one origin between its BIN-lookup
|
|
61
|
-
// configuration and its card association. A request that is not for that origin
|
|
62
|
-
// defers synchronously; a POST to it is handled asynchronously (a document read
|
|
63
|
-
// backs the capture and the correction). The continue runs inside that try, so
|
|
64
|
-
// if it or a gate step throws, the native checkout is stopped and only this
|
|
65
|
-
// request fails.
|
|
66
|
-
const mercadoPreClaim = (ctx) => {
|
|
67
|
-
const { url, method } = ctx.request;
|
|
68
|
-
if (!(ctx.mercado.matches(url) && !['GET', 'HEAD', 'OPTIONS'].includes(method)))
|
|
69
|
-
return undefined;
|
|
70
|
-
return mercadoContinuation(ctx);
|
|
71
|
-
};
|
|
72
|
-
async function mercadoContinuation(ctx) {
|
|
73
|
-
const { url, method } = ctx.request;
|
|
74
|
-
try {
|
|
75
|
-
if (!ctx.attachmentReady() || ctx.interceptionAborted() || ctx.lifecycleAborted() || ctx.terminal())
|
|
76
|
-
throw new Error('checkout_interception_not_ready');
|
|
77
|
-
const body = ctx.request.body ?? '';
|
|
78
|
-
if (await ctx.mercado.configuration(url, method, body, ctx.readDocumentUrl)) {
|
|
79
|
-
await ctx.effects.continueRequest();
|
|
80
|
-
return { kind: 'continue' };
|
|
81
|
-
}
|
|
82
|
-
const postData = ctx.mercado.associationBody(url, method, body, await ctx.readDocumentUrl());
|
|
83
|
-
if (postData !== undefined) {
|
|
84
|
-
if (ctx.interceptionAborted() || ctx.lifecycleAborted() || ctx.documentClosed())
|
|
85
|
-
throw new Error('checkout_interception_not_ready');
|
|
86
|
-
await ctx.effects.continueRequest(postData);
|
|
87
|
-
return { kind: 'continue' };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
ctx.mercado.invalidate();
|
|
92
|
-
ctx.lifecycle.failed(new PaymentOutcomeUnknownError(ctx.lifecycle.getState().authorizationId, 'mercado_checkout_continuation_stopped'));
|
|
93
|
-
ctx.onEvent?.({ type: 'blocked', detail: 'mercado_checkout_continuation_stopped' });
|
|
94
|
-
await ctx.effects.abort();
|
|
95
|
-
return { kind: 'abort' };
|
|
96
|
-
}
|
|
97
|
-
return undefined;
|
|
98
|
-
}
|
|
99
|
-
// Braintree shares /graphql between its client configuration and its card
|
|
100
|
-
// tokenization. Classify (synchronously) before URL-only recognition: a
|
|
101
|
-
// configuration query continues untouched, a malformed tokenization aborts,
|
|
102
|
-
// everything else (a real tokenization, or a non-Braintree request) defers.
|
|
103
|
-
// classifyBraintreeRequest takes the raw body (string or null); a null body on
|
|
104
|
-
// a Braintree POST is classified the same as an empty one, so both adapters
|
|
105
|
-
// reach the same verdict. The configuration continue swallows a transport
|
|
106
|
-
// failure, as the inline CDP path did (the Playwright path never reaches a
|
|
107
|
-
// failing route here).
|
|
108
|
-
const braintreePreClaim = (ctx) => {
|
|
109
|
-
const { url, method, body } = ctx.request;
|
|
110
|
-
const braintree = method.toUpperCase() === 'POST' ? classifyBraintreeRequest(url, method, body) : undefined;
|
|
111
|
-
if (braintree === 'configuration')
|
|
112
|
-
return braintreeContinue(ctx);
|
|
113
|
-
if (braintree === 'invalid')
|
|
114
|
-
return braintreeAbort(ctx);
|
|
115
|
-
return undefined;
|
|
116
|
-
};
|
|
117
|
-
async function braintreeContinue(ctx) {
|
|
118
|
-
await ctx.effects.continueRequest().catch(() => { });
|
|
119
|
-
return { kind: 'continue' };
|
|
120
|
-
}
|
|
121
|
-
async function braintreeAbort(ctx) {
|
|
122
|
-
ctx.onEvent?.({ type: 'blocked', detail: 'unsupported_braintree_graphql_operation' });
|
|
123
|
-
await ctx.effects.abort();
|
|
124
|
-
return { kind: 'abort' };
|
|
125
|
-
}
|
|
126
|
-
// Native Stripe Checkout. Enablement and the claim are synchronous, so a
|
|
127
|
-
// disabled attachment or a non-Stripe request defers with no await. Once a step
|
|
128
|
-
// is claimed, the readiness checks, document assertion and fulfill run
|
|
129
|
-
// asynchronously: the tokenization step is answered locally with a synthetic
|
|
130
|
-
// payment method (no selected-card data, no authorization) and the final
|
|
131
|
-
// confirmation step pauses for the vault claim flow. The gate returns that
|
|
132
|
-
// pause itself rather than deferring, so a claimed step never reaches a gate
|
|
133
|
-
// registered after Stripe: a broad family gate appended later cannot continue
|
|
134
|
-
// or abort the confirmation the vault must claim. The fulfill runs inside the
|
|
135
|
-
// try, so a transport failure is reported as stub_response and aborts, and
|
|
136
|
-
// checkout_prepared fires only after a successful fulfill.
|
|
137
|
-
const stripePreClaim = (ctx) => {
|
|
138
|
-
if (!ctx.stripe.isEnabled())
|
|
139
|
-
return undefined;
|
|
140
|
-
const stripeUrl = ctx.request.url;
|
|
141
|
-
let stripeStep;
|
|
142
|
-
try {
|
|
143
|
-
const nativeRequest = { url: stripeUrl, method: ctx.request.method, headers: ctx.request.headers, body: ctx.request.body ?? '' };
|
|
144
|
-
stripeStep = ctx.stripe.claim(nativeRequest);
|
|
145
|
-
}
|
|
146
|
-
catch (error) {
|
|
147
|
-
return stripeReject(ctx, error, stripeUrl, 'classification', undefined);
|
|
148
|
-
}
|
|
149
|
-
ctx.stripeStep = stripeStep;
|
|
150
|
-
if (!stripeStep)
|
|
151
|
-
return undefined;
|
|
152
|
-
return stripeAfterClaim(ctx, stripeUrl, stripeStep);
|
|
153
|
-
};
|
|
154
|
-
async function stripeAfterClaim(ctx, stripeUrl, stripeStep) {
|
|
155
|
-
let stripeStage = 'readiness';
|
|
156
|
-
try {
|
|
157
|
-
if (!ctx.attachmentReady() || ctx.stripeAttachmentBusy())
|
|
158
|
-
throw stripeCheckoutReadinessError('attachment_not_ready');
|
|
159
|
-
if (ctx.interceptionAborted())
|
|
160
|
-
throw stripeCheckoutReadinessError('cancelled');
|
|
161
|
-
if (ctx.terminal() || ctx.lifecycle.isBlocked())
|
|
162
|
-
throw stripeCheckoutReadinessError(ctx.lifecycle.isCancelled() ? 'cancelled' : 'checkout_inactive');
|
|
163
|
-
if (ctx.awaitingApproval())
|
|
164
|
-
throw stripeCheckoutReadinessError('approval_pending');
|
|
165
|
-
stripeStage = 'document';
|
|
166
|
-
ctx.stripe.assertDocument(await ctx.readDocumentUrl());
|
|
167
|
-
stripeStage = 'claim';
|
|
168
|
-
ctx.stripe.assertClaim(stripeStep);
|
|
169
|
-
stripeStage = 'readiness';
|
|
170
|
-
if (ctx.interceptionAborted())
|
|
171
|
-
throw stripeCheckoutReadinessError('cancelled');
|
|
172
|
-
if (ctx.lifecycle.isBlocked())
|
|
173
|
-
throw stripeCheckoutReadinessError(ctx.lifecycle.isCancelled() ? 'cancelled' : 'checkout_inactive');
|
|
174
|
-
if (ctx.requestInactive())
|
|
175
|
-
throw stripeCheckoutReadinessError('request_inactive');
|
|
176
|
-
if (stripeStep.phase === 'tokenization') {
|
|
177
|
-
stripeStage = 'stub_response';
|
|
178
|
-
await ctx.effects.answer(stripeStep.response);
|
|
179
|
-
ctx.onEvent?.({ type: 'checkout_prepared', detail: { processor: 'stripe' } });
|
|
180
|
-
return { kind: 'answer' };
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
return stripeReject(ctx, error, stripeUrl, stripeStage, stripeStep.phase);
|
|
185
|
-
}
|
|
186
|
-
return { kind: 'pause', stripeStep };
|
|
187
|
-
}
|
|
188
|
-
async function stripeReject(ctx, error, stripeUrl, stage, phase) {
|
|
189
|
-
const detail = ctx.stripe.describeRejection(error, stripeUrl, stage, phase);
|
|
190
|
-
if (!(error instanceof StripeCheckoutClaimConflictError))
|
|
191
|
-
ctx.stripe.invalidate();
|
|
192
|
-
ctx.onEvent?.({ type: 'checkout_blocked', detail });
|
|
193
|
-
ctx.onEvent?.({ type: 'blocked', detail: ctx.failureSummary(error) });
|
|
194
|
-
await ctx.effects.abort();
|
|
195
|
-
return { kind: 'abort' };
|
|
196
|
-
}
|
|
197
|
-
// Adyen merchant-hosted checkouts (merchant-hosted.ts). A request to an armed
|
|
198
|
-
// profile's endpoint, or a sibling of it, is the profile's to judge before the
|
|
199
|
-
// terminal arbiter, which would otherwise continue it (no processor recognizes a
|
|
200
|
-
// merchant's own server):
|
|
201
|
-
// - no encrypted card data (a basket query, a GET, a preflight): defer, and the
|
|
202
|
-
// request is judged as it was before any profile existed;
|
|
203
|
-
// - no readable body on anything but a GET, HEAD or OPTIONS: one the adapter
|
|
204
|
-
// could not read (CDP reports it with no bytes: a blob or a file) is not
|
|
205
|
-
// proven to carry no card, so it is judged as a card body the profile cannot
|
|
206
|
-
// finish, like every unreadable card body in cdp.ts. Playwright's postData()
|
|
207
|
-
// is null for a missing body and an unreadable one alike, so an empty body
|
|
208
|
-
// counts as unread in both adapters and they reach the same verdict;
|
|
209
|
-
// - a card request to a profile this attachment already handed a card to: the
|
|
210
|
-
// page's retry carries the agent's dummy card and the approval is spent, so it
|
|
211
|
-
// aborts (merchant-handoff.ts reports the unknown outcome);
|
|
212
|
-
// - a profile in observe: report the request's shape (the SHA-256 of its key
|
|
213
|
-
// paths, never a value; null for a body that was not read) with the verdict,
|
|
214
|
-
// then abort as unsupported, so the dummy card never reaches the merchant and
|
|
215
|
-
// no authorization exists;
|
|
216
|
-
// - a card body the profile cannot finish (another operation, a stored card,
|
|
217
|
-
// storing the card, three fields, a sibling endpoint, an unreadable body):
|
|
218
|
-
// abort as unsupported, with the reason;
|
|
219
|
-
// - the profile's reviewed card request, on an enabled profile, with no
|
|
220
|
-
// preparation on this attachment: abort as preparation_required before any
|
|
221
|
-
// pause, prompt or authorization, and refuse a later prepare() the way the
|
|
222
|
-
// first card request always does (preparation.ts);
|
|
223
|
-
// - the same request with a preparation: pause for the claim flow, where only a
|
|
224
|
-
// ready preparation for this profile pays it.
|
|
225
|
-
// A reader that throws on the body is not proof it carries no card either: the
|
|
226
|
-
// request is judged as an unreadable card body and aborted, never left paused.
|
|
227
|
-
// Synchronous until it must abort, like every gate.
|
|
228
|
-
const UNREAD_BODY_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
229
|
-
export const MERCHANT_BODY_UNREADABLE = 'the paused request carries no body the SDK could read, so Agentcard cannot tell whether it carries a card';
|
|
230
|
-
const merchantHostedPreClaim = (ctx) => {
|
|
231
|
-
const { url, method } = ctx.request;
|
|
232
|
-
const profile = typeof ctx.vault.merchantProfileOf === 'function' ? ctx.vault.merchantProfileOf(url) : null;
|
|
233
|
-
if (!profile)
|
|
234
|
-
return undefined;
|
|
235
|
-
const body = ctx.request.body;
|
|
236
|
-
const unread = body === null || body === '';
|
|
237
|
-
let verdict;
|
|
238
|
-
try {
|
|
239
|
-
verdict = !unread ? classifyProfileRequest(profile, url, method, body)
|
|
240
|
-
: UNREAD_BODY_METHODS.has(String(method).toUpperCase()) ? { verdict: 'pass' } : { verdict: 'abort', reason: MERCHANT_BODY_UNREADABLE };
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
verdict = { verdict: 'abort', reason: MERCHANT_BODY_UNREADABLE };
|
|
244
|
-
}
|
|
245
|
-
if (verdict.verdict === 'pass')
|
|
246
|
-
return undefined;
|
|
247
|
-
const templated = profileTemplatedUrl(profile, url);
|
|
248
|
-
if (ctx.merchantHandoff?.retried(profile.id))
|
|
249
|
-
return merchantHostedAbort(ctx);
|
|
250
|
-
if (profile.status !== 'enabled') {
|
|
251
|
-
ctx.onEvent?.({ type: 'merchant_profile_observed', detail: { profile: profile.id, url: templated, method, verdict: verdict.verdict,
|
|
252
|
-
...(verdict.verdict === 'abort' ? { reason: verdict.reason } : {}), keyPathSha256: unread ? null : keyPathSha256Of(body) } });
|
|
253
|
-
return merchantHostedUnsupported(ctx, profile, templated, 'merchant_profile_observe', 'Agentcard has not turned on payments to this merchant yet');
|
|
254
|
-
}
|
|
255
|
-
if (verdict.verdict === 'abort')
|
|
256
|
-
return merchantHostedUnsupported(ctx, profile, templated, 'merchant_request_unsupported', verdict.reason);
|
|
257
|
-
if (!ctx.preparationGate.isEngaged())
|
|
258
|
-
return merchantHostedPreparationRequired(ctx);
|
|
259
|
-
return { kind: 'pause', stripeStep: ctx.stripeStep };
|
|
260
|
-
};
|
|
261
|
-
/** The body's key-path hash for an observe event, or null when it cannot be read: telemetry never stops the gate. */
|
|
262
|
-
function keyPathSha256Of(body) {
|
|
263
|
-
try {
|
|
264
|
-
return merchantBodyKeyPathSha256(body);
|
|
265
|
-
}
|
|
266
|
-
catch {
|
|
267
|
-
return null;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
async function merchantHostedAbort(ctx) {
|
|
271
|
-
await ctx.effects.abort();
|
|
272
|
-
return { kind: 'abort' };
|
|
273
|
-
}
|
|
274
|
-
async function merchantHostedPreparationRequired(ctx) {
|
|
275
|
-
ctx.preparationGate.observeUnprepared();
|
|
276
|
-
ctx.lifecycle.preparationRequired();
|
|
277
|
-
ctx.onEvent?.({ type: 'blocked', detail: 'preparation_required' });
|
|
278
|
-
await ctx.effects.abort();
|
|
279
|
-
return { kind: 'abort' };
|
|
280
|
-
}
|
|
281
|
-
async function merchantHostedUnsupported(ctx, profile, url, category, reason) {
|
|
282
|
-
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
283
|
-
ctx.lifecycle.unsupported(category);
|
|
284
|
-
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url, method: ctx.request.method, profile: profile.id, reason } });
|
|
285
|
-
await ctx.effects.abort();
|
|
286
|
-
return { kind: 'abort' };
|
|
287
|
-
}
|
|
288
|
-
// Fiserv Commerce Hub card capture (fiserv.ts). A POST the registry recognizes on
|
|
289
|
-
// one of Fiserv's two card-capture hosts is this gate's to judge before any claim:
|
|
290
|
-
// - only a capture that carries no Vault card (Fastlane's PaymentToken, ACH's
|
|
291
|
-
// PaymentCheck, a gift or EBT card, each in its exact source shape) continues
|
|
292
|
-
// untouched, and no preparation is claimed or spent;
|
|
293
|
-
// - every other body is judged as a card capture, including one not proven
|
|
294
|
-
// card-free: one the adapter could not read (CDP reports it with no bytes;
|
|
295
|
-
// Playwright's postData() is null for it and for a missing body alike), one over
|
|
296
|
-
// 64 KiB, one that is not JSON, a PaymentCard with a plaintext card, or a
|
|
297
|
-
// non-card type or category that also carries a card member. As the
|
|
298
|
-
// registry requires of an unsupported card body, none of them continues to
|
|
299
|
-
// Fiserv with the agent's placeholder;
|
|
300
|
-
// - a card capture with no preparation on this attachment is refused as
|
|
301
|
-
// preparation_required before any pause, prompt or authorization, since only a
|
|
302
|
-
// prepared checkout pays Fiserv, and a later prepare() is refused as after any
|
|
303
|
-
// first card request (preparation.ts). Only while the attachment is live and
|
|
304
|
-
// idle: otherwise (still arming a frame, tearing down, blocked, or an approval
|
|
305
|
-
// outstanding) the adapter refuses it the way it refuses any card request;
|
|
306
|
-
// - a card capture with a preparation pauses for the claim flow, which pays it
|
|
307
|
-
// only through a ready Fiserv preparation for this capture's endpoint, key and
|
|
308
|
-
// merchant (prepared-processor.ts) and refuses any other body as
|
|
309
|
-
// checkout_changed, and continues it with the approved envelope at
|
|
310
|
-
// source.encryptionData (cse-body.ts).
|
|
311
|
-
// Synchronous until it must continue or abort, like every gate. Exported for the
|
|
312
|
-
// gate registry's tests.
|
|
313
|
-
export const fiservPreClaim = (ctx) => {
|
|
314
|
-
const { url, method, body } = ctx.request;
|
|
315
|
-
if (String(method).toUpperCase() !== 'POST' || !isFiservCaptureHost(url) || !ctx.vault.isCardRequest(url, method))
|
|
316
|
-
return undefined;
|
|
317
|
-
if (!isFiservPausableBody(body))
|
|
318
|
-
return fiservContinue(ctx);
|
|
319
|
-
const idle = ctx.attachmentReady() && !ctx.stripeAttachmentBusy() && !ctx.interceptionAborted() && !ctx.terminal()
|
|
320
|
-
&& !ctx.lifecycle.isBlocked() && !ctx.awaitingApproval();
|
|
321
|
-
if (idle && !ctx.preparationGate.isEngaged())
|
|
322
|
-
return fiservPreparationRequired(ctx);
|
|
323
|
-
return undefined;
|
|
324
|
-
};
|
|
325
|
-
async function fiservContinue(ctx) {
|
|
326
|
-
await ctx.effects.continueRequest().catch(() => { });
|
|
327
|
-
return { kind: 'continue' };
|
|
328
|
-
}
|
|
329
|
-
async function fiservPreparationRequired(ctx) {
|
|
330
|
-
ctx.preparationGate.observeUnprepared();
|
|
331
|
-
ctx.lifecycle.preparationRequired();
|
|
332
|
-
ctx.onEvent?.({ type: 'blocked', detail: 'preparation_required' });
|
|
333
|
-
await ctx.effects.abort();
|
|
334
|
-
return { kind: 'abort' };
|
|
335
|
-
}
|
|
336
|
-
// The terminal arbiter, always last. A request that no gate claimed and that is
|
|
337
|
-
// neither a native Stripe step nor a recognized card request continues, unless
|
|
338
|
-
// it matches a declared payment endpoint we do not support (then it aborts as an
|
|
339
|
-
// unsupported checkout). Otherwise it is a card request and pauses for the vault
|
|
340
|
-
// claim flow. The Stripe gate pauses its own claimed final step before any later
|
|
341
|
-
// gate runs, so the stripeStep check here is only a second guard. The pause is
|
|
342
|
-
// synchronous (no effect); the continue swallows a transport failure, as the
|
|
343
|
-
// inline CDP path did.
|
|
344
|
-
const unsupportedPreClaim = (ctx) => {
|
|
345
|
-
const { url, method } = ctx.request;
|
|
346
|
-
if (!ctx.stripeStep && !ctx.vault.isCardRequest(url, method)) {
|
|
347
|
-
if (ctx.guards.matches(url, method))
|
|
348
|
-
return unsupportedAbort(ctx);
|
|
349
|
-
return unsupportedContinue(ctx);
|
|
350
|
-
}
|
|
351
|
-
return { kind: 'pause', stripeStep: ctx.stripeStep };
|
|
352
|
-
};
|
|
353
|
-
async function unsupportedAbort(ctx) {
|
|
354
|
-
const { url, method } = ctx.request;
|
|
355
|
-
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
356
|
-
ctx.lifecycle.unsupported();
|
|
357
|
-
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method } });
|
|
358
|
-
await ctx.effects.abort();
|
|
359
|
-
return { kind: 'abort' };
|
|
360
|
-
}
|
|
361
|
-
async function unsupportedContinue(ctx) {
|
|
362
|
-
await ctx.effects.continueRequest().catch(() => { });
|
|
363
|
-
return { kind: 'continue' };
|
|
364
|
-
}
|
|
365
|
-
/** The processor gates, in order. A new family adds one entry here; the terminal
|
|
366
|
-
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
367
|
-
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
368
|
-
* pauses each one itself. */
|
|
369
|
-
export const PSP_PRE_CLAIM_GATES = [mercadoPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, fiservPreClaim];
|
|
370
|
-
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
371
|
-
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
372
|
-
* Returns synchronously while every gate defers synchronously (an ordinary card
|
|
373
|
-
* request pauses with no await), and a promise once a gate does I/O. */
|
|
374
|
-
export function dispatchPreClaim(ctx, gates = PSP_PRE_CLAIM_GATES) {
|
|
375
|
-
return runGates(ctx, gates, 0);
|
|
376
|
-
}
|
|
377
|
-
function runGates(ctx, gates, from) {
|
|
378
|
-
for (let i = from; i < gates.length; i++) {
|
|
379
|
-
const result = gates[i](ctx);
|
|
380
|
-
if (result instanceof Promise)
|
|
381
|
-
return result.then((decision) => decision ?? runGates(ctx, gates, i + 1));
|
|
382
|
-
if (result)
|
|
383
|
-
return result;
|
|
384
|
-
}
|
|
385
|
-
return unsupportedPreClaim(ctx);
|
|
386
|
-
}
|