@agent-cards/sdk 0.23.20 → 0.23.22

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 (64) hide show
  1. package/CHANGELOG.md +55 -2
  2. package/PREFLIGHT.md +6 -0
  3. package/README.md +169 -5
  4. package/dist/amadeus-checkout.generated.d.ts +6 -1
  5. package/dist/amadeus-checkout.generated.js +26 -2
  6. package/dist/card-fields.generated.js +2341 -127
  7. package/dist/cdp.js +199 -28
  8. package/dist/client.d.ts +177 -9
  9. package/dist/client.js +387 -37
  10. package/dist/comgate.d.ts +50 -0
  11. package/dist/comgate.js +80 -0
  12. package/dist/creditguard.generated.js +4 -2
  13. package/dist/cse-body.js +11 -0
  14. package/dist/cybersource-secure-acceptance.generated.d.ts +1 -1
  15. package/dist/ebanx.d.ts +1 -1
  16. package/dist/ebanx.js +1 -1
  17. package/dist/eprotect.generated.d.ts +1 -1
  18. package/dist/eximbay.d.ts +46 -0
  19. package/dist/eximbay.js +69 -0
  20. package/dist/gmo.generated.js +7 -3
  21. package/dist/hipay.generated.d.ts +5 -5
  22. package/dist/hipay.generated.js +5 -2
  23. package/dist/hold.d.ts +1 -1
  24. package/dist/hold.js +10 -4
  25. package/dist/hosted-entry.d.ts +4 -0
  26. package/dist/hosted-entry.js +32 -0
  27. package/dist/index.d.ts +1 -0
  28. package/dist/lifecycle.d.ts +9 -4
  29. package/dist/lifecycle.js +16 -4
  30. package/dist/paddle-tokenex.generated.js +7 -3
  31. package/dist/paygent.generated.d.ts +60 -0
  32. package/dist/paygent.generated.js +425 -0
  33. package/dist/payrails.generated.js +7 -3
  34. package/dist/payrexx.d.ts +91 -0
  35. package/dist/payrexx.generated.d.ts +91 -0
  36. package/dist/payrexx.generated.js +536 -0
  37. package/dist/payrexx.js +141 -0
  38. package/dist/pre-claim.d.ts +30 -1
  39. package/dist/pre-claim.js +257 -39
  40. package/dist/preflight-catalog.json +58 -0
  41. package/dist/preflight-schemas.json +4 -0
  42. package/dist/preflight.generated.js +26 -2
  43. package/dist/prepared-processor.d.ts +7 -2
  44. package/dist/prepared-processor.js +16 -2
  45. package/dist/primer.generated.js +2469 -255
  46. package/dist/przelewy24.d.ts +47 -0
  47. package/dist/przelewy24.js +88 -0
  48. package/dist/registry.d.ts +6 -5
  49. package/dist/registry.js +27 -7
  50. package/dist/shiji-sts.generated.js +7 -3
  51. package/dist/stripe-checkout.generated.js +896 -19
  52. package/dist/toast-seal.generated.d.ts +37 -0
  53. package/dist/toast-seal.generated.js +625 -0
  54. package/dist/toast.generated.d.ts +41 -0
  55. package/dist/toast.generated.js +113 -0
  56. package/dist/tokenex-iframe.d.ts +93 -0
  57. package/dist/tokenex-iframe.generated.d.ts +81 -0
  58. package/dist/tokenex-iframe.generated.js +403 -0
  59. package/dist/tokenex-iframe.js +195 -0
  60. package/dist/transbank.d.ts +45 -0
  61. package/dist/transbank.js +73 -0
  62. package/dist/twoc2p.generated.js +7 -3
  63. package/examples/preflight/kernel-native/inventory.json +1 -1
  64. package/package.json +3 -3
package/dist/cdp.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BUILTIN_REGISTRY, cardUrlPatterns } from './registry.js';
2
- import { AdyenTestPlatformRefusedError, AmountRequiredError, ApprovalDeclinedError, ApprovalTimeoutError, CardEncryptedError, CheckoutApiError, CheckoutCancelledError, CheckoutPreparationError, MerchantTotalError, PaymentOutcomeUnknownError, PreparationRequiredError, ProcessorRefusedError, UnsupportedCapabilityError, UnsupportedModeError, } from './client.js';
2
+ import { AdyenTestPlatformRefusedError, AmountRequiredError, ApprovalDeclinedError, ApprovalTimeoutError, CardEncryptedError, CheckoutApiError, CheckoutCancelledError, CheckoutPreparationError, MerchantTotalError, PaymentOutcomeUnknownError, PreparationRequiredError, ProcessorRefusedError, UnsupportedCapabilityError, UnsupportedModeError, redactUrl, } from './client.js';
3
3
  import { PreparationGate } from './preparation.js';
4
4
  import { preparedApprovalAnswersRetry } from './prepared-processor.js';
5
5
  import { StripeCheckoutGate } from './stripe-checkout.js';
@@ -11,16 +11,23 @@ import { EprotectGate, classifyEprotectRequest, eprotectScriptAnswer } from './e
11
11
  import { dispatchPreClaim, eventUrl, isArmedCreditguardCall, routesHipayVault } from './pre-claim.js';
12
12
  import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
13
13
  import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
14
+ import { isToastPaymentMethodsUrl, readToastBody } from './toast.generated.js';
14
15
  import { PADDLE_VGS_PAY_PATTERNS, PaddleVgsPayGate, isPaddleCheckoutService, recognizesPaddleVgs } from './paddle-vgs-pay.js';
15
16
  import { DatatransRepeats } from './datatrans.js';
17
+ import { TokenexIframeRepeats, isArmedTokenexIframeUrl } from './tokenex-iframe.js';
16
18
  import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
17
19
  import { paddleTokenexTokenContinuation } from './paddle-tokenex.js';
18
20
  import { twoC2pContinuation, twoC2pRetire } from './twoc2p.js';
19
21
  import { isArmedEkashuUrl } from './ekashu.js';
22
+ import { isArmedTransbankUrl } from './transbank.js';
20
23
  import { isArmedRedsysUrl } from './redsys.js';
24
+ import { isArmedPrzelewy24Url } from './przelewy24.js';
21
25
  import { isArmedQuickpayUrl } from './quickpay.js';
26
+ import { COMGATE_CARD_PATTERNS, isArmedComgateUrl, servesComgate } from './comgate.js';
27
+ import { isArmedEximbayUrl } from './eximbay.js';
22
28
  import { PAYPAL_HOSTED_FIELDS_PATTERNS, isPaypalHostedFieldsConfirm } from './paypal-hosted-fields.js';
23
29
  import { EBANX_RESEND_PATTERNS, EbanxFollowUps, isEbanxResendUrl } from './ebanx.js';
30
+ import { PayrexxFollowUps } from './payrexx.js';
24
31
  import { PAYPAL_CARD_FORM_PATTERNS, isPaypalCardFormUrl } from './paypal-card-forms.js';
25
32
  import { CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, SecureAcceptanceFollowUpWatch, isCybersourceSecureAcceptanceUrl, secureAcceptanceFollowUp, secureAcceptanceFrameAnswer, secureAcceptanceReplyLast4, } from './cybersource-secure-acceptance.js';
26
33
  import { CheckoutAttachmentError, attachmentDeadline, attachmentFailure, withinAttachmentDeadline } from './attachment.js';
@@ -1284,9 +1291,23 @@ function requestIdentity(opts, url, method, body, documentUrl, pageAmount, flexS
1284
1291
  // out, so the shop joins the identity: a retry that names another shop needs its own approval.
1285
1292
  const shop = gmoRequestShop(url, body);
1286
1293
  const identity = shop === undefined ? shaped.shape : `${shaped.shape};gmo_shop:${JSON.stringify(shop)}`;
1287
- return { url, method, documentUrl, pageAmount, body: `${identity}${paddleVgsRetryKey(url, body, headers)}`,
1294
+ return { url, method, documentUrl, pageAmount, body: `${identity}${paddleVgsRetryKey(url, body, headers)}${toastRetryKey(url, body)}`,
1288
1295
  amountEvidence: opts.amount != null || pageAmount !== undefined || shaped.namesAmount };
1289
1296
  }
1297
+ /**
1298
+ * What a Toast card request's retry must repeat, beyond its shape: the sealed card opens at any Toast
1299
+ * restaurant whose payment intent names it, and the frame seals the ZIP inside it, so the payment session
1300
+ * and the ZIP join the identity. A request under another session or with another ZIP never receives the
1301
+ * card the cardholder approved for the first, while the frame's retry, its placeholder sealed afresh, still
1302
+ * does. A body Toast's reader refuses joins whole, so it matches nothing but itself. Empty for every other
1303
+ * request.
1304
+ */
1305
+ function toastRetryKey(url, body) {
1306
+ if (!isToastPaymentMethodsUrl(url))
1307
+ return '';
1308
+ const read = readToastBody(body);
1309
+ return `;toast:${JSON.stringify(read.ok ? [read.sessionSecret, read.postalCode] : body)}`;
1310
+ }
1290
1311
  /**
1291
1312
  * What a Paddle VGS card request's retry must repeat byte for byte, beyond its shape: Paddle's frame
1292
1313
  * sends its second request with the same body under the same bearer, and the bearer names the
@@ -1750,6 +1771,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1750
1771
  let setupFailureReported = false;
1751
1772
  const ownedSessions = new Set([pageSessionId]);
1752
1773
  const sessionTypes = new Map([[pageSessionId, 'page']]);
1774
+ // Each window this attachment adopted (adoptOpenedWindow): its session, and its target's id, which is its main frame's.
1775
+ const windowTargets = new Map();
1753
1776
  const sessionParents = new Map();
1754
1777
  // The frame in its parent session that a child target hangs from (TargetInfo.parentFrameId), so a
1755
1778
  // frame lookup can climb from a cross-origin frame into the document that frames it.
@@ -1861,6 +1884,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1861
1884
  const juspayCheckout = new JuspayCheckoutGate(opts.vault);
1862
1885
  // The approved EBANX token request on this attachment and the sends claimed under it (ebanx.ts).
1863
1886
  const ebanxFollowUps = new EbanxFollowUps();
1887
+ // The approved Payrexx card request on this attachment and the 3-D Secure sends claimed under it (payrexx.ts).
1888
+ const payrexxFollowUps = new PayrexxFollowUps();
1864
1889
  const aci = new AciGate(opts.vault);
1865
1890
  const eprotect = new EprotectGate(opts.vault);
1866
1891
  // The merchant's follow-up request after a delivered Secure Acceptance reply (cybersource-secure-acceptance.ts).
@@ -1874,6 +1899,20 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1874
1899
  preparationFrameId = tree.frameTree.frame.id;
1875
1900
  return tree.frameTree.frame.url;
1876
1901
  };
1902
+ // The URL the page's main frame last committed: its target's URL when the attachment is set up, then kept from
1903
+ // Page.frameNavigated and Page.navigatedWithinDocument, and from the page target's own reports (notePageTarget),
1904
+ // which name a document the page commits before this attachment has enabled its Page events. Chromium leaves
1905
+ // Page.getFrameTree unanswered on the page's session while that session's own top-level navigation is paused (a
1906
+ // hosted page entry the page sends this window to, by script or through the merchant's redirect), so
1907
+ // readDocumentUrl would wait out its bound and the create would name no checkout page. For that one paused request
1908
+ // the page is still the document it last committed.
1909
+ let committedTopUrl;
1910
+ /** A TargetInfo of the page's own target names the URL the page last committed: the target list, and each report. */
1911
+ const notePageTarget = (info) => {
1912
+ if (pageTargetId !== undefined && info?.targetId === pageTargetId && info?.type === 'page' && typeof info.url === 'string' && info.url) {
1913
+ committedTopUrl = info.url.split('#')[0];
1914
+ }
1915
+ };
1877
1916
  // prepare({ tokenizeBeforePay: true }) sends the card Microform whose field documents are committed
1878
1917
  // now. A field's commit can come before this attachment enabled its session, so no event reports
1879
1918
  // it; each iframe session's own frame tree, and the page's, is read once first (as rereadMicroforms
@@ -1908,7 +1947,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1908
1947
  const frame = find(tree?.frameTree, 0);
1909
1948
  if (!frame)
1910
1949
  return null;
1911
- return { navigation: frame.parentId || sessionId !== pageSessionId ? 'frame' : 'top', url: typeof frame.url === 'string' ? frame.url : undefined };
1950
+ return { navigation: frame.parentId || (sessionId !== pageSessionId && !(sessionId && windowTargets.has(sessionId))) ? 'frame' : 'top',
1951
+ url: typeof frame.url === 'string' ? frame.url : undefined };
1912
1952
  };
1913
1953
  /**
1914
1954
  * Which frame a paused document request navigates, as documentFrame reads it, without the frame tree where
@@ -1924,6 +1964,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1924
1964
  return 'frame';
1925
1965
  if (sessionId === pageSessionId && pageTargetId !== undefined && frameId === pageTargetId)
1926
1966
  return 'top';
1967
+ // An adopted window's main frame is that window's own top-level document, and its frame tree is as unanswered.
1968
+ if (sessionId && windowTargets.get(sessionId) === frameId)
1969
+ return 'top';
1927
1970
  return (await documentFrame(sessionId, frameId))?.navigation ?? null;
1928
1971
  };
1929
1972
  /**
@@ -2117,6 +2160,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2117
2160
  let lastSubmitted = null;
2118
2161
  // The approved Datatrans answers, per frameId: the repeat rule (datatrans.ts).
2119
2162
  const datatransRepeats = new DatatransRepeats();
2163
+ // The approved TokenEx iframe answers, per frameId (the card-number frame is an out-of-process iframe whose own
2164
+ // session pauses its post; its frameId names it): the repeat rule (tokenex-iframe.ts).
2165
+ const tokenexIframeRepeats = new TokenexIframeRepeats();
2120
2166
  // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
2121
2167
  const worldlineProducts = new WorldlineMyCheckoutProducts();
2122
2168
  // The Shiji STS getCvvId companion of the getTokenId this attachment last continued (shiji-sts.ts).
@@ -2161,11 +2207,14 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2161
2207
  // EBANX's 3-D Secure calls and hosted confirm, which send the typed card again
2162
2208
  // after its token: the cardholder's device sends one the approved token request
2163
2209
  // covers, and any other is refused (see ebanx.ts).
2210
+ // ComGate's card hosts are armed only for an org the API served ComGate to, so the
2211
+ // gate can fail every card post there while the gateway link pauses (see comgate.ts).
2164
2212
  const urlPatterns = [...new Set([...(derived.length > 0 ? derived : FALLBACK_CARD_PATTERNS), ...guards.patterns,
2165
2213
  ...(stripeCheckout.isEnabled() ? ['https://api.stripe.com/*'] : []), MERCADO_CHECKOUT_PATTERN, ...merchantPatterns,
2166
2214
  ...(recognizesGmo(opts.vault) ? GMO_TOKEN_V1_PATTERNS : []), ...juspayCheckout.urlPatterns(), ...cardEndpointPatterns(),
2167
2215
  ...(payrails ? PAYRAILS_GUARD_PATTERNS : []), ...PAYPAL_HOSTED_FIELDS_PATTERNS, ...PAYPAL_CARD_FORM_PATTERNS,
2168
- ...CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS, ...(recognizesPaddleVgs(opts.vault) ? PADDLE_VGS_PAY_PATTERNS : []), ...EBANX_RESEND_PATTERNS])];
2216
+ ...CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS, ...(recognizesPaddleVgs(opts.vault) ? PADDLE_VGS_PAY_PATTERNS : []), ...EBANX_RESEND_PATTERNS,
2217
+ ...(servesComgate(opts.vault) ? COMGATE_CARD_PATTERNS : [])])];
2169
2218
  // The hold-only rules' patterns (Bolt's tokenizer; every request a placeholder card can ride, by resource type).
2170
2219
  const fetchPatterns = [...urlPatterns.map((urlPattern) => ({ urlPattern, requestStage: 'Request' })),
2171
2220
  ...(holdGate ? holdGate.patterns().map((p) => ({ ...p, requestStage: 'Request' })) : [])];
@@ -2294,6 +2343,55 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2294
2343
  lifecycle.failed(new PaymentOutcomeUnknownError(lifecycle.getState().authorizationId, 'browser_interception_unavailable'));
2295
2344
  opts.onEvent?.({ type: 'failed', detail: 'browser_interception_unavailable' });
2296
2345
  };
2346
+ /**
2347
+ * A window the merchant's page opened (window.open, or a form or link aimed at a new window), or one such a window
2348
+ * opened in turn, in the merchant's own browser context: it belongs to this checkout, so it is attached and armed
2349
+ * as the page is, and its requests reach the same gates. Chromium reports the window (Target.targetCreated, with its
2350
+ * openerId) as it starts, and the page session's auto-attach never covers a new page target. The attach races the
2351
+ * window's first request: a window that opens on the merchant's page and is sent on to a hosted page by script or
2352
+ * by the merchant's redirect is armed before that entry; a window opened straight onto a hosted page can load it
2353
+ * first, as every window did before. A window reported while this attachment still arms the page waits in
2354
+ * pendingWindows and is adopted once the page is armed, inside the setup deadline and before the attachment reports
2355
+ * ready, its opener judged then, so a window that window opened is adopted after it. A window that is gone before it
2356
+ * could be attached was never armed, as before; one attached but not armed fails the attachment, as a frame does.
2357
+ */
2358
+ const openedWindows = new Set();
2359
+ const pendingWindows = [];
2360
+ const adoptOpenedWindow = async (info, duringSetup = false) => {
2361
+ if (!info || info.type !== 'page' || typeof info.targetId !== 'string' || !info.targetId || info.targetId === pageTargetId
2362
+ || openedWindows.has(info.targetId) || info.browserContextId !== merchantContext)
2363
+ return;
2364
+ if (setupStop.signal.aborted)
2365
+ return;
2366
+ if (!attachmentReady && !duringSetup) {
2367
+ pendingWindows.push(info);
2368
+ return;
2369
+ }
2370
+ if (typeof info.openerId !== 'string' || !(info.openerId === pageTargetId || openedWindows.has(info.openerId)))
2371
+ return;
2372
+ openedWindows.add(info.targetId);
2373
+ let child;
2374
+ try {
2375
+ child = (await cdp.send('Target.attachToTarget', { targetId: info.targetId, flatten: true }))?.sessionId;
2376
+ }
2377
+ catch {
2378
+ return;
2379
+ }
2380
+ try {
2381
+ if (typeof child !== 'string' || !child || ownedSessions.has(child) || detachedSessions.has(child) || rejectedSessions.has(child)) {
2382
+ throw new CheckoutAttachmentError('unavailable');
2383
+ }
2384
+ ownedSessions.add(child);
2385
+ sessionTypes.set(child, 'page');
2386
+ sessionParents.set(child, pageSessionId);
2387
+ windowTargets.set(child, info.targetId);
2388
+ await arm(child);
2389
+ opts.onEvent?.({ type: 'window_armed', detail: {} });
2390
+ }
2391
+ catch (error) {
2392
+ failInterception(error);
2393
+ }
2394
+ };
2297
2395
  cdp.on(async (method, params, sessionId) => {
2298
2396
  // A frame that committed a new document drops the companion binding made for the getTokenId its
2299
2397
  // old document sent, with the device's CVV ciphertext: the document that replaced it never
@@ -2304,6 +2402,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2304
2402
  shijiCompanions.releasePage((page) => page === replaced);
2305
2403
  }
2306
2404
  if ((method === 'Target.targetCreated' || method === 'Target.targetInfoChanged') && targetContextKnown) {
2405
+ // The page's own target reports each document it commits from discovery on, Page events or not.
2406
+ notePageTarget(params?.targetInfo);
2307
2407
  try {
2308
2408
  assertContextTarget(params?.targetInfo, merchantContext, targetAllowance);
2309
2409
  if (params?.targetInfo?.type === 'shared_worker')
@@ -2312,8 +2412,18 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2312
2412
  catch (error) {
2313
2413
  failInterception(error);
2314
2414
  }
2415
+ if (method === 'Target.targetCreated')
2416
+ await adoptOpenedWindow(params?.targetInfo);
2315
2417
  return;
2316
2418
  }
2419
+ // The page's main frame committed a document, or moved within it: the URL a held top-level navigation leaves.
2420
+ if (sessionId === pageSessionId && method === 'Page.frameNavigated' && !params.frame?.parentId && typeof params.frame?.url === 'string') {
2421
+ committedTopUrl = params.frame.url;
2422
+ }
2423
+ if (sessionId === pageSessionId && method === 'Page.navigatedWithinDocument' && pageTargetId !== undefined
2424
+ && params?.frameId === pageTargetId && typeof params?.url === 'string') {
2425
+ committedTopUrl = params.url.split('#')[0];
2426
+ }
2317
2427
  if (((method === 'Page.frameNavigated' && !params.frame?.parentId) || (method === 'Page.navigatedWithinDocument' && preparationFrameId && params.frameId === preparationFrameId)) && sessionId === pageSessionId) {
2318
2428
  preparationGate.invalidate('merchant_document_changed');
2319
2429
  mercadoCheckout.invalidate();
@@ -2353,10 +2463,11 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2353
2463
  }
2354
2464
  // A watched shared worker sent a card request: it went out unpaused, so nothing is approved from here on.
2355
2465
  if (method === 'Network.requestWillBeSent' && sessionId && watchedWorkerSessions.has(sessionId) && sharedWorkerCardRequest(params?.request)) {
2466
+ // Origin and path as redactUrl reads them, so a credential a processor writes into the path never shows.
2356
2467
  let endpoint = '';
2357
2468
  try {
2358
- const u = new URL(params.request.url);
2359
- endpoint = `${u.origin}${u.pathname}`;
2469
+ new URL(params.request.url);
2470
+ endpoint = redactUrl(params.request.url);
2360
2471
  }
2361
2472
  catch { /* unreadable */ }
2362
2473
  opts.onEvent?.({ type: 'card_request_unwatched', detail: { endpoint, from: 'shared_worker' } });
@@ -2641,6 +2752,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2641
2752
  const { requestId, request, resourceType, networkId, frameId } = params;
2642
2753
  // When this request paused: the merchant's total is read as of this moment.
2643
2754
  const pausedAt = Date.now();
2755
+ // The page's own top-level navigation, paused: its frame tree cannot be read until this request is decided, so its
2756
+ // document is the one it last committed (committedTopUrl). Every other request reads the frame tree as before.
2757
+ const readPausedDocument = resourceType === 'Document' && sessionId === pageSessionId && pageTargetId !== undefined
2758
+ && frameId === pageTargetId && committedTopUrl !== undefined ? async () => committedTopUrl : readDocumentUrl;
2644
2759
  // A paused document request has not committed either: it only starts the context's check.
2645
2760
  if (resourceType === 'Document')
2646
2761
  microforms.warm(withFragment(request));
@@ -2723,7 +2838,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2723
2838
  cybersourceFlex: { judge: (flexRequest) => microforms.judge(flexRequest, frameId, () => rereadMicroforms(sessionId, frameId)) },
2724
2839
  juspay: juspayCheckout,
2725
2840
  datatrans: datatransRepeats,
2841
+ tokenexIframe: tokenexIframeRepeats,
2726
2842
  ebanx: ebanxFollowUps,
2843
+ payrexx: payrexxFollowUps,
2727
2844
  frame: typeof frameId === 'string' && frameId ? frameId : undefined,
2728
2845
  payrails,
2729
2846
  shijiSts: { companions: shijiCompanions, page: () => cdpShijiPage(sessionId, frameId) },
@@ -2794,7 +2911,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2794
2911
  if (awaitingApproval && activeRequest?.attempt.lost && !terminal && !setupStop.signal.aborted && !lifecycle.isCancelled()
2795
2912
  && typeof networkId === 'string' && networkId) {
2796
2913
  const retryBody = pausedBody(request);
2797
- const retry = retryBody === null ? null : requestIdentity(opts, request.url, request.method, retryBody, documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape, request.headers);
2914
+ const retry = retryBody === null ? null : requestIdentity(opts, request.url, request.method, retryBody, documentKey(await documentUrlOf(readPausedDocument, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape, request.headers);
2798
2915
  // A prepared approval is answered only in the card frame it was bound in: the same request from
2799
2916
  // another frame (a replacement for a removed one, or a second card frame) is refused below.
2800
2917
  if (retry && activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
@@ -2837,8 +2954,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2837
2954
  // A request that skipped the spent preparation's claim and was not bound as
2838
2955
  // its retry (the approval ended while it was judged) is refused as that
2839
2956
  // claim would refuse it, never asked as a new approval.
2840
- // An EBANX send still out (a confirm that may pay) holds the attachment too: a new card request the page sends
2841
- // meanwhile, a second tap on the hosted page's Pay among them, would start another payment over it.
2957
+ // An EBANX or Payrexx send still out (a confirm that may pay, a 3-D Secure step whose answer names the token the
2958
+ // page charges) holds the attachment too: a new card request the page sends meanwhile, a second tap on the hosted
2959
+ // page's Pay among them, would start another payment over it.
2842
2960
  const followUpOut = lifecycle.followUpsInFlight();
2843
2961
  if (terminal || lifecycle.isBlocked() || awaitingApproval || followUpOut || preparedRetry || Date.now() < quietUntil) {
2844
2962
  let repeatedAuthorizationId = null;
@@ -2849,7 +2967,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2849
2967
  }
2850
2968
  }
2851
2969
  const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
2852
- : followUpOut ? 'an EBANX send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
2970
+ : followUpOut ? 'a follow-up send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
2853
2971
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
2854
2972
  if (preparation)
2855
2973
  preparationGate.retireUnboundClaim();
@@ -2890,8 +3008,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2890
3008
  // The approval the device gave, and whether it reached a browser request.
2891
3009
  let approvedId = null;
2892
3010
  let deliveryBegun = false;
2893
- // Whether this handoff kept an EBANX approval's follow-ups (ebanxFollowUps.remember), which a failed handoff stops.
2894
- let ebanxRemembered = false;
3011
+ // Whether this handoff kept an approval's follow-ups (ebanxFollowUps.remember, payrexxFollowUps.remember), which a
3012
+ // failed handoff stops.
3013
+ let followUpsRemembered = false;
2895
3014
  // The business's pause, so resume resolves once the page has its answer.
2896
3015
  let businessPause = null;
2897
3016
  // Hold mode: the person's release, settled with what came of it.
@@ -2948,7 +3067,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2948
3067
  await preparationGate.assertDocument();
2949
3068
  // One bounded read of the document serves both the merchant origin and
2950
3069
  // the identity a retry has to repeat (see sameCheckoutRequest).
2951
- const documentUrl = await documentUrlOf(readDocumentUrl, attempt.signal);
3070
+ const documentUrl = await documentUrlOf(readPausedDocument, attempt.signal);
2952
3071
  const pageOrigin = pageOriginFrom(documentUrl);
2953
3072
  // The agent's amount is a card endpoint's only authority: no page total is read.
2954
3073
  const pageAmount = cardEndpoint ? undefined : await pageAmountOf(opts);
@@ -3225,12 +3344,14 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3225
3344
  throw new PaymentOutcomeUnknownError(replay.authorizationId, 'paddle_vgs_card_facts_unavailable');
3226
3345
  }
3227
3346
  // An approved EBANX token request whose read names a window covers the 3-D Secure calls and the confirm the
3228
- // page sends next (ebanx.ts): kept as the API stored it, the request that raised the approval. Kept before the
3229
- // page has the answer, since the page may send its first 3-D Secure call the moment the answer lands; a
3230
- // handoff that then fails stops them (the catch below).
3347
+ // page sends next (ebanx.ts), and an approved Payrexx card request whose answer started 3-D Secure covers the
3348
+ // card frame's two 3-D Secure sends (payrexx.ts): kept as the API stored it, the request that raised the
3349
+ // approval. Kept before the page has the answer, since the page may send its first 3-D Secure call the moment
3350
+ // the answer lands; a handoff that then fails stops them (the catch below).
3231
3351
  if (replay.mode !== 'device_encrypt') {
3232
3352
  ebanxFollowUps.remember({ url: request.url, body }, replay);
3233
- ebanxRemembered = true;
3353
+ payrexxFollowUps.remember({ url: request.url, body }, replay);
3354
+ followUpsRemembered = true;
3234
3355
  }
3235
3356
  await cdp.send('Fetch.fulfillRequest', {
3236
3357
  requestId: deliverTo,
@@ -3240,6 +3361,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3240
3361
  }, deliverOn);
3241
3362
  // The frame that received it: the paused request's, or its retry's once rebound.
3242
3363
  datatransRepeats.remember(activeRequest?.frameId || undefined, deliverRequest.url, deliverBody, replay);
3364
+ tokenexIframeRepeats.remember(activeRequest?.frameId || undefined, deliverRequest.url, deliverBody, replay);
3243
3365
  attempt.assertLive();
3244
3366
  // An ACI card-number request answered: its checkout's held CVC and expiry requests get the
3245
3367
  // same answer, and its /payment submission may continue (see AciGate).
@@ -3267,10 +3389,12 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3267
3389
  authorizationId: approvedId ?? lifecycle.getState().authorizationId, detail: failureSummary(err) });
3268
3390
  // Ignore the failure event caused by our own safe decline/error abort.
3269
3391
  const dead = attempt.target ?? { requestId, sessionId };
3270
- // An EBANX token answer this handoff kept follow-ups for and did not finish handing over: nothing after it is
3271
- // claimed (ebanx.ts EbanxFollowUps.stop).
3272
- if (ebanxRemembered)
3392
+ // An answer this handoff kept follow-ups for and did not finish handing over: nothing after it is claimed
3393
+ // (ebanx.ts EbanxFollowUps.stop, payrexx.ts PayrexxFollowUps.stop).
3394
+ if (followUpsRemembered) {
3273
3395
  ebanxFollowUps.stop();
3396
+ payrexxFollowUps.stop();
3397
+ }
3274
3398
  // The business's resume settles once the request is answered: a terminal read is an answer (failed, nothing charged), a lost handoff is not.
3275
3399
  const settlePause = () => { if (businessPause) {
3276
3400
  if (isApprovalOutcome(err))
@@ -3338,6 +3462,11 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3338
3462
  targetContextKnown = true;
3339
3463
  // A page target's id is its main frame's id: a navigation of that frame is the page's own (pausedNavigation).
3340
3464
  pageTargetId = typeof targetInfo.targetId === 'string' && targetInfo.targetId ? targetInfo.targetId : undefined;
3465
+ // The page's URL as its target reports it: the document a held top-level navigation leaves until the page commits
3466
+ // another one (the Page.frameNavigated branch above, and notePageTarget for a commit before the Page events are on).
3467
+ // No frame tree read: a navigation the page starts while this attachment arms would hold it.
3468
+ if (committedTopUrl === undefined && typeof targetInfo.url === 'string' && targetInfo.url)
3469
+ committedTopUrl = targetInfo.url.split('#')[0];
3341
3470
  await cdp.send('Target.setDiscoverTargets', { discover: true });
3342
3471
  assertActive();
3343
3472
  const { targetInfos } = await cdp.send('Target.getTargets');
@@ -3346,6 +3475,9 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3346
3475
  throw new CheckoutAttachmentError('unavailable');
3347
3476
  for (const target of targetInfos)
3348
3477
  assertContextTarget(target, merchantContext, targetAllowance);
3478
+ // The page may have committed another document since its TargetInfo was read; the list names the one it holds.
3479
+ for (const target of targetInfos)
3480
+ notePageTarget(target);
3349
3481
  for (const target of targetInfos)
3350
3482
  if (target?.type === 'shared_worker')
3351
3483
  await watchSharedWorker(target);
@@ -3354,13 +3486,24 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3354
3486
  // but their Fetch setup finishes later. Do not invite the caller to press
3355
3487
  // Pay while one of those already-running frames is still unprotected, or
3356
3488
  // while a shared worker reported during setup is not yet watched.
3357
- while (arming.size || workerWatches.size)
3358
- await Promise.all([...arming.values(), ...workerWatches.values()]);
3489
+ // A window the page opened while this attachment armed it is attached and armed here too, in the order Chromium
3490
+ // reported it (adoptOpenedWindow), and the frames and workers such a window brings are waited for as the page's
3491
+ // own are, all under the setup deadline. One that cannot be armed fails the attachment.
3492
+ while (arming.size || workerWatches.size || pendingWindows.length) {
3493
+ if (arming.size || workerWatches.size)
3494
+ await Promise.all([...arming.values(), ...workerWatches.values()]);
3495
+ else
3496
+ await adoptOpenedWindow(pendingWindows.shift(), true);
3497
+ assertActive();
3498
+ }
3359
3499
  assertActive();
3360
3500
  }, setupTimeoutMs, setupStop.signal);
3361
3501
  if (setupStop.signal.aborted)
3362
3502
  throw attachmentFailure(setupStop.signal.reason);
3363
3503
  attachmentReady = true;
3504
+ // A window reported after that drain, before this line, is adopted the way a later window is.
3505
+ for (const info of pendingWindows.splice(0))
3506
+ void adoptOpenedWindow(info);
3364
3507
  opts.onEvent?.({ type: 'fetch_armed', detail: { patterns: urlPatterns, ...(holdGate ? { hold: true, holdRules: holdGate.rules.map((r) => r.psp) } : {}), ...(bypassServiceWorkers ? { serviceWorkers: 'bypass' } : {}) } });
3365
3508
  }
3366
3509
  catch (error) {
@@ -3424,6 +3567,8 @@ export async function attachToPlaywright(page, opts) {
3424
3567
  const juspayCheckout = new JuspayCheckoutGate(opts.vault);
3425
3568
  // The approved EBANX token request on this attachment and the sends claimed under it (ebanx.ts).
3426
3569
  const ebanxFollowUps = new EbanxFollowUps();
3570
+ // The approved Payrexx card request on this attachment and the 3-D Secure sends claimed under it (payrexx.ts).
3571
+ const payrexxFollowUps = new PayrexxFollowUps();
3427
3572
  const aci = new AciGate(opts.vault);
3428
3573
  let navigationBodyReplaced;
3429
3574
  const eprotect = new EprotectGate(opts.vault);
@@ -3700,6 +3845,8 @@ export async function attachToPlaywright(page, opts) {
3700
3845
  let lastSubmitted = null;
3701
3846
  // The approved Datatrans answers, per Frame: the repeat rule (datatrans.ts).
3702
3847
  const datatransRepeats = new DatatransRepeats();
3848
+ // The approved TokenEx iframe answers, per Frame: the repeat rule (tokenex-iframe.ts).
3849
+ const tokenexIframeRepeats = new TokenexIframeRepeats();
3703
3850
  // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
3704
3851
  const worldlineProducts = new WorldlineMyCheckoutProducts();
3705
3852
  const frameOf = (request) => { try {
@@ -3720,8 +3867,10 @@ export async function attachToPlaywright(page, opts) {
3720
3867
  // The matcher sees only the URL. Worldpay eProtect's registration is a GET, so its family and
3721
3868
  // the vendor's give-up reports are routed by the eProtect gate (never its scripts).
3722
3869
  // Gravy's session fields are a PUT, so a URL a PUT recognizer claims is routed too; the
3723
- // pre-claim gates then pause only the PUT and continue the URL's other methods.
3870
+ // pre-claim gates then pause only the PUT and continue the URL's other methods. A hosted page
3871
+ // entered with a plain GET (hosted-entry.ts, ComGate's gateway link) is routed by its exact link.
3724
3872
  (url) => opts.vault.isCardRequest(url.toString()) || opts.vault.isCardRequest(url.toString(), 'PUT')
3873
+ || opts.vault.isCardRequest(url.toString(), 'GET')
3725
3874
  || guards.matches(url.toString()) || stripeCheckout.matches(url.toString()) || mercadoCheckout.matches(url.toString())
3726
3875
  || eprotect.matches(url.toString())
3727
3876
  // An armed merchant profile's endpoint or a sibling of it (merchant-hosted.ts), as attachToCdp arms it.
@@ -3739,9 +3888,16 @@ export async function attachToPlaywright(page, opts) {
3739
3888
  || aci.matches(url.toString())
3740
3889
  // Every Payrails host, routed only for an org the API served Payrails to (see attachToCdp).
3741
3890
  || (payrails !== null && isPayrailsHost(url.toString()))
3891
+ // Every request to Przelewy24's pay wall hosts once its entry is served (przelewy24.ts), as attachToCdp arms
3892
+ // the hosts: the card method's post, the card form's post and the Saferpay resolver reach the Przelewy24
3893
+ // gate and fail.
3894
+ || isArmedPrzelewy24Url(opts.vault, url.toString())
3742
3895
  // Every request to an NMI host whose entry is served (ekashu.ts), as attachToCdp arms the
3743
3896
  // host: the card page's post and its 3-D Secure calls reach the NMI gate and fail.
3744
3897
  || isArmedEkashuUrl(opts.vault, url.toString())
3898
+ // Every request to a Webpay host once its entry is served (transbank.ts), as attachToCdp arms the hosts: the
3899
+ // token's submit and Webpay's card steps reach the Webpay gate and fail, and the entry pauses.
3900
+ || isArmedTransbankUrl(opts.vault, url.toString())
3745
3901
  // A CreditGuard page's own calls beside its submit (its card lookup and its 3-D Secure calls), for an
3746
3902
  // org the API served CreditGuard to, as attachToCdp arms its hosts: the CreditGuard gate answers them
3747
3903
  // under a ready preparation and lets them go on with none (pre-claim.ts isArmedCreditguardCall).
@@ -3752,6 +3908,15 @@ export async function attachToPlaywright(page, opts) {
3752
3908
  // Every request to QuickPay's host once its entry is served (quickpay.ts), as attachToCdp arms the host:
3753
3909
  // the window's card post, its other card requests and its card lookup reach the QuickPay gate and fail.
3754
3910
  || isArmedQuickpayUrl(opts.vault, url.toString())
3911
+ // Every request to one of ComGate's card hosts once its entry is served (comgate.ts), as attachToCdp arms them:
3912
+ // the gateway's card post and every other card call there reach the ComGate gate and fail.
3913
+ || isArmedComgateUrl(opts.vault, url.toString())
3914
+ // Every request on the TokenEx iframe's /iframe/v3 path once its entry is served (tokenex-iframe.ts), as attachToCdp
3915
+ // arms its hosts: a card post the recognizer's exact URL does not match reaches the TokenEx gate and fails.
3916
+ || isArmedTokenexIframeUrl(opts.vault, url.toString())
3917
+ // Every request to an Eximbay host once its entry is served there (eximbay.ts), as attachToCdp arms the host: the
3918
+ // step, the window's lookups and its card post reach the Eximbay gate and fail.
3919
+ || isArmedEximbayUrl(opts.vault, url.toString())
3755
3920
  // PayPal's legacy Hosted Fields confirm, for every org, as attachToCdp arms it (paypal-hosted-fields.ts).
3756
3921
  || isPaypalHostedFieldsConfirm(url.toString())
3757
3922
  // PayPal's Card Fields confirm and GraphQL on www.paypal.com, for every org, as attachToCdp arms them (paypal-card-forms.ts).
@@ -3857,7 +4022,9 @@ export async function attachToPlaywright(page, opts) {
3857
4022
  } },
3858
4023
  juspay: juspayCheckout,
3859
4024
  datatrans: datatransRepeats,
4025
+ tokenexIframe: tokenexIframeRepeats,
3860
4026
  ebanx: ebanxFollowUps,
4027
+ payrexx: payrexxFollowUps,
3861
4028
  frame: frameOf(request),
3862
4029
  payrails,
3863
4030
  shijiSts: { companions: shijiCompanions, page: async () => (await playwrightShijiDocumentPage(request)) ?? `pw:none:${++playwrightShijiSeq}` },
@@ -3952,7 +4119,7 @@ export async function attachToPlaywright(page, opts) {
3952
4119
  // identically whichever adapter is attached. An Amadeus card request is
3953
4120
  // 'failed' (ERR_FAILED) in both too, see failsAsNetworkError (refusalFailure).
3954
4121
  // As in attachToCdp: an unbound request that skipped the spent preparation's claim is refused.
3955
- // As in attachToCdp: an EBANX send still out holds the attachment too.
4122
+ // As in attachToCdp: an EBANX or Payrexx send still out holds the attachment too.
3956
4123
  const followUpOut = lifecycle.followUpsInFlight();
3957
4124
  if (terminal || lifecycle.isBlocked() || awaitingApproval || followUpOut || preparedRetry || Date.now() < quietUntil) {
3958
4125
  let repeatedAuthorizationId = null;
@@ -3967,7 +4134,7 @@ export async function attachToPlaywright(page, opts) {
3967
4134
  }
3968
4135
  }
3969
4136
  const why = terminal ?? (lifecycle.isBlocked() ? lifecycle.getState().status : awaitingApproval ? 'an approval is already outstanding'
3970
- : followUpOut ? 'an EBANX send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
4137
+ : followUpOut ? 'a follow-up send is in flight' : preparedRetry ? 'CheckoutPreparationError' : 'awaiting approval cooldown');
3971
4138
  opts.onEvent?.({ type: 'blocked', detail: why instanceof Error ? failureSummary(why) : String(why) });
3972
4139
  if (preparation)
3973
4140
  preparationGate.retireUnboundClaim();
@@ -4021,7 +4188,7 @@ export async function attachToPlaywright(page, opts) {
4021
4188
  let approvedId = null;
4022
4189
  let deliveryBegun = false;
4023
4190
  // See the same flag in attachToCdp.
4024
- let ebanxRemembered = false;
4191
+ let followUpsRemembered = false;
4025
4192
  // A prepared Amadeus SDK add must be sent within its window (see amadeus.ts).
4026
4193
  let disarmSendWindow = null;
4027
4194
  try {
@@ -4239,10 +4406,12 @@ export async function attachToPlaywright(page, opts) {
4239
4406
  // See the same step in attachToCdp: kept before the page has the answer.
4240
4407
  if (replay.mode !== 'device_encrypt') {
4241
4408
  ebanxFollowUps.remember({ url: request.url(), body }, replay);
4242
- ebanxRemembered = true;
4409
+ payrexxFollowUps.remember({ url: request.url(), body }, replay);
4410
+ followUpsRemembered = true;
4243
4411
  }
4244
4412
  await deliverTo.fulfill({ status: replay.status, headers: withCorsHeaders(replay.headers, cors.headers), body: replay.body });
4245
4413
  datatransRepeats.remember(frameOf(deliverRequest), deliverRequest.url(), deliverBody, replay);
4414
+ tokenexIframeRepeats.remember(frameOf(deliverRequest), deliverRequest.url(), deliverBody, replay);
4246
4415
  assertRequestLive();
4247
4416
  // See the same step in attachToCdp.
4248
4417
  aci.answered(deliverRequest.url(), { status: replay.status, headers: replay.headers, body: replay.body }, replay.authorizationId);
@@ -4257,8 +4426,10 @@ export async function attachToPlaywright(page, opts) {
4257
4426
  catch (err) {
4258
4427
  const dead = attempt.target?.route ?? route;
4259
4428
  // See the same step in attachToCdp.
4260
- if (ebanxRemembered)
4429
+ if (followUpsRemembered) {
4261
4430
  ebanxFollowUps.stop();
4431
+ payrexxFollowUps.stop();
4432
+ }
4262
4433
  if (activeRequest?.attempt === attempt)
4263
4434
  activeRequest = null;
4264
4435
  try {