@agent-cards/sdk 0.23.8 → 0.23.10

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 (59) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/PREFLIGHT.md +7 -5
  3. package/README.md +181 -92
  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 +163 -9
  11. package/dist/cdp.js +79 -34
  12. package/dist/client.d.ts +61 -31
  13. package/dist/client.js +99 -42
  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/cybersource-secure-acceptance.generated.js +4 -3
  19. package/dist/datatrans.generated.js +8 -5
  20. package/dist/elavon-converge.d.ts +1 -1
  21. package/dist/elavon-converge.generated.js +7 -4
  22. package/dist/elavon-converge.js +4 -3
  23. package/dist/fiserv.d.ts +9 -5
  24. package/dist/fiserv.generated.d.ts +3 -3
  25. package/dist/fiserv.generated.js +50 -20
  26. package/dist/fiserv.js +10 -6
  27. package/dist/payrails.d.ts +78 -18
  28. package/dist/payrails.generated.d.ts +12 -2
  29. package/dist/payrails.generated.js +213 -18
  30. package/dist/payrails.js +165 -44
  31. package/dist/pre-claim.js +51 -29
  32. package/dist/preflight-capabilities.generated.d.ts +0 -106
  33. package/dist/preflight-capabilities.generated.js +0 -153
  34. package/dist/preflight-catalog.json +61 -181
  35. package/dist/preflight-schemas.json +6 -6
  36. package/dist/preflight.generated.d.ts +0 -106
  37. package/dist/preflight.generated.js +27 -15
  38. package/dist/preparation.d.ts +2 -0
  39. package/dist/preparation.js +4 -2
  40. package/dist/prepared-processor.d.ts +19 -5
  41. package/dist/prepared-processor.js +21 -5
  42. package/dist/primer.generated.d.ts +5 -1
  43. package/dist/primer.generated.js +200 -33
  44. package/dist/registry.d.ts +18 -12
  45. package/dist/registry.js +11 -8
  46. package/dist/shiji-sts.d.ts +24 -22
  47. package/dist/shiji-sts.generated.d.ts +1 -1
  48. package/dist/shiji-sts.generated.js +12 -8
  49. package/dist/shiji-sts.js +49 -46
  50. package/dist/stripe-checkout.generated.js +146 -49
  51. package/dist/verifone.generated.js +192 -17
  52. package/dist/verifone.js +1 -1
  53. package/dist/wspay.d.ts +17 -16
  54. package/dist/wspay.generated.d.ts +6 -1
  55. package/dist/wspay.generated.js +13 -3
  56. package/dist/wspay.js +9 -11
  57. package/examples/preflight/kernel-native/README.md +1 -1
  58. package/examples/preflight/kernel-native/inventory.json +1 -19
  59. 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';
@@ -328,15 +329,15 @@ function cseAuthorizedDetail(replay, url, shijiCvv = null) {
328
329
  * getCvvId companion right before the continue (shiji-sts.ts). It is written
329
330
  * only into `approved`, the request the approval was created from, byte for
330
331
  * 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.
332
+ * the cvv is bound to the SHA-256 of the reviewed getCvvId keys the approval
333
+ * served (`shijiCvvKeySequenceSha256`): no keys, no continuation. Every other
334
+ * processor takes cseContinuationBody (cse-body.ts cseBody), and `shijiCvv` and
335
+ * `shijiCvvKeySequenceSha256` are null.
335
336
  */
336
337
  function cseContinuation(opts, url, body, replay, approved, preparation) {
337
338
  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 };
339
+ return shiji ? { body: shiji.body, shijiCvv: shiji.cvv, shijiCvvKeySequenceSha256: shiji.cvvKeySequenceSha256 }
340
+ : { body: cseContinuationBody(opts, body, replay, url, preparation), shijiCvv: null, shijiCvvKeySequenceSha256: null };
340
341
  }
341
342
  /**
342
343
  * The opaque page id a Shiji STS companion is bound to: the CDP session and
@@ -1287,7 +1288,10 @@ function sameCheckoutRequest(first, retry) {
1287
1288
  * answer is a token or ciphertext, and any request for the same purchase can
1288
1289
  * carry it. No for a hosted form (a navigation: there is no request to
1289
1290
  * 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
1291
+ * to one native request by design) other than a Bambora token create, whose
1292
+ * card frame gives up 5 seconds after sending it and whose page asks again
1293
+ * with the same body (preparedApprovalAnswersRetry), from that same card frame
1294
+ * only (sameFrame), a native Stripe Checkout step, a
1291
1295
  * Mercado Pago request whose claim is bound to one document read, a Juspay
1292
1296
  * /txns (the device pays that request's own order; a page that clicks Pay
1293
1297
  * again creates a new order, which the earlier answer would send to the
@@ -1303,12 +1307,23 @@ function sameCheckoutRequest(first, retry) {
1303
1307
  * getTokenId while the cardholder decides. Unknown modes (a duck-typed vault
1304
1308
  * without checkoutModeOf) are taken as `token`.
1305
1309
  */
1306
- function survivesAbandonment(opts, url, method, navigation, bound) {
1310
+ function survivesAbandonment(opts, url, method, navigation, bound, preparation) {
1307
1311
  if (navigation || bound || isShijiStsHost(url))
1308
1312
  return false;
1313
+ if (preparation && !preparedApprovalAnswersRetry(preparation.psp))
1314
+ return false;
1309
1315
  const mode = typeof opts.vault?.checkoutModeOf === 'function' ? opts.vault.checkoutModeOf(url, method) : 'token';
1310
1316
  return mode === 'token' || mode === 'cse';
1311
1317
  }
1318
+ /**
1319
+ * Whether `frameId` on `sessionId` is `owner`, the frame a prepared request came from. A prepared
1320
+ * approval that outlives its request (survivesAbandonment) belongs to that card frame for as long as
1321
+ * it lasts: only that frame's retry is answered from it, and the frame's removal stops the attachment
1322
+ * even after the page gave the request up. A frame with no id is nobody's.
1323
+ */
1324
+ function sameFrame(owner, sessionId, frameId) {
1325
+ return !!owner && typeof owner.frameId === 'string' && owner.frameId !== '' && owner.frameId === frameId && owner.sessionId === sessionId;
1326
+ }
1312
1327
  /**
1313
1328
  * One card request's attempt at an approval, and the browser request that
1314
1329
  * currently carries it. Separate from explicit cancellation: the client can
@@ -2284,9 +2299,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2284
2299
  if (!(method === 'Page.frameDetached' && params.reason === 'swap')) {
2285
2300
  microforms.forget(method === 'Page.frameDetached' ? params.frameId : method === 'Target.detachedFromTarget' ? params.targetId : undefined);
2286
2301
  }
2302
+ // A prepared request's card frame ends its approval even once the page gave the request up and its
2303
+ // live frame id is gone; a target's detach is still read from the session, which a loss keeps.
2287
2304
  if (activeRequest && (method === 'Target.detachedFromTarget' ? activeRequest.sessionId === params.sessionId
2288
2305
  : method === 'Inspector.detached' ? activeRequest.sessionId === sessionId
2289
- : activeRequest.sessionId === sessionId && activeRequest.frameId === params.frameId))
2306
+ : (activeRequest.sessionId === sessionId && activeRequest.frameId === params.frameId)
2307
+ || sameFrame(activeRequest.preparedFrame, sessionId, params.frameId)))
2290
2308
  activeRequest.attempt.stop();
2291
2309
  // The frame or target the bound getTokenId came from is gone, so its getCvvId cannot follow:
2292
2310
  // drop the companion binding and the CVV ciphertext it holds (a binding elsewhere stays).
@@ -2477,8 +2495,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2477
2495
  return;
2478
2496
  }
2479
2497
  let preparation;
2498
+ // The page asking again while a prepared approval outlives its first request (survivesAbandonment):
2499
+ // the preparation is already bound to that approval, so the request is judged as its retry below
2500
+ // and never claimed again.
2501
+ const preparedRetry = awaitingApproval && activeRequest?.attempt.lost === true && preparationGate.isConsumed();
2480
2502
  try {
2481
- preparation = stripeStep ? undefined : preparationGate.claim(request.url, request.method, pausedBody(request), request.headers);
2503
+ preparation = stripeStep || preparedRetry ? undefined : preparationGate.claim(request.url, request.method, pausedBody(request), request.headers);
2482
2504
  }
2483
2505
  catch (error) {
2484
2506
  opts.onEvent?.({ type: 'blocked', detail: failureSummary(error) });
@@ -2499,7 +2521,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2499
2521
  && typeof networkId === 'string' && networkId) {
2500
2522
  const retryBody = pausedBody(request);
2501
2523
  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)) {
2524
+ // A prepared approval is answered only in the card frame it was bound in: the same request from
2525
+ // another frame (a replacement for a removed one, or a second card frame) is refused below.
2526
+ if (retry && activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
2527
+ && (!activeRequest.preparedFrame || sameFrame(activeRequest.preparedFrame, sessionId, frameId))) {
2503
2528
  if (preparation)
2504
2529
  preparationGate.retireUnboundClaim();
2505
2530
  activeRequest.requestId = requestId;
@@ -2535,7 +2560,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2535
2560
  }
2536
2561
  // Same stop condition as the Playwright adapter: once a failure proves
2537
2562
  // retrying is pointless, fail the request without calling the API again.
2538
- if (terminal || lifecycle.isBlocked() || awaitingApproval || Date.now() < quietUntil) {
2563
+ // A request that skipped the spent preparation's claim and was not bound as
2564
+ // its retry (the approval ended while it was judged) is refused as that
2565
+ // claim would refuse it, never asked as a new approval.
2566
+ if (terminal || lifecycle.isBlocked() || awaitingApproval || preparedRetry || Date.now() < quietUntil) {
2539
2567
  let repeatedAuthorizationId = null;
2540
2568
  if (lastSubmitted) {
2541
2569
  const body = pausedBody(request);
@@ -2543,7 +2571,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2543
2571
  repeatedAuthorizationId = lastSubmitted.authorizationId;
2544
2572
  }
2545
2573
  }
2546
- const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding' : 'awaiting approval cooldown');
2574
+ const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
2575
+ : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
2547
2576
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
2548
2577
  if (preparation)
2549
2578
  preparationGate.retireUnboundClaim();
@@ -2571,8 +2600,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2571
2600
  awaitingApproval = true;
2572
2601
  // A card endpoint approval never outlives its request: the device answers that
2573
2602
  // 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)));
2603
+ const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url, request.method, resourceType === 'Document', !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url, request.method)
2604
+ || juspayCheckout.bindsApproval(request.url, request.method), preparation));
2576
2605
  let handoffStarted = false;
2577
2606
  // The approval the device gave, and whether it reached a browser request.
2578
2607
  let approvedId = null;
@@ -2614,7 +2643,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2614
2643
  }
2615
2644
  attempt.bind({ requestId, sessionId, request });
2616
2645
  activeRequest = { attempt, requestId, networkId, sessionId, frameId, request,
2617
- identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape) };
2646
+ identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape),
2647
+ preparedFrame: preparation ? { sessionId, frameId } : undefined };
2618
2648
  // An ACI card-number request: the page's CVC and expiry requests for its checkout are held
2619
2649
  // until this approval ends (see AciGate). One whose checkout already has a card-number
2620
2650
  // request running or answered never begins; the pre-claim gate refuses it before this.
@@ -2787,7 +2817,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2787
2817
  const continuation = cseContinuation(opts, deliverRequest.url, deliverBody, replay, { url: request.url, body }, preparation);
2788
2818
  const postData = Buffer.from(continuation.body).toString('base64');
2789
2819
  if (continuation.shijiCvv !== null) {
2790
- shijiCompanions.bind({ url: request.url, method: request.method, body }, cdpShijiPage(sessionId, frameId), continuation.shijiCvv, continuation.shijiCvvKeySequences ?? []);
2820
+ shijiCompanions.bind({ url: request.url, method: request.method, body }, cdpShijiPage(sessionId, frameId), continuation.shijiCvv, continuation.shijiCvvKeySequenceSha256 ?? []);
2791
2821
  }
2792
2822
  handoffStarted = true;
2793
2823
  if (replay.kind === 'verifone') {
@@ -3276,7 +3306,8 @@ export async function attachToPlaywright(page, opts) {
3276
3306
  }
3277
3307
  });
3278
3308
  page.on?.('framedetached', (frame) => {
3279
- if (activeRequest?.frames.includes(frame))
3309
+ // As in attachToCdp: a prepared request's card frame ends its approval even after the page gave the request up.
3310
+ if (activeRequest && (activeRequest.frames.includes(frame) || activeRequest.preparedFrames?.includes(frame)))
3280
3311
  activeRequest.attempt.stop();
3281
3312
  const gone = frame && typeof frame === 'object' ? playwrightShijiFrames.get(frame) : undefined;
3282
3313
  if (gone)
@@ -3304,7 +3335,10 @@ export async function attachToPlaywright(page, opts) {
3304
3335
  // so the ACI gate routes them (never ACI's widget script, /bins or /v1/collect).
3305
3336
  // The matcher sees only the URL. Worldpay eProtect's registration is a GET, so its family and
3306
3337
  // 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())
3338
+ // Gravy's session fields are a PUT, so a URL a PUT recognizer claims is routed too; the
3339
+ // pre-claim gates then pause only the PUT and continue the URL's other methods.
3340
+ (url) => opts.vault.isCardRequest(url.toString()) || opts.vault.isCardRequest(url.toString(), 'PUT')
3341
+ || guards.matches(url.toString()) || stripeCheckout.matches(url.toString()) || mercadoCheckout.matches(url.toString())
3308
3342
  || eprotect.matches(url.toString())
3309
3343
  // An armed merchant profile's endpoint or a sibling of it (merchant-hosted.ts), as attachToCdp arms it.
3310
3344
  || (typeof opts.vault.merchantProfileOf === 'function' && opts.vault.merchantProfileOf(url.toString()) !== null)
@@ -3458,8 +3492,10 @@ export async function attachToPlaywright(page, opts) {
3458
3492
  if (withoutCard === 'continue')
3459
3493
  return route.fallback();
3460
3494
  let preparation;
3495
+ // As in attachToCdp: the retry of a prepared request whose approval outlived it is never claimed again.
3496
+ const preparedRetry = awaitingApproval && activeRequest?.attempt.lost === true && preparationGate.isConsumed();
3461
3497
  try {
3462
- preparation = stripeStep ? undefined : preparationGate.claim(request.url(), request.method(), request.postData() ?? '', request.headers());
3498
+ preparation = stripeStep || preparedRetry ? undefined : preparationGate.claim(request.url(), request.method(), request.postData() ?? '', request.headers());
3463
3499
  }
3464
3500
  catch (error) {
3465
3501
  opts.onEvent?.({ type: 'blocked', detail: failureSummary(error) });
@@ -3473,15 +3509,17 @@ export async function attachToPlaywright(page, opts) {
3473
3509
  // merchantAttempt and the same check in attachToCdp).
3474
3510
  if (awaitingApproval && activeRequest?.attempt.lost && !terminal && !setupStop.signal.aborted && !lifecycle.isCancelled()) {
3475
3511
  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)) {
3512
+ const retryFrames = [];
3513
+ try {
3514
+ for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
3515
+ retryFrames.push(frame);
3516
+ }
3517
+ catch { /* covered by requestfailed */ }
3518
+ // As in attachToCdp: a prepared approval is answered only in the card frame it was bound in.
3519
+ if (activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
3520
+ && (!activeRequest.preparedFrames || (retryFrames.length > 0 && retryFrames[0] === activeRequest.preparedFrames[0]))) {
3477
3521
  if (preparation)
3478
3522
  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
3523
  activeRequest.request = request;
3486
3524
  activeRequest.frames = retryFrames;
3487
3525
  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 +3545,8 @@ export async function attachToPlaywright(page, opts) {
3507
3545
  // CDP adapter's Fetch.failRequest uses, so a refused request resolves
3508
3546
  // identically whichever adapter is attached. An Amadeus card request is
3509
3547
  // 'failed' (ERR_FAILED) in both too, see failsAsNetworkError (refusalFailure).
3510
- if (terminal || lifecycle.isBlocked() || awaitingApproval || Date.now() < quietUntil) {
3548
+ // As in attachToCdp: an unbound request that skipped the spent preparation's claim is refused.
3549
+ if (terminal || lifecycle.isBlocked() || awaitingApproval || preparedRetry || Date.now() < quietUntil) {
3511
3550
  let repeatedAuthorizationId = null;
3512
3551
  if (lastSubmitted) {
3513
3552
  let body = null;
@@ -3519,7 +3558,8 @@ export async function attachToPlaywright(page, opts) {
3519
3558
  repeatedAuthorizationId = lastSubmitted.authorizationId;
3520
3559
  }
3521
3560
  }
3522
- const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding' : 'awaiting approval cooldown');
3561
+ const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
3562
+ : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
3523
3563
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
3524
3564
  if (preparation)
3525
3565
  preparationGate.retireUnboundClaim();
@@ -3546,8 +3586,8 @@ export async function attachToPlaywright(page, opts) {
3546
3586
  navigation = false;
3547
3587
  }
3548
3588
  // 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())));
3589
+ const attempt = merchantAttempt(lifecycle, survivesAbandonment(opts, request.url(), request.method(), navigation, !!stripeStep || !!cardEndpoint || mercadoCheckout.requiresDocument(request.url(), request.method())
3590
+ || juspayCheckout.bindsApproval(request.url(), request.method()), preparation));
3551
3591
  const frames = [];
3552
3592
  try {
3553
3593
  for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
@@ -3555,9 +3595,13 @@ export async function attachToPlaywright(page, opts) {
3555
3595
  }
3556
3596
  catch { /* requestfailed/page close still cover unavailable frame metadata */ }
3557
3597
  // Judged on the request that currently carries the attempt: after a
3558
- // retry binds, the first request's failure is history, not a loss.
3598
+ // retry binds, the first request's failure is history, not a loss. A
3599
+ // prepared request's own frames are judged for as long as the approval
3600
+ // lasts, as framedetached judges them: their removal ends it.
3559
3601
  const assertRequestLive = () => {
3560
3602
  const current = attempt.target;
3603
+ if (preparation && frames.some((frame) => frame.isDetached?.()))
3604
+ attempt.stop();
3561
3605
  if (current && (current.request.failure?.() || current.frames.some((frame) => frame.isDetached?.())))
3562
3606
  attempt.lose();
3563
3607
  if (page.isClosed?.())
@@ -3587,7 +3631,8 @@ export async function attachToPlaywright(page, opts) {
3587
3631
  const payToInterceptMs = readPayToInterceptMs(opts.payClickedAt, approvalStartedAt);
3588
3632
  attempt.bind({ route, request, frames });
3589
3633
  activeRequest = { request, frames, attempt,
3590
- identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape) };
3634
+ identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape),
3635
+ preparedFrames: preparation ? [...frames] : undefined };
3591
3636
  // An ACI card-number request: see the same step in attachToCdp.
3592
3637
  if (!aci.begin(request.url(), request.method(), body, preparation?.merchantOrigin)) {
3593
3638
  throw new CheckoutPreparationError(preparation?.id ?? null, ACI_CHECKOUT_REUSED);
@@ -3728,7 +3773,7 @@ export async function attachToPlaywright(page, opts) {
3728
3773
  if (boundPage === null)
3729
3774
  throw new ShijiStsError('unsupported');
3730
3775
  assertRequestLive();
3731
- shijiCompanions.bind({ url: request.url(), method: request.method(), body }, boundPage, continuation.shijiCvv, continuation.shijiCvvKeySequences ?? []);
3776
+ shijiCompanions.bind({ url: request.url(), method: request.method(), body }, boundPage, continuation.shijiCvv, continuation.shijiCvvKeySequenceSha256 ?? []);
3732
3777
  }
3733
3778
  handoffStarted = true;
3734
3779
  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
@@ -1198,8 +1226,9 @@ export declare class VaultClient {
1198
1226
  private mergedRegistry;
1199
1227
  /**
1200
1228
  * 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.
1229
+ * `recognizer`. A POST to a recognised endpoint, a PUT for a recognizer whose card request is
1230
+ * a PUT (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect) that
1231
+ * exact GET, query included.
1203
1232
  */
1204
1233
  recognizerOf(url: string, method?: string): Recognizer | null;
1205
1234
  /** Refresh recognizers from the API so new PSPs work without a redeploy. */
@@ -1244,8 +1273,9 @@ export declare class VaultClient {
1244
1273
  cardOnFilePolicy(url: string, method?: string): string | null;
1245
1274
  /**
1246
1275
  * 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.
1276
+ * a recognized endpoint, a PUT for a recognizer whose card request is a PUT
1277
+ * (Gravy), or for a recognizer whose card request is a GET (Worldpay eProtect)
1278
+ * that exact GET, query included.
1249
1279
  */
1250
1280
  isCardRequest(url: string, method?: string): boolean;
1251
1281
  /**