@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.
Files changed (106) hide show
  1. package/README.md +6 -753
  2. package/cdp.d.ts +1 -0
  3. package/cdp.js +2 -0
  4. package/index.d.ts +1 -0
  5. package/index.js +2 -0
  6. package/package.json +33 -33
  7. package/playwright.d.ts +1 -0
  8. package/playwright.js +2 -0
  9. package/preflight.d.ts +1 -0
  10. package/preflight.js +2 -0
  11. package/CHANGELOG.md +0 -132
  12. package/PREFLIGHT.md +0 -312
  13. package/dist/adyen-merchant-hosted.generated.d.ts +0 -277
  14. package/dist/adyen-merchant-hosted.generated.js +0 -1902
  15. package/dist/adyen.generated.d.ts +0 -24
  16. package/dist/adyen.generated.js +0 -64
  17. package/dist/attachment.d.ts +0 -11
  18. package/dist/attachment.js +0 -50
  19. package/dist/braintree.d.ts +0 -2
  20. package/dist/braintree.generated.d.ts +0 -10
  21. package/dist/braintree.generated.js +0 -302
  22. package/dist/braintree.js +0 -2
  23. package/dist/builtin-registry.generated.d.ts +0 -2
  24. package/dist/builtin-registry.generated.js +0 -1
  25. package/dist/card-fields.generated.d.ts +0 -3
  26. package/dist/card-fields.generated.js +0 -46
  27. package/dist/cdp.d.ts +0 -192
  28. package/dist/cdp.js +0 -2393
  29. package/dist/checkout-com.generated.d.ts +0 -4
  30. package/dist/checkout-com.generated.js +0 -183
  31. package/dist/client.d.ts +0 -849
  32. package/dist/client.js +0 -1754
  33. package/dist/cse-body.d.ts +0 -25
  34. package/dist/cse-body.js +0 -41
  35. package/dist/fiserv.d.ts +0 -65
  36. package/dist/fiserv.generated.d.ts +0 -73
  37. package/dist/fiserv.generated.js +0 -830
  38. package/dist/fiserv.js +0 -104
  39. package/dist/hosted-form.d.ts +0 -44
  40. package/dist/hosted-form.js +0 -78
  41. package/dist/index.d.ts +0 -18
  42. package/dist/index.js +0 -10
  43. package/dist/lifecycle.d.ts +0 -179
  44. package/dist/lifecycle.js +0 -395
  45. package/dist/mercado-checkout.d.ts +0 -20
  46. package/dist/mercado-checkout.generated.d.ts +0 -52
  47. package/dist/mercado-checkout.generated.js +0 -198
  48. package/dist/mercado-checkout.js +0 -108
  49. package/dist/merchant-handoff.d.ts +0 -54
  50. package/dist/merchant-handoff.js +0 -100
  51. package/dist/merchant-hosted.d.ts +0 -140
  52. package/dist/merchant-hosted.js +0 -170
  53. package/dist/merchant-total-watch.d.ts +0 -115
  54. package/dist/merchant-total-watch.js +0 -268
  55. package/dist/merchant-total.d.ts +0 -257
  56. package/dist/merchant-total.js +0 -383
  57. package/dist/owned-shop.generated.d.ts +0 -24
  58. package/dist/owned-shop.generated.js +0 -108
  59. package/dist/paysafe.generated.d.ts +0 -12
  60. package/dist/paysafe.generated.js +0 -87
  61. package/dist/playwright.d.ts +0 -3
  62. package/dist/playwright.js +0 -3
  63. package/dist/pre-claim.d.ts +0 -123
  64. package/dist/pre-claim.js +0 -386
  65. package/dist/preflight-capabilities.generated.d.ts +0 -1253
  66. package/dist/preflight-capabilities.generated.js +0 -1929
  67. package/dist/preflight-catalog.json +0 -4727
  68. package/dist/preflight-playwright.d.ts +0 -34
  69. package/dist/preflight-playwright.js +0 -355
  70. package/dist/preflight-schemas.json +0 -1122
  71. package/dist/preflight.d.ts +0 -1
  72. package/dist/preflight.generated.d.ts +0 -1965
  73. package/dist/preflight.generated.js +0 -570
  74. package/dist/preflight.js +0 -2
  75. package/dist/preparation.d.ts +0 -38
  76. package/dist/preparation.js +0 -191
  77. package/dist/prepared-processor.d.ts +0 -43
  78. package/dist/prepared-processor.js +0 -172
  79. package/dist/recurly.generated.d.ts +0 -1
  80. package/dist/recurly.generated.js +0 -87
  81. package/dist/registry.d.ts +0 -121
  82. package/dist/registry.js +0 -310
  83. package/dist/spreedly.generated.d.ts +0 -10
  84. package/dist/spreedly.generated.js +0 -332
  85. package/dist/stripe-checkout.d.ts +0 -81
  86. package/dist/stripe-checkout.generated.d.ts +0 -82
  87. package/dist/stripe-checkout.generated.js +0 -1093
  88. package/dist/stripe-checkout.js +0 -140
  89. package/dist/substitute.d.ts +0 -38
  90. package/dist/substitute.js +0 -23
  91. package/dist/substitutions.generated.d.ts +0 -11
  92. package/dist/substitutions.generated.js +0 -818
  93. package/examples/existing-browser.mjs +0 -63
  94. package/examples/preflight/classify-direct.mjs +0 -21
  95. package/examples/preflight/classify-kernel.mjs +0 -30
  96. package/examples/preflight/inspect-browser.mjs +0 -44
  97. package/examples/preflight/kernel-native/README.md +0 -112
  98. package/examples/preflight/kernel-native/documented-adapters.json +0 -113
  99. package/examples/preflight/kernel-native/inventory.json +0 -233
  100. package/examples/preflight/kernel-native/qualification.mjs +0 -182
  101. package/examples/preflight/kernel-profile.empty.json +0 -11
  102. package/examples/preflight/mollie-hosted.observations.json +0 -23
  103. package/examples/preflight/mollie-hosted.result.json +0 -103
  104. package/examples/preflight/stripe-script.direct.result.json +0 -92
  105. package/examples/preflight/stripe-script.observations.json +0 -16
  106. 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
- }