@agent-cards/sdk 0.23.27 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/PREFLIGHT.md +7 -1
  3. package/README.md +137 -99
  4. package/dist/aci.d.ts +52 -22
  5. package/dist/aci.js +79 -15
  6. package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
  7. package/dist/adyen-merchant-hosted.generated.js +459 -21
  8. package/dist/agent-amount.generated.js +1 -1
  9. package/dist/agentcard.d.ts +4 -5
  10. package/dist/agentcard.js +4 -5
  11. package/dist/amadeus.d.ts +9 -3
  12. package/dist/amadeus.js +33 -4
  13. package/dist/api-module.d.ts +1 -7
  14. package/dist/api-module.js +3 -5
  15. package/dist/{voice-retry.js → api-retry.js} +1 -1
  16. package/dist/api-types.d.ts +53 -0
  17. package/dist/{voice-types.js → api-types.js} +7 -2
  18. package/dist/approvals.d.ts +166 -0
  19. package/dist/approvals.js +213 -0
  20. package/dist/buy.d.ts +20 -101
  21. package/dist/buy.js +19 -74
  22. package/dist/capabilities.generated.js +1 -1
  23. package/dist/card-fields.generated.js +1398 -106
  24. package/dist/cdp.d.ts +3 -13
  25. package/dist/cdp.js +231 -175
  26. package/dist/client.d.ts +104 -22
  27. package/dist/client.js +215 -40
  28. package/dist/creditguard.generated.d.ts +4 -4
  29. package/dist/datatrans.generated.js +22 -1
  30. package/dist/elavon-converge.generated.js +5 -3
  31. package/dist/hold.d.ts +8 -0
  32. package/dist/hold.js +43 -2
  33. package/dist/hosted-entry.js +2 -0
  34. package/dist/index.d.ts +9 -15
  35. package/dist/index.js +6 -10
  36. package/dist/juspay-gate.js +2 -2
  37. package/dist/juspay.generated.d.ts +2 -2
  38. package/dist/juspay.generated.js +174 -4
  39. package/dist/lifecycle.d.ts +9 -61
  40. package/dist/lifecycle.js +24 -10
  41. package/dist/merchant-card-endpoints.generated.d.ts +6 -1
  42. package/dist/merchant-card-endpoints.generated.js +344 -19
  43. package/dist/merchant-hosted.d.ts +8 -1
  44. package/dist/nike-order.d.ts +51 -0
  45. package/dist/nike-order.js +81 -0
  46. package/dist/oceanpayment.d.ts +38 -0
  47. package/dist/oceanpayment.js +70 -0
  48. package/dist/paddle-tokenex.generated.js +105 -10
  49. package/dist/payplug.d.ts +41 -0
  50. package/dist/payplug.js +71 -0
  51. package/dist/payrails.generated.js +27 -7
  52. package/dist/payrexx.generated.js +176 -2
  53. package/dist/pre-claim.d.ts +9 -0
  54. package/dist/pre-claim.js +86 -7
  55. package/dist/preflight-capabilities.generated.d.ts +0 -4
  56. package/dist/preflight-capabilities.generated.js +0 -5
  57. package/dist/preflight-catalog.json +46 -5
  58. package/dist/preflight-playwright.d.ts +2 -2
  59. package/dist/preflight-playwright.js +63 -14
  60. package/dist/preflight-schemas.json +4 -0
  61. package/dist/preflight.generated.d.ts +0 -4
  62. package/dist/preflight.generated.js +24 -1
  63. package/dist/prepared-processor.d.ts +18 -5
  64. package/dist/prepared-processor.js +48 -8
  65. package/dist/primer.generated.js +1418 -120
  66. package/dist/processout.generated.d.ts +61 -0
  67. package/dist/processout.generated.js +577 -0
  68. package/dist/requests.js +1 -1
  69. package/dist/shiji-sts.generated.js +105 -10
  70. package/dist/stripe-checkout.generated.js +1405 -234
  71. package/dist/substitutions.generated.js +460 -26
  72. package/dist/tap-payments.generated.js +105 -10
  73. package/dist/twoc2p.generated.js +105 -10
  74. package/dist/url-masks/bluesnap.d.ts +2 -0
  75. package/dist/url-masks/bluesnap.js +15 -0
  76. package/dist/url-masks/oceanpayment.d.ts +2 -0
  77. package/dist/url-masks/oceanpayment.js +13 -0
  78. package/dist/url-masks/payplug.d.ts +2 -0
  79. package/dist/url-masks/payplug.js +13 -0
  80. package/dist/url-masks/vtex_payments.d.ts +2 -0
  81. package/dist/url-masks/vtex_payments.js +12 -0
  82. package/dist/url-masks.generated.d.ts +1 -1
  83. package/dist/url-masks.generated.js +6 -2
  84. package/dist/verifone.generated.js +42 -2
  85. package/dist/vtex-payments.generated.d.ts +127 -0
  86. package/dist/vtex-payments.generated.js +544 -0
  87. package/dist/webhooks.d.ts +2 -2
  88. package/dist/webhooks.js +2 -2
  89. package/dist/wspay.generated.d.ts +1 -1
  90. package/dist/wspay.generated.js +175 -3
  91. package/examples/preflight/kernel-native/inventory.json +1 -1
  92. package/package.json +6 -8
  93. package/dist/languages.d.ts +0 -23
  94. package/dist/languages.js +0 -76
  95. package/dist/voice-pkce.d.ts +0 -49
  96. package/dist/voice-pkce.js +0 -147
  97. package/dist/voice-stream.generated.d.ts +0 -118
  98. package/dist/voice-stream.generated.js +0 -170
  99. package/dist/voice-types.d.ts +0 -735
  100. package/dist/voice.d.ts +0 -373
  101. package/dist/voice.js +0 -815
  102. package/readme.test.mjs +0 -53
  103. /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
package/dist/cdp.js CHANGED
@@ -1,15 +1,15 @@
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, redactUrl, } from './client.js';
2
+ import { AdyenTestPlatformRefusedError, AmountRequiredError, ApprovalDeclinedError, ApprovalTimeoutError, CardEncryptedError, CheckoutApiError, 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';
6
6
  import { MercadoCheckoutGate } from './mercado-checkout.js';
7
7
  import { MERCADO_CHECKOUT_PATTERN } from './mercado-checkout.generated.js';
8
8
  import { JuspayCheckoutGate } from './juspay-gate.js';
9
- import { ACI_CHECKOUT_REUSED, AciGate, playwrightReplacesNavigationBody } from './aci.js';
10
- import { EprotectGate, classifyEprotectRequest, eprotectScriptAnswer } from './eprotect.js';
11
- import { SbpsGate, classifySbpsRequest, sbpsScriptAnswer } from './sbps.js';
12
- import { dispatchPreClaim, eventUrl, isArmedCreditguardCall, routesHipayVault } from './pre-claim.js';
9
+ import { AciGate, playwrightReplacesNavigationBody } from './aci.js';
10
+ import { EprotectGate } from './eprotect.js';
11
+ import { SbpsGate } from './sbps.js';
12
+ import { dispatchPreClaim, eventUrl, isArmedCreditguardCall, nikeOrderGate, routesHipayVault } from './pre-claim.js';
13
13
  import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
14
14
  import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
15
15
  import { isToastPaymentMethodsUrl, readToastBody } from './toast.generated.js';
@@ -26,12 +26,16 @@ import { isArmedTransbankUrl, transbankMerchantReturn, transbankMerchantReturnAn
26
26
  import { isArmedRedsysUrl } from './redsys.js';
27
27
  import { isArmedPrzelewy24Url } from './przelewy24.js';
28
28
  import { isArmedBarionUrl } from './barion.js';
29
+ import { isArmedPayplugUrl } from './payplug.js';
29
30
  import { isArmedQuickpayUrl } from './quickpay.js';
30
31
  import { COMGATE_CARD_PATTERNS, isArmedComgateUrl, servesComgate } from './comgate.js';
31
32
  import { isArmedEximbayUrl } from './eximbay.js';
33
+ import { isArmedOceanpaymentUrl } from './oceanpayment.js';
32
34
  import { PAYPAL_HOSTED_FIELDS_PATTERNS, isPaypalHostedFieldsConfirm } from './paypal-hosted-fields.js';
33
35
  import { EBANX_RESEND_PATTERNS, EbanxFollowUps, isEbanxResendUrl } from './ebanx.js';
34
36
  import { PayrexxFollowUps } from './payrexx.js';
37
+ import { NIKE_ORDER_PATTERNS, NikeOrders, nikeOrderRouted } from './nike-order.js';
38
+ import { nikeOrderRequest as sharedNikeOrderRequest } from './merchant-card-endpoints.generated.js';
35
39
  import { PAYPAL_CARD_FORM_PATTERNS, isPaypalCardFormUrl } from './paypal-card-forms.js';
36
40
  import { CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, SecureAcceptanceFollowUpWatch, isCybersourceSecureAcceptanceUrl, secureAcceptanceFollowUp, secureAcceptanceFrameAnswer, secureAcceptanceReplyLast4, } from './cybersource-secure-acceptance.js';
37
41
  import { CheckoutAttachmentError, attachmentDeadline, attachmentFailure, withinAttachmentDeadline } from './attachment.js';
@@ -94,6 +98,18 @@ const HOSTED_FORM_REPEAT_REASON = 'already submitted on the cardholder\'s device
94
98
  function targetRecord(value) {
95
99
  return value !== null && typeof value === 'object' && !Array.isArray(value);
96
100
  }
101
+ /**
102
+ * Per CDP connection, the attachment holding Nike's order at the browser target for each browser context: every one sees
103
+ * each pause there, and only the holder of the context that sent a request answers it (attachToCdp nikeOrderAtRoot). Each
104
+ * time an attachment asks through Nike's profile in a context, it takes the context over from whichever one held it.
105
+ */
106
+ const nikeRootHolders = new WeakMap();
107
+ /**
108
+ * Per Playwright browser context, the attachment whose route judges Nike's order there: the last one whose route was on
109
+ * when it asked through Nike's profile (attachToPlaywright holdNikeOrdersInContext). Every other attachment's route
110
+ * hands the order to the next route.
111
+ */
112
+ const nikeContextHolders = new WeakMap();
97
113
  function contextId(value) {
98
114
  return typeof value === 'string' && value.length > 0 && value.length <= 256 && !/[\u0000-\u0020\u007f]/.test(value);
99
115
  }
@@ -130,6 +146,11 @@ function assertContextTarget(target, merchantContext, allow = {}) {
130
146
  // reports are read, and a card request seen there ends the checkout before anything is approved (sharedWorkers).
131
147
  if (allow.sharedWorkers && target.type === 'shared_worker' && knownContext)
132
148
  return;
149
+ // A worklet (CDP type 'worklet': an audio, paint, animation or layout worklet) has no fetch, XHR, WebSocket or
150
+ // beacon in its global scope, so it can never send a card request; the module it loads is fetched by its owning
151
+ // frame, through that frame's interception. Shopify's checkout runs one in its web-pixel sandbox frames.
152
+ if (target.type === 'worklet' && knownContext)
153
+ return;
133
154
  if (!['page', 'iframe', 'worker', 'browser_ui'].includes(target.type)
134
155
  || (target.browserContextId !== undefined && !contextId(target.browserContextId))) {
135
156
  throw new CheckoutAttachmentError('unavailable');
@@ -1447,6 +1468,8 @@ function merchantAttempt(lifecycle, survives) {
1447
1468
  // prepare again. Every other stop leaves the outcome unknown.
1448
1469
  const error = () => reason === 'amadeus_send_window_closed' || reason === 'secure_acceptance_send_window_closed'
1449
1470
  || reason === 'wix_payments_send_window_closed' || reason === 'hipay_send_window_closed' || reason === 'openpay_send_window_closed'
1471
+ || reason === 'vtex_payments_send_window_closed'
1472
+ || reason === 'processout_send_window_closed'
1450
1473
  ? new CheckoutPreparationError(lifecycle.getState().preparationId ?? null, reason)
1451
1474
  : new PaymentOutcomeUnknownError(lifecycle.getState().authorizationId, reason);
1452
1475
  const attempt = {
@@ -1560,53 +1583,6 @@ async function retireUnusedApproval(opts, lifecycle, authorizationId) {
1560
1583
  opts.onEvent?.({ type: 'failed', detail: 'PaymentOutcomeUnknownError' });
1561
1584
  }
1562
1585
  }
1563
- /**
1564
- * The business's read, as a replay this transport can deliver. An approved `token` read carries
1565
- * the processor's answer; `cse` the ciphertext; `hosted_form` the device's stamp. A terminal read is thrown as
1566
- * the error the create path would have thrown for the same outcome, so the request is failed with nothing
1567
- * charged and the page's next request is quieted as after any decline. A read that is not final, or an
1568
- * approval without a usable answer, is refused here and leaves the request paused.
1569
- */
1570
- function replayOf(result, timeoutMs) {
1571
- const status = result.status;
1572
- if (status === 'awaiting_approval')
1573
- throw new Error('The purchase still awaits the person: resume takes a final read (voice.awaitResult), not an open one.');
1574
- if (status === 'declined')
1575
- throw new ApprovalDeclinedError(result.terminal_reason ?? 'declined');
1576
- if (status === 'cancelled')
1577
- throw new ApprovalDeclinedError(result.terminal_reason ?? 'cancelled');
1578
- if (status === 'expired' || status === 'timeout')
1579
- throw new ApprovalTimeoutError(timeoutMs);
1580
- if (status !== 'approved' && status !== 'completed')
1581
- throw new PaymentOutcomeUnknownError(result.id ?? null, 'authorization_status_unrecognized');
1582
- const authorizationId = result.id;
1583
- if (typeof authorizationId !== 'string' || !/^cauth_[A-Za-z0-9_-]{1,128}$/.test(authorizationId))
1584
- throw new Error('resume needs the authorization\'s id on the read (voice.awaitResult carries it).');
1585
- const execution = result.execution_mode === 'autopilot' || result.execution_mode === 'user_approval' ? { executionMode: result.execution_mode } : {};
1586
- const mode = result.mode ?? (result.response ? 'token' : result.substitutions ? 'cse' : result.submitted_at ? 'hosted_form' : 'token');
1587
- if (mode === 'hosted_form') {
1588
- if (typeof result.submitted_at !== 'string' || !result.submitted_at)
1589
- throw new Error('A hosted-form read resumes with submitted_at, the device\'s stamp.');
1590
- return { mode: 'hosted_form', kind: 'submitted_on_device', outcome: 'unverified', authorizationId, submittedAt: result.submitted_at, ...execution };
1591
- }
1592
- if (mode === 'cse') {
1593
- const sub = result.substitutions;
1594
- if (!sub || sub.encoding !== 'json' || typeof sub.at !== 'string' || !sub.fields || typeof sub.fields !== 'object')
1595
- throw new Error('A cse read resumes with substitutions, the ciphertext the device produced.');
1596
- return { mode: 'cse', authorizationId, substitutions: { encoding: 'json', at: sub.at, fields: { ...sub.fields }, ...(sub.remove ? { remove: [...sub.remove] } : {}) }, ...execution };
1597
- }
1598
- if (mode !== 'token')
1599
- throw new PaymentOutcomeUnknownError(authorizationId, 'approved_mode_unsupported');
1600
- const response = result.response;
1601
- if (!response || !Number.isInteger(response.status) || response.status < 100 || response.status > 599 || typeof response.body !== 'string'
1602
- || !response.headers || typeof response.headers !== 'object' || Array.isArray(response.headers)) {
1603
- throw new Error('An approved token read resumes with response, the processor\'s answer (voice.awaitResult carries it once the purchase finished).');
1604
- }
1605
- // The read's processor_context rides as it came: a Paddle VGS answer is handed to the page only with the
1606
- // approved card's details its /pay names (paddle-vgs-pay.ts checks them before the hand-over).
1607
- return { mode: 'token', authorizationId, status: response.status, headers: { ...response.headers }, body: response.body, amountVerified: null,
1608
- ...(result.processor_context !== undefined ? { processorContext: result.processor_context } : {}), ...execution };
1609
- }
1610
1586
  /** A CDP Fetch url pattern (`*` any run of characters, `?` one, `\\` escapes) against a URL. */
1611
1587
  function urlPatternMatches(pattern, url) {
1612
1588
  let re = '';
@@ -1636,39 +1612,6 @@ function holdUntilDecided(decision, signal, lost) {
1636
1612
  signal.addEventListener('abort', lost, { once: true });
1637
1613
  return decision.finally(() => signal.removeEventListener('abort', lost));
1638
1614
  }
1639
- /**
1640
- * Worldpay eProtect's registration, resumed by the business. The read carries the registration as the API
1641
- * stored it, bare JSON, and the paused request is the vendor's JSONP <script>, which runs its answer and reads
1642
- * the registration only through the callback the paused URL named. So it gets the answer authorize() gives on
1643
- * the create path (client.ts): the registration inside that callback, under Worldpay's script type, and only a
1644
- * registration payment-core's judge accepts for that URL. A read in any other mode never answers it either. The
1645
- * paused URL alone decides, whatever recognizer claimed it. Any other request's replay passes as it came, and a
1646
- * create's replay never comes here, so no answer is wrapped twice.
1647
- */
1648
- function eprotectResumedReplay(replay, request) {
1649
- if (classifyEprotectRequest(request.url, request.method)?.kind !== 'registration')
1650
- return replay;
1651
- const answer = replay.mode === 'token' ? eprotectScriptAnswer(request.url, replay) : null;
1652
- if (replay.mode !== 'token' || !answer)
1653
- throw new PaymentOutcomeUnknownError(replay.authorizationId, 'eprotect_registration_unconfirmed');
1654
- return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
1655
- }
1656
- /**
1657
- * SB Payment Service's token request, resumed by the business, on the same terms as eProtect's above. The read
1658
- * carries the token as the API stored it, which the paused <script> cannot run, so it gets the answer authorize()
1659
- * gives on the create path (client.ts): the token inside the callback the paused URL named, under a script type,
1660
- * and only a token payment-core's judge accepts for that URL. A read in any other mode never answers it either.
1661
- * The paused URL alone decides, whatever recognizer claimed it. Any other request's replay passes as it came, and
1662
- * a create's replay never comes here, so no answer is wrapped twice.
1663
- */
1664
- function sbpsResumedReplay(replay, request) {
1665
- if (classifySbpsRequest(request.url, request.method)?.kind !== 'token_request')
1666
- return replay;
1667
- const answer = replay.mode === 'token' ? sbpsScriptAnswer(request.url, replay) : null;
1668
- if (replay.mode !== 'token' || !answer)
1669
- throw new PaymentOutcomeUnknownError(replay.authorizationId, 'sbps_token_unconfirmed');
1670
- return { ...replay, status: answer.status, headers: answer.headers, body: answer.body };
1671
- }
1672
1615
  /** The recogniser that claimed a paused request, by processor name, so a caller never re-matches the URL; null on a duck-typed vault without the read. */
1673
1616
  function recognizerNameOf(opts, url, method) {
1674
1617
  const read = opts.vault.recognizerOf;
@@ -1681,61 +1624,6 @@ function recognizerNameOf(opts, url, method) {
1681
1624
  return null;
1682
1625
  }
1683
1626
  }
1684
- function pauseForBusiness(opts, lifecycle, attemptSignal, request, pageOrigin) {
1685
- const timeoutMs = opts.timeoutMs ?? 15 * 60_000;
1686
- let deliveredResolve = () => { }, deliveredReject = () => { };
1687
- const delivered = new Promise((resolve, reject) => { deliveredResolve = resolve; deliveredReject = reject; });
1688
- delivered.catch(() => { });
1689
- const replay = new Promise((resolve, reject) => {
1690
- let settled = false;
1691
- const signals = [lifecycle.abort.signal, attemptSignal];
1692
- let timer;
1693
- const finish = (fn) => {
1694
- if (settled)
1695
- return;
1696
- settled = true;
1697
- clearTimeout(timer);
1698
- for (const signal of signals)
1699
- signal.removeEventListener('abort', onAbort);
1700
- lifecycle.setResumeHandler(null);
1701
- fn();
1702
- };
1703
- const onAbort = () => finish(() => reject(attemptSignal.aborted ? (attemptSignal.reason ?? new PaymentOutcomeUnknownError(null, 'merchant_request_aborted')) : new CheckoutCancelledError()));
1704
- timer = setTimeout(() => finish(() => reject(new ApprovalTimeoutError(timeoutMs))), timeoutMs);
1705
- timer.unref?.();
1706
- for (const signal of signals)
1707
- signal.addEventListener('abort', onAbort, { once: true });
1708
- if (signals.some((signal) => signal.aborted)) {
1709
- onAbort();
1710
- return;
1711
- }
1712
- lifecycle.setResumeHandler((result) => {
1713
- if (typeof result.id === 'string' && result.id)
1714
- lifecycle.approvalCreated(result.id);
1715
- let outcome;
1716
- try {
1717
- outcome = replayOf(result, timeoutMs);
1718
- }
1719
- catch (err) {
1720
- // A terminal outcome answers the request (failed, nothing charged); a read that is not final leaves it paused.
1721
- if (err instanceof ApprovalDeclinedError || err instanceof ApprovalTimeoutError || err instanceof PaymentOutcomeUnknownError) {
1722
- finish(() => reject(err));
1723
- return delivered;
1724
- }
1725
- return Promise.reject(err);
1726
- }
1727
- finish(() => resolve(outcome));
1728
- return delivered;
1729
- });
1730
- try {
1731
- Promise.resolve(opts.onPaused({ request, recognizer: recognizerNameOf(opts, request.url, request.method), ...(pageOrigin ? { page_origin: pageOrigin } : {}), controller: lifecycle })).catch((err) => finish(() => reject(err)));
1732
- }
1733
- catch (err) {
1734
- finish(() => reject(err));
1735
- }
1736
- });
1737
- return { replay, delivered: { resolve: deliveredResolve, reject: deliveredReject } };
1738
- }
1739
1627
  /**
1740
1628
  * Arm the send window of a request paused under a preparation, when its processor sets one: a
1741
1629
  * prepared Amadeus SDK add, a prepared Secure Acceptance token create and a prepared Wix tokenize
@@ -1789,10 +1677,6 @@ function watchSecureAcceptanceFollowUp(watch, preparation, replay) {
1789
1677
  */
1790
1678
  export async function attachToCdp(cdp, pageSessionId, opts) {
1791
1679
  opts = safeOptions(opts);
1792
- if (opts.pauseOnly && typeof opts.onPaused !== 'function')
1793
- throw new Error('pauseOnly needs onPaused: the business receives the paused request there and answers it with controller.resume.');
1794
- if (opts.pauseOnly && opts.hold)
1795
- throw new Error('hold and pauseOnly are two different pauses: attach with one of them.');
1796
1680
  const setupTimeoutMs = attachmentDeadline(opts.attachmentTimeoutMs);
1797
1681
  const setupStop = new AbortController();
1798
1682
  let attachmentReady = false;
@@ -1801,6 +1685,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1801
1685
  const sessionTypes = new Map([[pageSessionId, 'page']]);
1802
1686
  // Each window this attachment adopted (adoptOpenedWindow): its session, and its target's id, which is its main frame's.
1803
1687
  const windowTargets = new Map();
1688
+ // The id of every target this attachment armed besides its page: each adopted window and each child it attached.
1689
+ const armedTargets = new Set();
1804
1690
  const sessionParents = new Map();
1805
1691
  // The frame in its parent session that a child target hangs from (TargetInfo.parentFrameId), so a
1806
1692
  // frame lookup can climb from a cross-origin frame into the document that frames it.
@@ -1914,6 +1800,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1914
1800
  const ebanxFollowUps = new EbanxFollowUps();
1915
1801
  // The approved Payrexx card request on this attachment and the 3-D Secure sends claimed under it (payrexx.ts).
1916
1802
  const payrexxFollowUps = new PayrexxFollowUps();
1803
+ // The approval Nike's order is held to once a card went through Nike's profile (nike-order.ts).
1804
+ const nikeOrders = new NikeOrders();
1917
1805
  const aci = new AciGate(opts.vault);
1918
1806
  const eprotect = new EprotectGate(opts.vault);
1919
1807
  const sbps = new SbpsGate(opts.vault);
@@ -2246,8 +2134,15 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2246
2134
  ...(payrails ? PAYRAILS_GUARD_PATTERNS : []), ...PAYPAL_HOSTED_FIELDS_PATTERNS, ...PAYPAL_CARD_FORM_PATTERNS,
2247
2135
  ...CYBERSOURCE_SECURE_ACCEPTANCE_PATTERNS, ...(recognizesPaddleVgs(opts.vault) ? PADDLE_VGS_PAY_PATTERNS : []), ...EBANX_RESEND_PATTERNS,
2248
2136
  ...(servesComgate(opts.vault) ? COMGATE_CARD_PATTERNS : [])])];
2137
+ // Nike's API host is armed in every frame this checkout arms, from the attach on: once a card went through Nike's
2138
+ // profile, Nike's order gate judges each order the page sends, a document loaded before the ask included, and anything
2139
+ // before that, or that is not the order, continues untouched (see nike-order.ts). Chrome applies the browser target's
2140
+ // interception (holdNikeOrdersAtRoot) only to documents loaded after it is armed, so it cannot hold these. The host
2141
+ // stays out of urlPatterns, which name what the SDK's own rules cover: in hold mode, a request to it that carries the
2142
+ // placeholder card is still held as hold-only.
2143
+ const armedPatterns = [...urlPatterns, ...NIKE_ORDER_PATTERNS];
2249
2144
  // The hold-only rules' patterns (Bolt's tokenizer; every request a placeholder card can ride, by resource type).
2250
- const fetchPatterns = [...urlPatterns.map((urlPattern) => ({ urlPattern, requestStage: 'Request' })),
2145
+ const fetchPatterns = [...armedPatterns.map((urlPattern) => ({ urlPattern, requestStage: 'Request' })),
2251
2146
  ...(holdGate ? holdGate.patterns().map((p) => ({ ...p, requestStage: 'Request' })) : [])];
2252
2147
  const arm = async (sessionId, resume = false, worker = false) => {
2253
2148
  const key = sessionId ?? '__root__';
@@ -2416,6 +2311,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2416
2311
  sessionTypes.set(child, 'page');
2417
2312
  sessionParents.set(child, pageSessionId);
2418
2313
  windowTargets.set(child, info.targetId);
2314
+ armedTargets.add(info.targetId);
2419
2315
  await arm(child);
2420
2316
  opts.onEvent?.({ type: 'window_armed', detail: {} });
2421
2317
  }
@@ -2423,6 +2319,107 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2423
2319
  failInterception(error);
2424
2320
  }
2425
2321
  };
2322
+ /**
2323
+ * Nike's order is held at the browser target too, from the moment this attachment asks the cardholder to pay through
2324
+ * Nike's profile (nike-order.ts), for the routes this checkout's own frames do not cover (armedPatterns): Chrome applies
2325
+ * Fetch there only to a document, worker or shared worker that loads after it is armed, and pauses a request to Nike's
2326
+ * API host from every one of them in the browser, a tab this checkout did not open included. One that cannot be armed
2327
+ * refuses that payment before anything is created.
2328
+ */
2329
+ let nikeAtRoot = null;
2330
+ // This attachment among those holding Nike's order on the same connection (nikeRootHolders), by its browser context.
2331
+ const nikeHolder = {};
2332
+ const holdNikeOrdersAtRoot = () => {
2333
+ // Every ask takes the context over, one for another attempt after a later attachment asked there included; the
2334
+ // browser target is armed once, and the context's other pages open by then at each ask.
2335
+ const holders = nikeRootHolders.get(cdp) ?? new Map();
2336
+ nikeRootHolders.set(cdp, holders.set(merchantContext, nikeHolder));
2337
+ return (nikeAtRoot ??= cdp.send('Fetch.enable', { patterns: NIKE_ORDER_PATTERNS.map((urlPattern) => ({ urlPattern, requestStage: 'Request' })) }))
2338
+ .then(holdNikeOrdersInPages);
2339
+ };
2340
+ /**
2341
+ * Every other page of this browser context, and every frame in one, open when this attachment asks through Nike's
2342
+ * profile: the browser target's pause never reaches a document already loaded, so each gets its own interception for
2343
+ * Nike's API host (nikeOrderInPage), as Playwright's context route covers every page of its context. A page or frame
2344
+ * that cannot be armed refuses the payment, as the browser target does.
2345
+ */
2346
+ const nikePageTargets = new Set();
2347
+ const nikePageSessions = new Set();
2348
+ const holdNikeOrdersInPages = async () => {
2349
+ const { targetInfos } = await cdp.send('Target.getTargets');
2350
+ if (!Array.isArray(targetInfos))
2351
+ throw new Error('targets_unreadable');
2352
+ for (const info of targetInfos) {
2353
+ if (!targetRecord(info) || (info.type !== 'page' && info.type !== 'iframe') || info.browserContextId !== merchantContext
2354
+ || typeof info.targetId !== 'string' || info.targetId === pageTargetId || armedTargets.has(info.targetId)
2355
+ || nikePageTargets.has(info.targetId))
2356
+ continue;
2357
+ const { sessionId } = await cdp.send('Target.attachToTarget', { targetId: info.targetId, flatten: true });
2358
+ if (typeof sessionId !== 'string' || !sessionId)
2359
+ throw new Error('target_unattached');
2360
+ nikePageSessions.add(sessionId);
2361
+ await cdp.send('Fetch.enable', { patterns: NIKE_ORDER_PATTERNS.map((urlPattern) => ({ urlPattern, requestStage: 'Request' })) }, sessionId);
2362
+ // Recorded once its interception is on, so the next attempt after one that failed here arms it again.
2363
+ nikePageTargets.add(info.targetId);
2364
+ }
2365
+ };
2366
+ // Each target's browser context, from discovery on: a page or frame of another context, the default one included,
2367
+ // belongs to another checkout. A report that names no context never replaces one that did.
2368
+ const targetContexts = new Map();
2369
+ const noteTargetContext = (info) => {
2370
+ if (typeof info?.targetId === 'string' && (contextId(info.browserContextId) || !targetContexts.has(info.targetId))) {
2371
+ targetContexts.set(info.targetId, info.browserContextId);
2372
+ }
2373
+ };
2374
+ /**
2375
+ * A request paused at the browser target, answered by the attachment holding Nike's order in the browser context that
2376
+ * sent it: its Nike order gate judges it, and anything else goes on untouched. Every attachment on the connection sees
2377
+ * each pause, so the others leave it alone, an earlier attachment a later one took the context over from included. A
2378
+ * request from a context nobody holds goes on untouched, from the first holder alone; one from a source the browser
2379
+ * never reported as a target (a shared worker) is the holder's while one context is held, and while several are, nobody
2380
+ * can tell whose it is, so the first holder refuses it when it is Nike's order and lets anything else go on.
2381
+ */
2382
+ const nikeOrderAtRoot = async (requestId, request, frameId) => {
2383
+ // Root pauses this attachment did not arm are another attachment's to answer.
2384
+ if (!nikeAtRoot)
2385
+ return;
2386
+ const holders = nikeRootHolders.get(cdp) ?? new Map();
2387
+ // The page's own frame is this checkout's, whatever a later report named.
2388
+ const placed = typeof frameId === 'string' && (frameId === pageTargetId || targetContexts.has(frameId));
2389
+ const owner = placed ? holders.get(frameId === pageTargetId ? merchantContext : targetContexts.get(frameId))
2390
+ : holders.size === 1 ? [...holders.values()][0] : null;
2391
+ const fail = () => cdp.send('Fetch.failRequest', { requestId, errorReason: cdpFailure(request.url) }).catch(() => { });
2392
+ if (owner !== nikeHolder) {
2393
+ // Only an order whose owner nobody can tell is refused: anything else nobody holds goes on.
2394
+ if ((owner === undefined || owner === null) && [...holders.values()][0] === nikeHolder) {
2395
+ await (owner === null && sharedNikeOrderRequest(request.url, request.method) === true ? fail()
2396
+ : cdp.send('Fetch.continueRequest', { requestId }).catch(() => { }));
2397
+ }
2398
+ return;
2399
+ }
2400
+ await judgeNikeOrder(requestId, request);
2401
+ };
2402
+ /**
2403
+ * A request paused in another page or frame of this context, armed for Nike's order at the ask (holdNikeOrdersInPages):
2404
+ * the attachment holding the context judges it, and any other lets it go on, to the holder, which armed every page and
2405
+ * frame of the context it does not own the same way when it asked.
2406
+ */
2407
+ const nikeOrderInPage = async (sessionId, requestId, request) => {
2408
+ if (nikeRootHolders.get(cdp)?.get(merchantContext) === nikeHolder)
2409
+ await judgeNikeOrder(requestId, request, sessionId);
2410
+ else
2411
+ await cdp.send('Fetch.continueRequest', { requestId }, sessionId).catch(() => { });
2412
+ };
2413
+ // Nike's order gate on a request paused at the browser target (no session) or in a page armed at the ask.
2414
+ const judgeNikeOrder = async (requestId, request, sessionId) => {
2415
+ const fail = () => cdp.send('Fetch.failRequest', { requestId, errorReason: cdpFailure(request.url) }, sessionId).catch(() => { });
2416
+ const gate = {
2417
+ request: { url: request.url, method: request.method, headers: request.headers ?? {}, get body() { return pausedBody(request); } },
2418
+ nikeOrders, lifecycle, onEvent: opts.onEvent, lifecycleAborted: () => lifecycle.abort.signal.aborted, terminal: () => !!terminal,
2419
+ effects: { continueRequest: () => cdp.send('Fetch.continueRequest', { requestId }, sessionId), answer: fail, abort: fail },
2420
+ };
2421
+ await (nikeOrderGate(gate) ?? cdp.send('Fetch.continueRequest', { requestId }, sessionId).catch(() => { }));
2422
+ };
2426
2423
  cdp.on(async (method, params, sessionId) => {
2427
2424
  // A frame that committed a new document drops the companion binding made for the getTokenId its
2428
2425
  // old document sent, with the device's CVV ciphertext: the document that replaced it never
@@ -2435,6 +2432,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2435
2432
  if ((method === 'Target.targetCreated' || method === 'Target.targetInfoChanged') && targetContextKnown) {
2436
2433
  // The page's own target reports each document it commits from discovery on, Page events or not.
2437
2434
  notePageTarget(params?.targetInfo);
2435
+ noteTargetContext(params?.targetInfo);
2438
2436
  try {
2439
2437
  assertContextTarget(params?.targetInfo, merchantContext, targetAllowance);
2440
2438
  if (params?.targetInfo?.type === 'shared_worker')
@@ -2506,6 +2504,16 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2506
2504
  failInterception(new CheckoutAttachmentError('unavailable'));
2507
2505
  return;
2508
2506
  }
2507
+ // A request paused at the browser target, which this attachment arms only for Nike's order (nikeOrderAtRoot).
2508
+ if (method === 'Fetch.requestPaused' && sessionId === undefined) {
2509
+ await nikeOrderAtRoot(params.requestId, params.request, params.frameId);
2510
+ return;
2511
+ }
2512
+ // A request paused in another page or frame of this context that this attachment armed for Nike's order at the ask.
2513
+ if (method === 'Fetch.requestPaused' && sessionId && nikePageSessions.has(sessionId)) {
2514
+ await nikeOrderInPage(sessionId, params.requestId, params.request);
2515
+ return;
2516
+ }
2509
2517
  // The transport is browser-scoped. A second tab's request or child target
2510
2518
  // must never be authorized with this page's merchant, amount or preparation.
2511
2519
  if (!sessionId || !ownedSessions.has(sessionId))
@@ -2754,6 +2762,13 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2754
2762
  await cdp.send('Runtime.runIfWaitingForDebugger', {}, child).catch(() => { });
2755
2763
  return;
2756
2764
  }
2765
+ // A worklet sends no request of its own (assertContextTarget): released from its debugger wait, never owned.
2766
+ if (params.targetInfo?.type === 'worklet' && typeof child === 'string' && child
2767
+ && child !== pageSessionId && !ownedSessions.has(child)) {
2768
+ rejectedSessions.add(child);
2769
+ await cdp.send('Runtime.runIfWaitingForDebugger', {}, child).catch(() => { });
2770
+ return;
2771
+ }
2757
2772
  // CDP may omit the context ID for ordinary children; keep those attached.
2758
2773
  try {
2759
2774
  if (typeof child !== 'string' || !child || !['page', 'iframe', 'worker'].includes(params.targetInfo?.type)) {
@@ -2764,6 +2779,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2764
2779
  throw new CheckoutAttachmentError('unavailable');
2765
2780
  }
2766
2781
  ownedSessions.add(child);
2782
+ if (typeof params.targetInfo.targetId === 'string')
2783
+ armedTargets.add(params.targetInfo.targetId);
2767
2784
  sessionTypes.set(child, params.targetInfo.type);
2768
2785
  sessionParents.set(child, sessionId);
2769
2786
  if (typeof params.targetInfo.parentFrameId === 'string' && params.targetInfo.parentFrameId) {
@@ -2883,6 +2900,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2883
2900
  tokenexIframe: tokenexIframeRepeats,
2884
2901
  ebanx: ebanxFollowUps,
2885
2902
  payrexx: payrexxFollowUps,
2903
+ nikeOrders,
2886
2904
  frame: typeof frameId === 'string' && frameId ? frameId : undefined,
2887
2905
  payrails,
2888
2906
  shijiSts: { companions: shijiCompanions, page: () => cdpShijiPage(sessionId, frameId) },
@@ -3053,8 +3071,6 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3053
3071
  // Whether this handoff kept an approval's follow-ups (ebanxFollowUps.remember, payrexxFollowUps.remember), which a
3054
3072
  // failed handoff stops.
3055
3073
  let followUpsRemembered = false;
3056
- // The business's pause, so resume resolves once the page has its answer.
3057
- let businessPause = null;
3058
3074
  // Hold mode: the person's release, settled with what came of it.
3059
3075
  let holdRelease = null;
3060
3076
  // A prepared Amadeus SDK add must be sent within its window (see amadeus.ts).
@@ -3094,11 +3110,11 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3094
3110
  activeRequest = { attempt, requestId, networkId, sessionId, frameId, request,
3095
3111
  identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape, request.headers),
3096
3112
  preparedFrame: preparation ? { sessionId, frameId } : undefined };
3097
- // An ACI card-number request: the page's CVC and expiry requests for its checkout are held
3098
- // until this approval ends (see AciGate). One whose checkout already has a card-number
3099
- // request running or answered never begins; the pre-claim gate refuses it before this.
3113
+ // An ACI or JSON split card's card request: the page's CVC and expiry requests for its
3114
+ // checkout are held until this approval ends (see AciGate). One whose checkout already has a
3115
+ // card request running or answered never begins; the pre-claim gate refuses it before this.
3100
3116
  if (!aci.begin(request.url, request.method, body, preparation?.merchantOrigin)) {
3101
- throw new CheckoutPreparationError(preparation?.id ?? null, ACI_CHECKOUT_REUSED);
3117
+ throw new CheckoutPreparationError(preparation?.id ?? null, aci.reuseCode(request.url));
3102
3118
  }
3103
3119
  // A Worldpay eProtect registration: the vendor's give-up report for it ends this attempt as
3104
3120
  // the page abandoning its request (see EprotectGate.gaveUp).
@@ -3148,8 +3164,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3148
3164
  const cardRecord = cardEndpoint ? await cardEndpointFacts(cardEndpointRecord, cardEndpoint, request.url, pausedAt, attempt.signal) : null;
3149
3165
  attempt.assertLive();
3150
3166
  // Paddle's own checkout answers as of the pause, once the texts of those that finished before it are read
3151
- // (a Billing /pay only; sent only with a Paddle TokenEx create, which a pause-only business makes itself).
3152
- const paddleAnswers = opts.pauseOnly ? undefined : await paddleCheckout.capture(pausedAt, request.url, attempt.signal);
3167
+ // (a Billing /pay only; sent only with a Paddle TokenEx create).
3168
+ const paddleAnswers = await paddleCheckout.capture(pausedAt, request.url, attempt.signal);
3153
3169
  attempt.assertLive();
3154
3170
  // Hold mode: nothing is created until the person's total releases the request (hold.ts).
3155
3171
  if (holdGate) {
@@ -3167,10 +3183,11 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3167
3183
  holdRelease = decision;
3168
3184
  attempt.assertLive();
3169
3185
  }
3170
- // Decision 176: pause only. The business creates the authorization from the paused request and answers
3171
- // through controller.resume; the replay it hands back goes down the same delivery path as a create's.
3172
- const pause = opts.pauseOnly ? pauseForBusiness(opts, lifecycle, attempt.signal, { url: request.url, method: request.method, headers: request.headers ?? {}, body }, pageOrigin) : null;
3173
- businessPause = pause;
3186
+ // From the create on, the device may store the card at the merchant: Nike's orders are held from here (nike-order.ts).
3187
+ if (cardEndpoint && nikeOrders.asking(cardEndpoint.id)) {
3188
+ await holdNikeOrdersAtRoot().catch(() => { throw new CardEndpointRefusedError(cardEndpoint.id, 'request_unsupported', "Nike's order cannot be held in this browser"); });
3189
+ attempt.assertLive();
3190
+ }
3174
3191
  // A sandbox hold never sends the request on: the approval is created at the released total and read, the
3175
3192
  // page's request fails the moment it exists, and the attempt ends with nothing charged.
3176
3193
  const holdSandbox = !!holdRelease && opts.vault.sandbox === true;
@@ -3185,7 +3202,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3185
3202
  lifecycle.abort.signal.addEventListener('abort', retire, { once: true });
3186
3203
  }
3187
3204
  let holdRequestFailed = false;
3188
- const authorization = pause ? null : opts.vault.authorize({
3205
+ const authorization = opts.vault.authorize({
3189
3206
  user: opts.user,
3190
3207
  merchant: opts.merchant,
3191
3208
  amount: holdRelease ? holdRelease.amount : opts.amount,
@@ -3198,6 +3215,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3198
3215
  pageOrigin,
3199
3216
  pageAmount,
3200
3217
  ...(payToInterceptMs !== undefined ? { payToInterceptMs } : {}),
3218
+ // When the request paused, which a VTEX card post's send-by counts from (the client sends it for VTEX alone).
3219
+ pausedAt,
3201
3220
  preparation,
3202
3221
  timeoutMs: opts.timeoutMs,
3203
3222
  signal: lifecycle.abort.signal,
@@ -3231,7 +3250,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3231
3250
  ...(flexPause ? flexPause.sidecar : {}),
3232
3251
  ...(payrailsSidecar ? { payrails: payrailsSidecar } : {}) },
3233
3252
  });
3234
- if (holdSandbox && authorization) {
3253
+ if (holdSandbox) {
3235
3254
  const outcome = await authorization.then((r) => ({ outcome: 'approved_not_charged', authorizationId: r.authorizationId }), (err) => ({ outcome: err instanceof ApprovalDeclinedError ? 'declined' : err instanceof ApprovalTimeoutError ? 'expired' : 'failed',
3236
3255
  authorizationId: lifecycle.getState().authorizationId, detail: failureSummary(err) }));
3237
3256
  if (!holdRequestFailed)
@@ -3239,7 +3258,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3239
3258
  holdRelease.settle(outcome);
3240
3259
  throw new HoldAnsweredError(outcome);
3241
3260
  }
3242
- const replay = pause ? sbpsResumedReplay(eprotectResumedReplay(await pause.replay, request), request) : await authorization;
3261
+ const replay = await authorization;
3243
3262
  attempt.assertLive();
3244
3263
  approvedId = replay.authorizationId;
3245
3264
  if (lifecycle.isCancelled())
@@ -3272,6 +3291,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3272
3291
  // False only for a card endpoint answer the device withheld (see deliverCardEndpointAnswer).
3273
3292
  let handedOver = true;
3274
3293
  if (replay.mode === 'card_endpoint') {
3294
+ // The approved amount Nike's order after the card is held to (nike-order.ts).
3295
+ nikeOrders.remember(replay);
3275
3296
  handedOver = await deliverCardEndpointAnswer(opts, lifecycle, cardEndpoint, replay, { url: deliverRequest.url, headers: deliverRequest.headers }, {
3276
3297
  readJar: async () => (await cdp.send('Network.getCookies', { urls: [deliverRequest.url] }, deliverOn))?.cookies,
3277
3298
  writeCookie: async (cookie) => {
@@ -3430,7 +3451,6 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3430
3451
  }
3431
3452
  if (handedOver)
3432
3453
  lifecycle.handedOff(replay);
3433
- businessPause?.delivered.resolve();
3434
3454
  holdRelease?.settle({ outcome: 'authorized', authorizationId: replay.authorizationId });
3435
3455
  }
3436
3456
  catch (err) {
@@ -3451,13 +3471,6 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3451
3471
  ebanxFollowUps.stop();
3452
3472
  payrexxFollowUps.stop();
3453
3473
  }
3454
- // The business's resume settles once the request is answered: a terminal read is an answer (failed, nothing charged), a lost handoff is not.
3455
- const settlePause = () => { if (businessPause) {
3456
- if (isApprovalOutcome(err))
3457
- businessPause.delivered.resolve();
3458
- else
3459
- businessPause.delivered.reject(err);
3460
- } };
3461
3474
  if (activeRequest?.attempt === attempt)
3462
3475
  activeRequest = null;
3463
3476
  // Vault clients may normalize a merchantSignal abort. Preserve the
@@ -3492,7 +3505,6 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3492
3505
  // A Secure Acceptance frame post reads CyberSource's side too (secureAcceptanceFailedRefusal).
3493
3506
  const sent = handoffStarted || approvedId !== null, failedFor = approvedId ?? lifecycle.getState().authorizationId;
3494
3507
  await refusePaused(dead.requestId, dead.sessionId, request.url, cardEndpoint, secureAcceptance ? secureAcceptanceFailedRefusal(err, sent, failedFor) : failedNavigationRefusal(err, sent, failedFor), secureAcceptance);
3495
- settlePause();
3496
3508
  }
3497
3509
  finally {
3498
3510
  disarmSendWindow?.();
@@ -3532,8 +3544,10 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3532
3544
  for (const target of targetInfos)
3533
3545
  assertContextTarget(target, merchantContext, targetAllowance);
3534
3546
  // The page may have committed another document since its TargetInfo was read; the list names the one it holds.
3535
- for (const target of targetInfos)
3547
+ for (const target of targetInfos) {
3536
3548
  notePageTarget(target);
3549
+ noteTargetContext(target);
3550
+ }
3537
3551
  for (const target of targetInfos)
3538
3552
  if (target?.type === 'shared_worker')
3539
3553
  await watchSharedWorker(target);
@@ -3560,7 +3574,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3560
3574
  // A window reported after that drain, before this line, is adopted the way a later window is.
3561
3575
  for (const info of pendingWindows.splice(0))
3562
3576
  void adoptOpenedWindow(info);
3563
- opts.onEvent?.({ type: 'fetch_armed', detail: { patterns: urlPatterns, ...(holdGate ? { hold: true, holdRules: holdGate.rules.map((r) => r.psp) } : {}), ...(bypassServiceWorkers ? { serviceWorkers: 'bypass' } : {}) } });
3577
+ opts.onEvent?.({ type: 'fetch_armed', detail: { patterns: armedPatterns, ...(holdGate ? { hold: true, holdRules: holdGate.rules.map((r) => r.psp) } : {}), ...(bypassServiceWorkers ? { serviceWorkers: 'bypass' } : {}) } });
3564
3578
  }
3565
3579
  catch (error) {
3566
3580
  const failure = attachmentFailure(error);
@@ -3589,8 +3603,6 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3589
3603
  */
3590
3604
  export async function attachToPlaywright(page, opts) {
3591
3605
  opts = safeOptions(opts);
3592
- if (opts.pauseOnly)
3593
- throw new Error('pauseOnly is a raw CDP mode (attachToCdp); attachToPlaywright creates the authorization itself.');
3594
3606
  if (opts.hold)
3595
3607
  throw new Error('hold is a raw CDP mode (attachToCdp): it needs the frames\' own interception.');
3596
3608
  const setupTimeoutMs = attachmentDeadline(opts.attachmentTimeoutMs);
@@ -3625,6 +3637,36 @@ export async function attachToPlaywright(page, opts) {
3625
3637
  const ebanxFollowUps = new EbanxFollowUps();
3626
3638
  // The approved Payrexx card request on this attachment and the 3-D Secure sends claimed under it (payrexx.ts).
3627
3639
  const payrexxFollowUps = new PayrexxFollowUps();
3640
+ // The approval Nike's order is held to once a card went through Nike's profile (nike-order.ts).
3641
+ const nikeOrders = new NikeOrders();
3642
+ /**
3643
+ * Nike's order is held for every page of the browser context, from the moment this attachment asks the cardholder to
3644
+ * pay through Nike's profile: a context route on Nike's API host, which the Nike order gate judges. Playwright routes
3645
+ * no shared or service worker's request. A context that cannot take the route refuses that payment before anything
3646
+ * is created. The context keeps the route after this checkout ends; only the route of the attachment holding the
3647
+ * context judges the order (nikeContextHolders), and a released order goes straight to the network, past the others.
3648
+ */
3649
+ let nikeInContext = null;
3650
+ // This attachment, as the holder of Nike's order in its browser context (nikeContextHolders).
3651
+ const nikeHolder = {};
3652
+ const holdNikeOrdersInContext = () => Promise.resolve().then(async () => {
3653
+ const context = page.context();
3654
+ await (nikeInContext ??= Promise.resolve().then(() => context.route((url) => nikeOrderRouted(url.toString()), async (route) => {
3655
+ // Another attachment asked through Nike's profile here since this one did: its route judges the order.
3656
+ if (nikeContextHolders.get(context) !== nikeHolder)
3657
+ return route.fallback();
3658
+ const request = route.request();
3659
+ const fail = () => route.abort(refusalFailure(request.url()));
3660
+ const gate = {
3661
+ request: { url: request.url(), method: request.method(), get headers() { return request.headers(); }, get body() { return request.postData(); } },
3662
+ nikeOrders, lifecycle, onEvent: opts.onEvent, lifecycleAborted: () => lifecycle.abort.signal.aborted, terminal: () => !!terminal,
3663
+ effects: { continueRequest: () => route.continue(), answer: fail, abort: fail },
3664
+ };
3665
+ await (nikeOrderGate(gate) ?? route.fallback());
3666
+ })));
3667
+ // Every ask takes the context over once this attachment's route is on, one for another attempt included.
3668
+ nikeContextHolders.set(context, nikeHolder);
3669
+ });
3628
3670
  const aci = new AciGate(opts.vault);
3629
3671
  let navigationBodyReplaced;
3630
3672
  const eprotect = new EprotectGate(opts.vault);
@@ -3984,6 +4026,9 @@ export async function attachToPlaywright(page, opts) {
3984
4026
  // Every request to Barion's gateway hosts once its entry is served (barion.ts), as attachToCdp arms the hosts:
3985
4027
  // the gateway's email check, card posts and 3-D Secure post reach the Barion gate and fail.
3986
4028
  || isArmedBarionUrl(opts.vault, url.toString())
4029
+ // Every request to PayPlug's page host once its entry is served (payplug.ts): the page's card post reaches the
4030
+ // PayPlug gate and fails.
4031
+ || isArmedPayplugUrl(opts.vault, url.toString())
3987
4032
  // Every request to an NMI host whose entry is served (ekashu.ts), as attachToCdp arms the
3988
4033
  // host: the card page's post and its 3-D Secure calls reach the NMI gate and fail.
3989
4034
  || isArmedEkashuUrl(opts.vault, url.toString())
@@ -4013,6 +4058,9 @@ export async function attachToPlaywright(page, opts) {
4013
4058
  // Every request to an Eximbay host once its entry is served there (eximbay.ts), as attachToCdp arms the host: the
4014
4059
  // step, the window's lookups and its card post reach the Eximbay gate and fail.
4015
4060
  || isArmedEximbayUrl(opts.vault, url.toString())
4061
+ // Every request to an Oceanpayment host once its card post is served (oceanpayment.ts), as attachToCdp arms the
4062
+ // hosts: Cardinal's device-data call and any other post there reach the Oceanpayment gate and fail.
4063
+ || isArmedOceanpaymentUrl(opts.vault, url.toString())
4016
4064
  // PayPal's legacy Hosted Fields confirm, for every org, as attachToCdp arms it (paypal-hosted-fields.ts).
4017
4065
  || isPaypalHostedFieldsConfirm(url.toString())
4018
4066
  // PayPal's Card Fields confirm and GraphQL on www.paypal.com, for every org, as attachToCdp arms them (paypal-card-forms.ts).
@@ -4121,6 +4169,7 @@ export async function attachToPlaywright(page, opts) {
4121
4169
  tokenexIframe: tokenexIframeRepeats,
4122
4170
  ebanx: ebanxFollowUps,
4123
4171
  payrexx: payrexxFollowUps,
4172
+ nikeOrders,
4124
4173
  frame: frameOf(request),
4125
4174
  payrails,
4126
4175
  shijiSts: { companions: shijiCompanions, page: async () => (await playwrightShijiDocumentPage(request)) ?? `pw:none:${++playwrightShijiSeq}` },
@@ -4306,9 +4355,9 @@ export async function attachToPlaywright(page, opts) {
4306
4355
  activeRequest = { request, frames, attempt,
4307
4356
  identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape, request.headers()),
4308
4357
  preparedFrames: preparation ? [...frames] : undefined };
4309
- // An ACI card-number request: see the same step in attachToCdp.
4358
+ // An ACI or JSON split card's card request: see the same step in attachToCdp.
4310
4359
  if (!aci.begin(request.url(), request.method(), body, preparation?.merchantOrigin)) {
4311
- throw new CheckoutPreparationError(preparation?.id ?? null, ACI_CHECKOUT_REUSED);
4360
+ throw new CheckoutPreparationError(preparation?.id ?? null, aci.reuseCode(request.url()));
4312
4361
  }
4313
4362
  // A Worldpay eProtect registration: see the same step in attachToCdp.
4314
4363
  eprotect.begin(request.url(), request.method(), () => attempt.stop());
@@ -4356,6 +4405,10 @@ export async function attachToPlaywright(page, opts) {
4356
4405
  // Paddle's own checkout answers as of the pause, once their texts are read (see attachToCdp).
4357
4406
  const paddleAnswers = await paddleCheckout.capture(pausedAt, request.url(), attempt.signal);
4358
4407
  assertRequestLive();
4408
+ if (cardEndpoint && nikeOrders.asking(cardEndpoint.id)) {
4409
+ await holdNikeOrdersInContext().catch(() => { throw new CardEndpointRefusedError(cardEndpoint.id, 'request_unsupported', "Nike's order cannot be held in this browser"); });
4410
+ assertRequestLive();
4411
+ }
4359
4412
  const replay = await opts.vault.authorize({
4360
4413
  user: opts.user,
4361
4414
  merchant: opts.merchant,
@@ -4369,6 +4422,8 @@ export async function attachToPlaywright(page, opts) {
4369
4422
  pageOrigin,
4370
4423
  pageAmount,
4371
4424
  ...(payToInterceptMs !== undefined ? { payToInterceptMs } : {}),
4425
+ // Same as the CDP path: when the request paused, for a VTEX card post's send-by.
4426
+ pausedAt,
4372
4427
  preparation,
4373
4428
  timeoutMs: opts.timeoutMs,
4374
4429
  signal: lifecycle.abort.signal,
@@ -4415,6 +4470,7 @@ export async function attachToPlaywright(page, opts) {
4415
4470
  // False only for a card endpoint answer the device withheld (see deliverCardEndpointAnswer).
4416
4471
  let handedOver = true;
4417
4472
  if (replay.mode === 'card_endpoint') {
4473
+ nikeOrders.remember(replay);
4418
4474
  handedOver = await deliverCardEndpointAnswer(opts, lifecycle, cardEndpoint, replay, { url: deliverRequest.url(), headers: deliverRequest.headers() }, {
4419
4475
  readJar: () => page.context().cookies(deliverRequest.url()),
4420
4476
  writeCookie: (cookie) => page.context().addCookies([playwrightCookieParams(cookie)]),