@agent-cards/sdk 0.23.9 → 0.23.11

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 (65) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/PREFLIGHT.md +7 -5
  3. package/README.md +211 -102
  4. package/dist/aci.d.ts +7 -5
  5. package/dist/aci.generated.d.ts +2 -1
  6. package/dist/aci.generated.js +14 -4
  7. package/dist/aci.js +28 -7
  8. package/dist/amadeus-checkout.generated.js +2 -2
  9. package/dist/builtin-registry.generated.js +1 -1
  10. package/dist/card-fields.generated.js +165 -10
  11. package/dist/cdp.js +86 -34
  12. package/dist/client.d.ts +73 -33
  13. package/dist/client.js +136 -45
  14. package/dist/creditguard.generated.d.ts +1 -1
  15. package/dist/creditguard.generated.js +1 -3
  16. package/dist/cse-body.d.ts +7 -6
  17. package/dist/cse-body.js +8 -7
  18. package/dist/datatrans.generated.js +8 -5
  19. package/dist/elavon-converge.d.ts +5 -2
  20. package/dist/elavon-converge.generated.js +9 -5
  21. package/dist/elavon-converge.js +8 -4
  22. package/dist/fiserv.d.ts +9 -5
  23. package/dist/fiserv.generated.d.ts +3 -3
  24. package/dist/fiserv.generated.js +50 -20
  25. package/dist/fiserv.js +10 -6
  26. package/dist/lifecycle.d.ts +9 -1
  27. package/dist/lifecycle.js +34 -0
  28. package/dist/payrails.d.ts +78 -18
  29. package/dist/payrails.generated.d.ts +12 -2
  30. package/dist/payrails.generated.js +213 -18
  31. package/dist/payrails.js +165 -44
  32. package/dist/pre-claim.d.ts +7 -0
  33. package/dist/pre-claim.js +98 -36
  34. package/dist/preflight-capabilities.generated.d.ts +0 -106
  35. package/dist/preflight-capabilities.generated.js +0 -153
  36. package/dist/preflight-catalog.json +61 -181
  37. package/dist/preflight-schemas.json +6 -6
  38. package/dist/preflight.generated.d.ts +0 -106
  39. package/dist/preflight.generated.js +27 -15
  40. package/dist/preparation.d.ts +2 -0
  41. package/dist/preparation.js +4 -2
  42. package/dist/prepared-processor.d.ts +19 -5
  43. package/dist/prepared-processor.js +21 -5
  44. package/dist/primer.generated.d.ts +5 -1
  45. package/dist/primer.generated.js +202 -34
  46. package/dist/registry.d.ts +18 -12
  47. package/dist/registry.js +11 -8
  48. package/dist/shiji-sts.d.ts +24 -22
  49. package/dist/shiji-sts.generated.d.ts +1 -1
  50. package/dist/shiji-sts.generated.js +12 -8
  51. package/dist/shiji-sts.js +49 -46
  52. package/dist/stripe-checkout.generated.js +157 -49
  53. package/dist/verifone.generated.js +192 -17
  54. package/dist/verifone.js +1 -1
  55. package/dist/worldline-mycheckout.d.ts +27 -0
  56. package/dist/worldline-mycheckout.generated.d.ts +29 -0
  57. package/dist/worldline-mycheckout.generated.js +162 -0
  58. package/dist/worldline-mycheckout.js +93 -0
  59. package/dist/wspay.d.ts +17 -16
  60. package/dist/wspay.generated.d.ts +6 -1
  61. package/dist/wspay.generated.js +13 -3
  62. package/dist/wspay.js +9 -11
  63. package/examples/preflight/kernel-native/README.md +1 -1
  64. package/examples/preflight/kernel-native/inventory.json +1 -19
  65. package/package.json +3 -3
package/dist/cdp.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { BUILTIN_REGISTRY, cardUrlPatterns } from './registry.js';
2
2
  import { AdyenTestPlatformRefusedError, AmountRequiredError, ApprovalDeclinedError, ApprovalTimeoutError, CardEncryptedError, CheckoutApiError, CheckoutCancelledError, CheckoutPreparationError, MerchantTotalError, PaymentOutcomeUnknownError, PreparationRequiredError, ProcessorRefusedError, UnsupportedCapabilityError, UnsupportedModeError, } from './client.js';
3
3
  import { PreparationGate } from './preparation.js';
4
+ import { preparedApprovalAnswersRetry } from './prepared-processor.js';
4
5
  import { StripeCheckoutGate } from './stripe-checkout.js';
5
6
  import { MercadoCheckoutGate } from './mercado-checkout.js';
6
7
  import { MERCADO_CHECKOUT_PATTERN } from './mercado-checkout.generated.js';
@@ -29,6 +30,7 @@ import { CARD_ENDPOINT_REFUSED_STEP_ANSWER, CARD_ENDPOINT_WITHHELD_ANSWER, CardE
29
30
  import { CardEndpointFactsWatch } from './card-endpoint-facts.js';
30
31
  import { PAYRAILS_GUARD_PATTERNS, PAYRAILS_INIT_BODY_LIMIT, PayrailsSessionCapture, isPayrailsHost, payrailsInitCandidate, recognizesPayrails, } from './payrails.js';
31
32
  import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoint-page.js';
33
+ import { WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
32
34
  /**
33
35
  * How an adapter fails a paused request it will not let through: as a network error for a
34
36
  * CyberSource Flex request (refusedRequestFailure, so the Microform field reports it to the page)
@@ -328,15 +330,15 @@ function cseAuthorizedDetail(replay, url, shijiCvv = null) {
328
330
  * getCvvId companion right before the continue (shiji-sts.ts). It is written
329
331
  * only into `approved`, the request the approval was created from, byte for
330
332
  * byte, so the accessToken and keySequence are the ones the API reviewed, and
331
- * the cvv is bound to the reviewed getCvvId keys the approval served
332
- * (`shijiCvvKeySequences`): no keys, no continuation. Every other processor
333
- * takes cseContinuationBody (cse-body.ts cseBody), and `shijiCvv` and
334
- * `shijiCvvKeySequences` are null.
333
+ * the cvv is bound to the SHA-256 of the reviewed getCvvId keys the approval
334
+ * served (`shijiCvvKeySequenceSha256`): no keys, no continuation. Every other
335
+ * processor takes cseContinuationBody (cse-body.ts cseBody), and `shijiCvv` and
336
+ * `shijiCvvKeySequenceSha256` are null.
335
337
  */
336
338
  function cseContinuation(opts, url, body, replay, approved, preparation) {
337
339
  const shiji = shijiStsTokenContinuation(url, body, replay.substitutions, approved, replay.kind === 'merchant_hosted' || replay.kind === 'verifone' ? undefined : replay.shijiStsCompanion);
338
- return shiji ? { body: shiji.body, shijiCvv: shiji.cvv, shijiCvvKeySequences: shiji.cvvKeySequences }
339
- : { body: cseContinuationBody(opts, body, replay, url, preparation), shijiCvv: null, shijiCvvKeySequences: null };
340
+ return shiji ? { body: shiji.body, shijiCvv: shiji.cvv, shijiCvvKeySequenceSha256: shiji.cvvKeySequenceSha256 }
341
+ : { body: cseContinuationBody(opts, body, replay, url, preparation), shijiCvv: null, shijiCvvKeySequenceSha256: null };
340
342
  }
341
343
  /**
342
344
  * The opaque page id a Shiji STS companion is bound to: the CDP session and
@@ -1287,7 +1289,10 @@ function sameCheckoutRequest(first, retry) {
1287
1289
  * answer is a token or ciphertext, and any request for the same purchase can
1288
1290
  * carry it. No for a hosted form (a navigation: there is no request to
1289
1291
  * answer once the page has moved on), a prepared checkout (single use, bound
1290
- * to one native request by design), a native Stripe Checkout step, a
1292
+ * to one native request by design) other than a Bambora token create, whose
1293
+ * card frame gives up 5 seconds after sending it and whose page asks again
1294
+ * with the same body (preparedApprovalAnswersRetry), from that same card frame
1295
+ * only (sameFrame), a native Stripe Checkout step, a
1291
1296
  * Mercado Pago request whose claim is bound to one document read, a Juspay
1292
1297
  * /txns (the device pays that request's own order; a page that clicks Pay
1293
1298
  * again creates a new order, which the earlier answer would send to the
@@ -1303,12 +1308,23 @@ function sameCheckoutRequest(first, retry) {
1303
1308
  * getTokenId while the cardholder decides. Unknown modes (a duck-typed vault
1304
1309
  * without checkoutModeOf) are taken as `token`.
1305
1310
  */
1306
- function survivesAbandonment(opts, url, method, navigation, bound) {
1311
+ function survivesAbandonment(opts, url, method, navigation, bound, preparation) {
1307
1312
  if (navigation || bound || isShijiStsHost(url))
1308
1313
  return false;
1314
+ if (preparation && !preparedApprovalAnswersRetry(preparation.psp))
1315
+ return false;
1309
1316
  const mode = typeof opts.vault?.checkoutModeOf === 'function' ? opts.vault.checkoutModeOf(url, method) : 'token';
1310
1317
  return mode === 'token' || mode === 'cse';
1311
1318
  }
1319
+ /**
1320
+ * Whether `frameId` on `sessionId` is `owner`, the frame a prepared request came from. A prepared
1321
+ * approval that outlives its request (survivesAbandonment) belongs to that card frame for as long as
1322
+ * it lasts: only that frame's retry is answered from it, and the frame's removal stops the attachment
1323
+ * even after the page gave the request up. A frame with no id is nobody's.
1324
+ */
1325
+ function sameFrame(owner, sessionId, frameId) {
1326
+ return !!owner && typeof owner.frameId === 'string' && owner.frameId !== '' && owner.frameId === frameId && owner.sessionId === sessionId;
1327
+ }
1312
1328
  /**
1313
1329
  * One card request's attempt at an approval, and the browser request that
1314
1330
  * currently carries it. Separate from explicit cancellation: the client can
@@ -1973,6 +1989,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1973
1989
  let lastSubmitted = null;
1974
1990
  // The approved Datatrans answers, per frameId: the repeat rule (datatrans.ts).
1975
1991
  const datatransRepeats = new DatatransRepeats();
1992
+ // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
1993
+ const worldlineProducts = new WorldlineMyCheckoutProducts();
1976
1994
  // The Shiji STS getCvvId companion of the getTokenId this attachment last continued (shiji-sts.ts).
1977
1995
  // A cancelled attachment drops it, and the device's CVV ciphertext with it, at once: no getCvvId
1978
1996
  // can use it any more (pre-claim.ts refuses every one once the lifecycle is aborted). The page,
@@ -2284,9 +2302,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2284
2302
  if (!(method === 'Page.frameDetached' && params.reason === 'swap')) {
2285
2303
  microforms.forget(method === 'Page.frameDetached' ? params.frameId : method === 'Target.detachedFromTarget' ? params.targetId : undefined);
2286
2304
  }
2305
+ // A prepared request's card frame ends its approval even once the page gave the request up and its
2306
+ // live frame id is gone; a target's detach is still read from the session, which a loss keeps.
2287
2307
  if (activeRequest && (method === 'Target.detachedFromTarget' ? activeRequest.sessionId === params.sessionId
2288
2308
  : method === 'Inspector.detached' ? activeRequest.sessionId === sessionId
2289
- : activeRequest.sessionId === sessionId && activeRequest.frameId === params.frameId))
2309
+ : (activeRequest.sessionId === sessionId && activeRequest.frameId === params.frameId)
2310
+ || sameFrame(activeRequest.preparedFrame, sessionId, params.frameId)))
2290
2311
  activeRequest.attempt.stop();
2291
2312
  // The frame or target the bound getTokenId came from is gone, so its getCvvId cannot follow:
2292
2313
  // drop the companion binding and the CVV ciphertext it holds (a binding elsewhere stays).
@@ -2437,6 +2458,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2437
2458
  frame: typeof frameId === 'string' && frameId ? frameId : undefined,
2438
2459
  payrails,
2439
2460
  shijiSts: { companions: shijiCompanions, page: () => cdpShijiPage(sessionId, frameId) },
2461
+ worldlineMyCheckout: worldlineProducts,
2440
2462
  });
2441
2463
  const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
2442
2464
  if (preClaim.kind !== 'pause')
@@ -2477,8 +2499,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2477
2499
  return;
2478
2500
  }
2479
2501
  let preparation;
2502
+ // The page asking again while a prepared approval outlives its first request (survivesAbandonment):
2503
+ // the preparation is already bound to that approval, so the request is judged as its retry below
2504
+ // and never claimed again.
2505
+ const preparedRetry = awaitingApproval && activeRequest?.attempt.lost === true && preparationGate.isConsumed();
2480
2506
  try {
2481
- preparation = stripeStep ? undefined : preparationGate.claim(request.url, request.method, pausedBody(request), request.headers);
2507
+ preparation = stripeStep || preparedRetry ? undefined : preparationGate.claim(request.url, request.method, pausedBody(request), request.headers);
2482
2508
  }
2483
2509
  catch (error) {
2484
2510
  opts.onEvent?.({ type: 'blocked', detail: failureSummary(error) });
@@ -2499,7 +2525,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2499
2525
  && typeof networkId === 'string' && networkId) {
2500
2526
  const retryBody = pausedBody(request);
2501
2527
  const retry = retryBody === null ? null : requestIdentity(opts, request.url, request.method, retryBody, documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape);
2502
- if (retry && activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)) {
2528
+ // A prepared approval is answered only in the card frame it was bound in: the same request from
2529
+ // another frame (a replacement for a removed one, or a second card frame) is refused below.
2530
+ if (retry && activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
2531
+ && (!activeRequest.preparedFrame || sameFrame(activeRequest.preparedFrame, sessionId, frameId))) {
2503
2532
  if (preparation)
2504
2533
  preparationGate.retireUnboundClaim();
2505
2534
  activeRequest.requestId = requestId;
@@ -2535,7 +2564,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2535
2564
  }
2536
2565
  // Same stop condition as the Playwright adapter: once a failure proves
2537
2566
  // retrying is pointless, fail the request without calling the API again.
2538
- if (terminal || lifecycle.isBlocked() || awaitingApproval || Date.now() < quietUntil) {
2567
+ // A request that skipped the spent preparation's claim and was not bound as
2568
+ // its retry (the approval ended while it was judged) is refused as that
2569
+ // claim would refuse it, never asked as a new approval.
2570
+ if (terminal || lifecycle.isBlocked() || awaitingApproval || preparedRetry || Date.now() < quietUntil) {
2539
2571
  let repeatedAuthorizationId = null;
2540
2572
  if (lastSubmitted) {
2541
2573
  const body = pausedBody(request);
@@ -2543,7 +2575,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2543
2575
  repeatedAuthorizationId = lastSubmitted.authorizationId;
2544
2576
  }
2545
2577
  }
2546
- const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding' : 'awaiting approval cooldown');
2578
+ const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
2579
+ : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
2547
2580
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
2548
2581
  if (preparation)
2549
2582
  preparationGate.retireUnboundClaim();
@@ -2571,8 +2604,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2571
2604
  awaitingApproval = true;
2572
2605
  // A card endpoint approval never outlives its request: the device answers that
2573
2606
  // request, and a retry after the page gave up on it is refused, not bound.
2574
- const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url, request.method, resourceType === 'Document', !!preparation || !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url, request.method)
2575
- || juspayCheckout.bindsApproval(request.url, request.method)));
2607
+ const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url, request.method, resourceType === 'Document', !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url, request.method)
2608
+ || juspayCheckout.bindsApproval(request.url, request.method), preparation));
2576
2609
  let handoffStarted = false;
2577
2610
  // The approval the device gave, and whether it reached a browser request.
2578
2611
  let approvedId = null;
@@ -2614,7 +2647,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2614
2647
  }
2615
2648
  attempt.bind({ requestId, sessionId, request });
2616
2649
  activeRequest = { attempt, requestId, networkId, sessionId, frameId, request,
2617
- identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape) };
2650
+ identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape),
2651
+ preparedFrame: preparation ? { sessionId, frameId } : undefined };
2618
2652
  // An ACI card-number request: the page's CVC and expiry requests for its checkout are held
2619
2653
  // until this approval ends (see AciGate). One whose checkout already has a card-number
2620
2654
  // request running or answered never begins; the pre-claim gate refuses it before this.
@@ -2787,7 +2821,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2787
2821
  const continuation = cseContinuation(opts, deliverRequest.url, deliverBody, replay, { url: request.url, body }, preparation);
2788
2822
  const postData = Buffer.from(continuation.body).toString('base64');
2789
2823
  if (continuation.shijiCvv !== null) {
2790
- shijiCompanions.bind({ url: request.url, method: request.method, body }, cdpShijiPage(sessionId, frameId), continuation.shijiCvv, continuation.shijiCvvKeySequences ?? []);
2824
+ shijiCompanions.bind({ url: request.url, method: request.method, body }, cdpShijiPage(sessionId, frameId), continuation.shijiCvv, continuation.shijiCvvKeySequenceSha256 ?? []);
2791
2825
  }
2792
2826
  handoffStarted = true;
2793
2827
  if (replay.kind === 'verifone') {
@@ -3276,7 +3310,8 @@ export async function attachToPlaywright(page, opts) {
3276
3310
  }
3277
3311
  });
3278
3312
  page.on?.('framedetached', (frame) => {
3279
- if (activeRequest?.frames.includes(frame))
3313
+ // As in attachToCdp: a prepared request's card frame ends its approval even after the page gave the request up.
3314
+ if (activeRequest && (activeRequest.frames.includes(frame) || activeRequest.preparedFrames?.includes(frame)))
3280
3315
  activeRequest.attempt.stop();
3281
3316
  const gone = frame && typeof frame === 'object' ? playwrightShijiFrames.get(frame) : undefined;
3282
3317
  if (gone)
@@ -3287,6 +3322,8 @@ export async function attachToPlaywright(page, opts) {
3287
3322
  let lastSubmitted = null;
3288
3323
  // The approved Datatrans answers, per Frame: the repeat rule (datatrans.ts).
3289
3324
  const datatransRepeats = new DatatransRepeats();
3325
+ // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
3326
+ const worldlineProducts = new WorldlineMyCheckoutProducts();
3290
3327
  const frameOf = (request) => { try {
3291
3328
  return request.frame?.() ?? undefined;
3292
3329
  }
@@ -3304,7 +3341,10 @@ export async function attachToPlaywright(page, opts) {
3304
3341
  // so the ACI gate routes them (never ACI's widget script, /bins or /v1/collect).
3305
3342
  // The matcher sees only the URL. Worldpay eProtect's registration is a GET, so its family and
3306
3343
  // the vendor's give-up reports are routed by the eProtect gate (never its scripts).
3307
- (url) => opts.vault.isCardRequest(url.toString()) || guards.matches(url.toString()) || stripeCheckout.matches(url.toString()) || mercadoCheckout.matches(url.toString())
3344
+ // Gravy's session fields are a PUT, so a URL a PUT recognizer claims is routed too; the
3345
+ // pre-claim gates then pause only the PUT and continue the URL's other methods.
3346
+ (url) => opts.vault.isCardRequest(url.toString()) || opts.vault.isCardRequest(url.toString(), 'PUT')
3347
+ || guards.matches(url.toString()) || stripeCheckout.matches(url.toString()) || mercadoCheckout.matches(url.toString())
3308
3348
  || eprotect.matches(url.toString())
3309
3349
  // An armed merchant profile's endpoint or a sibling of it (merchant-hosted.ts), as attachToCdp arms it.
3310
3350
  || (typeof opts.vault.merchantProfileOf === 'function' && opts.vault.merchantProfileOf(url.toString()) !== null)
@@ -3423,6 +3463,7 @@ export async function attachToPlaywright(page, opts) {
3423
3463
  frame: frameOf(request),
3424
3464
  payrails,
3425
3465
  shijiSts: { companions: shijiCompanions, page: async () => (await playwrightShijiDocumentPage(request)) ?? `pw:none:${++playwrightShijiSeq}` },
3466
+ worldlineMyCheckout: worldlineProducts,
3426
3467
  });
3427
3468
  const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
3428
3469
  if (preClaim.kind !== 'pause')
@@ -3458,8 +3499,10 @@ export async function attachToPlaywright(page, opts) {
3458
3499
  if (withoutCard === 'continue')
3459
3500
  return route.fallback();
3460
3501
  let preparation;
3502
+ // As in attachToCdp: the retry of a prepared request whose approval outlived it is never claimed again.
3503
+ const preparedRetry = awaitingApproval && activeRequest?.attempt.lost === true && preparationGate.isConsumed();
3461
3504
  try {
3462
- preparation = stripeStep ? undefined : preparationGate.claim(request.url(), request.method(), request.postData() ?? '', request.headers());
3505
+ preparation = stripeStep || preparedRetry ? undefined : preparationGate.claim(request.url(), request.method(), request.postData() ?? '', request.headers());
3463
3506
  }
3464
3507
  catch (error) {
3465
3508
  opts.onEvent?.({ type: 'blocked', detail: failureSummary(error) });
@@ -3473,15 +3516,17 @@ export async function attachToPlaywright(page, opts) {
3473
3516
  // merchantAttempt and the same check in attachToCdp).
3474
3517
  if (awaitingApproval && activeRequest?.attempt.lost && !terminal && !setupStop.signal.aborted && !lifecycle.isCancelled()) {
3475
3518
  const retry = requestIdentity(opts, request.url(), request.method(), request.postData() ?? '', documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape);
3476
- if (activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)) {
3519
+ const retryFrames = [];
3520
+ try {
3521
+ for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
3522
+ retryFrames.push(frame);
3523
+ }
3524
+ catch { /* covered by requestfailed */ }
3525
+ // As in attachToCdp: a prepared approval is answered only in the card frame it was bound in.
3526
+ if (activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
3527
+ && (!activeRequest.preparedFrames || (retryFrames.length > 0 && retryFrames[0] === activeRequest.preparedFrames[0]))) {
3477
3528
  if (preparation)
3478
3529
  preparationGate.retireUnboundClaim();
3479
- const retryFrames = [];
3480
- try {
3481
- for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
3482
- retryFrames.push(frame);
3483
- }
3484
- catch { /* covered by requestfailed */ }
3485
3530
  activeRequest.request = request;
3486
3531
  activeRequest.frames = retryFrames;
3487
3532
  opts.onEvent?.({ type: 'card_request_paused', detail: { url: eventUrl(opts.vault, request.url()), recognizer: recognizerNameOf(opts, request.url(), request.method()), authorizationId: lifecycle.getState().authorizationId, retry: true } });
@@ -3507,7 +3552,8 @@ export async function attachToPlaywright(page, opts) {
3507
3552
  // CDP adapter's Fetch.failRequest uses, so a refused request resolves
3508
3553
  // identically whichever adapter is attached. An Amadeus card request is
3509
3554
  // 'failed' (ERR_FAILED) in both too, see failsAsNetworkError (refusalFailure).
3510
- if (terminal || lifecycle.isBlocked() || awaitingApproval || Date.now() < quietUntil) {
3555
+ // As in attachToCdp: an unbound request that skipped the spent preparation's claim is refused.
3556
+ if (terminal || lifecycle.isBlocked() || awaitingApproval || preparedRetry || Date.now() < quietUntil) {
3511
3557
  let repeatedAuthorizationId = null;
3512
3558
  if (lastSubmitted) {
3513
3559
  let body = null;
@@ -3519,7 +3565,8 @@ export async function attachToPlaywright(page, opts) {
3519
3565
  repeatedAuthorizationId = lastSubmitted.authorizationId;
3520
3566
  }
3521
3567
  }
3522
- const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding' : 'awaiting approval cooldown');
3568
+ const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
3569
+ : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
3523
3570
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
3524
3571
  if (preparation)
3525
3572
  preparationGate.retireUnboundClaim();
@@ -3546,8 +3593,8 @@ export async function attachToPlaywright(page, opts) {
3546
3593
  navigation = false;
3547
3594
  }
3548
3595
  // A card endpoint approval never outlives its request (see attachToCdp).
3549
- const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url(), request.method(), navigation, !!preparation || !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url(), request.method())
3550
- || juspayCheckout.bindsApproval(request.url(), request.method())));
3596
+ const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url(), request.method(), navigation, !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url(), request.method())
3597
+ || juspayCheckout.bindsApproval(request.url(), request.method()), preparation));
3551
3598
  const frames = [];
3552
3599
  try {
3553
3600
  for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
@@ -3555,9 +3602,13 @@ export async function attachToPlaywright(page, opts) {
3555
3602
  }
3556
3603
  catch { /* requestfailed/page close still cover unavailable frame metadata */ }
3557
3604
  // Judged on the request that currently carries the attempt: after a
3558
- // retry binds, the first request's failure is history, not a loss.
3605
+ // retry binds, the first request's failure is history, not a loss. A
3606
+ // prepared request's own frames are judged for as long as the approval
3607
+ // lasts, as framedetached judges them: their removal ends it.
3559
3608
  const assertRequestLive = () => {
3560
3609
  const current = attempt.target;
3610
+ if (preparation && frames.some((frame) => frame.isDetached?.()))
3611
+ attempt.stop();
3561
3612
  if (current && (current.request.failure?.() || current.frames.some((frame) => frame.isDetached?.())))
3562
3613
  attempt.lose();
3563
3614
  if (page.isClosed?.())
@@ -3587,7 +3638,8 @@ export async function attachToPlaywright(page, opts) {
3587
3638
  const payToInterceptMs = readPayToInterceptMs(opts.payClickedAt, approvalStartedAt);
3588
3639
  attempt.bind({ route, request, frames });
3589
3640
  activeRequest = { request, frames, attempt,
3590
- identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape) };
3641
+ identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape),
3642
+ preparedFrames: preparation ? [...frames] : undefined };
3591
3643
  // An ACI card-number request: see the same step in attachToCdp.
3592
3644
  if (!aci.begin(request.url(), request.method(), body, preparation?.merchantOrigin)) {
3593
3645
  throw new CheckoutPreparationError(preparation?.id ?? null, ACI_CHECKOUT_REUSED);
@@ -3728,7 +3780,7 @@ export async function attachToPlaywright(page, opts) {
3728
3780
  if (boundPage === null)
3729
3781
  throw new ShijiStsError('unsupported');
3730
3782
  assertRequestLive();
3731
- shijiCompanions.bind({ url: request.url(), method: request.method(), body }, boundPage, continuation.shijiCvv, continuation.shijiCvvKeySequences ?? []);
3783
+ shijiCompanions.bind({ url: request.url(), method: request.method(), body }, boundPage, continuation.shijiCvv, continuation.shijiCvvKeySequenceSha256 ?? []);
3732
3784
  }
3733
3785
  handoffStarted = true;
3734
3786
  if (replay.kind === 'verifone') {
package/dist/client.d.ts CHANGED
@@ -41,11 +41,13 @@ export interface PausedRequest {
41
41
  facts?: CardEndpointFactsRecord;
42
42
  };
43
43
  /**
44
- * A Payrails card save (mode device_encrypt) only: the session key the page's
45
- * Payrails init payload named (vaultConfiguration.encryptionPublicKey), which
46
- * the save itself does not carry. The adapters capture it; a caller running
47
- * its own interception passes it here, and a save without it is refused
48
- * before anything is created.
44
+ * A Payrails card save or card-form payment (mode device_encrypt) only: the
45
+ * session key the page's Payrails init payload named
46
+ * (vaultConfiguration.encryptionPublicKey), which the request itself does not
47
+ * carry, and for a card-form payment the payload's holderReference, which the
48
+ * payment's ciphertext carries and its body does not. The adapters capture
49
+ * both; a caller running its own interception passes them here, and a request
50
+ * without them is refused before anything is created.
49
51
  */
50
52
  payrails?: PayrailsSidecar;
51
53
  }
@@ -54,7 +56,7 @@ export interface PausedRequest {
54
56
  * only recognizers in these modes, so a request this build cannot complete
55
57
  * is never paused) and sent on every create. `device_encrypt`: the adapters
56
58
  * capture the Payrails session key the page's init payload names (payrails.ts),
57
- * and the cardholder's device encrypts and sends the save.
59
+ * and the cardholder's device encrypts and sends the save or card-form payment.
58
60
  */
59
61
  export declare const SUPPORTED_MODES: readonly CheckoutMode[];
60
62
  /**
@@ -124,14 +126,17 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
124
126
  * `primer_payment_instruments`: Primer's card tokenization, which this build
125
127
  * judges before it pauses (primer.ts) and hands back to the page only as a
126
128
  * single-use token. A merchant's client session can save the card after the
127
- * payment with no sign in that request, so the API creates a Primer
128
- * authorization only for a test-mode client and refuses a live one (400
129
- * invalid_primer_request, live_keep_on_file) until the cardholder can consent
130
- * to that save; this build then aborts the paused request with nothing sent. A
131
- * gated recognizer never sits in BUILTIN_REGISTRY, so it is paused only after a
132
- * sync.
133
- * `payrails_device_encrypt`: Payrails' card-form save, which this build pauses
134
- * only once it has bound the save to a captured session (payrails.ts). A gated
129
+ * payment with no sign in that request, so the API creates a live Primer
130
+ * authorization only when it names the checkout page's https origin
131
+ * (checkout_origin, which this build sends from the page's origin): the
132
+ * approval says the merchant keeps the card on file, under the merchant name
133
+ * the create sent, which Agentcard cannot confirm, and pays only with a card
134
+ * the person stored. A refused create aborts the paused request with nothing
135
+ * sent. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused only
136
+ * after a sync.
137
+ * `payrails_device_encrypt`: Payrails' card save and card-form payment, which
138
+ * this build pauses only once it has bound the request to a captured session
139
+ * (payrails.ts). A gated
135
140
  * recognizer never sits in BUILTIN_REGISTRY, so it is paused only after a sync.
136
141
  * `converge_hpp_process`: Elavon Converge's Hosted Payment Page card request
137
142
  * (elavon-converge.ts), paused in token mode and judged by the pre-claim gate.
@@ -183,6 +188,22 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
183
188
  * finds a card in it (cybersource-secure-acceptance.ts). Served on the same terms,
184
189
  * so every other company keeps the stop every build applies to a card posted to
185
190
  * Secure Acceptance.
191
+ * `gravy_session_fields_put`: Gravy Secure Fields' update of a checkout session,
192
+ * the one card request a recognizer declares as a PUT. This build pauses that PUT
193
+ * from Gravy's controller frame as it pauses a POST (isRecognizerRequest), creates
194
+ * the authorization with the PUT, and answers the frame with Gravy's own answer to
195
+ * the cardholder's device. Served on the same terms; an older build never sees the
196
+ * entry, and every company Agentcard has not turned Gravy on for keeps today's path,
197
+ * where the request continues to Gravy untouched.
198
+ * `moneris_hosted_tokenization`: Moneris Hosted Tokenization's card request, the
199
+ * frame's form post to /HPPtoken/request.php. This build pauses it only when it
200
+ * carries the number, the expiry and the CVD the cardholder's device fills, fails a
201
+ * frame that leaves the expiry or CVD to the merchant's page in the browser with a
202
+ * `blocked` event, `request_without_card`, and answers with Moneris's token as the
203
+ * API recorded it. The device sends the card through the checkout relay. Served on
204
+ * the same terms: only to a build that lists it, for a company Agentcard turned
205
+ * Moneris on for. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused
206
+ * only after a sync.
186
207
  */
187
208
  export declare const SUPPORTED_CAPABILITIES: readonly string[];
188
209
  /**
@@ -276,12 +297,13 @@ export interface CseReplay extends ExecutionMetadata {
276
297
  authorizationId: string;
277
298
  substitutions: Substitutions;
278
299
  /**
279
- * Shiji STS only: the getCvvId keySequence values the API reviewed as the booking page's own and
280
- * served beside the set (`shiji_sts_companion`). The adapters bind the companion getCvvId to them
281
- * (shiji-sts.ts ShijiStsCompanions), so the device's CVV goes under no other key.
300
+ * Shiji STS only: the SHA-256 of each getCvvId keySequence the API reviewed as the booking page's
301
+ * own, served beside the set (`shiji_sts_companion.key_sequence_sha256`). The adapters bind the
302
+ * companion getCvvId to a key that hashes to one of them (shiji-sts.ts ShijiStsCompanions), so the
303
+ * device's CVV goes under no other key.
282
304
  */
283
305
  shijiStsCompanion?: Readonly<{
284
- keySequences: readonly string[];
306
+ keySequenceSha256: readonly string[];
285
307
  }>;
286
308
  amountAuthority?: AmountAuthority;
287
309
  }
@@ -401,12 +423,15 @@ export interface CardEndpointReplay extends ExecutionMetadata {
401
423
  /**
402
424
  * The device_encrypt flow (Payrails): the cardholder's device encrypted the
403
425
  * real card under the session key the page's init payload named and sent the
404
- * save to Payrails itself. This is Payrails' 201 for that save, rebuilt from an
405
- * allow-list (the instrument's identifiers, its status, the network, the BIN
406
- * cut to six digits, the last four, the expiry, the holder name and the
407
- * fingerprint), to replay into the paused request as in the token flow. The
408
- * ciphertext the device made never reaches the runtime or the page. Never an
409
- * autopilot result: autopilot refuses this mode.
426
+ * save or card-form payment to Payrails itself. This is Payrails' answer,
427
+ * rebuilt from an allow-list, to replay into the paused request as in the token
428
+ * flow: for a save its 201 (the instrument's identifiers, its status, the
429
+ * network, the BIN cut to six digits, the last four, the expiry, the holder
430
+ * name and the fingerprint); for a card-form payment its 2xx naming the
431
+ * authorize action (its name, id, time and execution link, which the page's
432
+ * Payrails SDK polls for the issuer's answer). The ciphertext the device made
433
+ * never reaches the runtime or the page. Never an autopilot result: autopilot
434
+ * refuses this mode.
410
435
  */
411
436
  export interface DeviceEncryptReplay extends ExecutionMetadata {
412
437
  mode: 'device_encrypt';
@@ -430,11 +455,14 @@ export interface PrepareCheckoutOptions {
430
455
  * card request is then the one request this preparation pays.
431
456
  *
432
457
  * A Fiserv checkout (psp 'fiserv'): the id of the key Agentcard pinned for the
433
- * merchant, `agentcard_sandbox` in the sandbox environment. Required for Fiserv,
458
+ * merchant: `homedepot_orangepay` for Blinds.com and homedepot.com, and
459
+ * `agentcard_sandbox` in the sandbox environment. Required for Fiserv,
434
460
  * and refused for every processor but Adyen and Fiserv. The cardholder is asked to
435
461
  * approve a payment to the merchant that key belongs to, so the checkout's `merchant`
436
- * must be that merchant's name (`Agentcard sandbox` for `agentcard_sandbox`), and the
437
- * one card capture this preparation pays must carry an envelope under that key.
462
+ * must be that merchant's name (`Home Depot` for `homedepot_orangepay`, `Agentcard
463
+ * sandbox` for `agentcard_sandbox`), and the one card capture this preparation pays
464
+ * must be that merchant's, under that key or under a credentials session's key,
465
+ * which the envelope's key replaces.
438
466
  *
439
467
  * A Verifone checkout (psp 'verifone'): the id of the Verifone profile Agentcard
440
468
  * reviewed for the merchant, which this client must have armed and enabled
@@ -711,6 +739,12 @@ export interface PlaceholderCard {
711
739
  /** The stored card's expiry year, four digits ("2031"). */
712
740
  readonly expiryYear: string;
713
741
  }
742
+ /**
743
+ * The placeholder number for a network (lowercase, as GET /api/v2/vault_cards names it) on a
744
+ * processor's page: the processor's own number for that network when `overrides` names one, else
745
+ * the network's default, else undefined for a network with no placeholder.
746
+ */
747
+ export declare function placeholderNumber(brand: string, psp?: string, overrides?: Readonly<Record<string, Readonly<Record<string, string>>>>): string | undefined;
714
748
  /**
715
749
  * The recognizer says this processor is preparation-required, and no preparation
716
750
  * was passed. Its card may be sent only after the cardholder approved a
@@ -1198,8 +1232,9 @@ export declare class VaultClient {
1198
1232
  private mergedRegistry;
1199
1233
  /**
1200
1234
  * The recogniser that claims a card request, or null; what a paused request names as
1201
- * `recognizer`. A POST to a recognised endpoint, or for a recognizer whose card request is a
1202
- * GET (Worldpay eProtect) that exact GET, query included.
1235
+ * `recognizer`. A POST to a recognised endpoint, a PUT for a recognizer whose card request is
1236
+ * a PUT (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect) that
1237
+ * exact GET, query included.
1203
1238
  */
1204
1239
  recognizerOf(url: string, method?: string): Recognizer | null;
1205
1240
  /** Refresh recognizers from the API so new PSPs work without a redeploy. */
@@ -1244,8 +1279,9 @@ export declare class VaultClient {
1244
1279
  cardOnFilePolicy(url: string, method?: string): string | null;
1245
1280
  /**
1246
1281
  * True when this request is a card tokenization we can take over: a POST to
1247
- * a recognized endpoint, or for a recognizer whose card request is a GET
1248
- * (Worldpay eProtect) that exact GET, query included.
1282
+ * a recognized endpoint, a PUT for a recognizer whose card request is a PUT
1283
+ * (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect)
1284
+ * that exact GET, query included.
1249
1285
  */
1250
1286
  isCardRequest(url: string, method?: string): boolean;
1251
1287
  /**
@@ -1306,12 +1342,16 @@ export declare class VaultClient {
1306
1342
  * preselects that card: without a pin the page preselects its own choice (its recommended
1307
1343
  * card, or the first card the cardholder's device can open), which can be another card. After
1308
1344
  * prepare(), pass the prepared checkout's `cardId`, the card the cardholder already approved.
1309
- * The facts hold only while the cardholder pays with that card. Throws PlaceholderCardError
1310
- * when there is no such card, its network has no placeholder, or it carries no expiry.
1345
+ * The facts hold only while the cardholder pays with that card. `psp` names the processor
1346
+ * whose card form the agent types into (the recognizer's name, `worldline_mycheckout`): a
1347
+ * processor that needs its own number for the card's network gets it, and every other gets the
1348
+ * network's default. Throws PlaceholderCardError when there is no such card, its network has no
1349
+ * placeholder, or it carries no expiry.
1311
1350
  */
1312
1351
  placeholderCard(input: {
1313
1352
  user: string;
1314
1353
  cardId?: string;
1354
+ psp?: string;
1315
1355
  signal?: AbortSignal;
1316
1356
  }): Promise<PlaceholderCard>;
1317
1357
  /** Wait for real device approval before the caller starts native tokenization. */