@agent-cards/sdk 0.23.28 → 0.24.1
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.
- package/CHANGELOG.md +84 -0
- package/PREFLIGHT.md +7 -1
- package/README.md +197 -102
- package/dist/aci.d.ts +52 -22
- package/dist/aci.js +79 -15
- package/dist/adyen-merchant-hosted.generated.d.ts +2 -2
- package/dist/adyen-merchant-hosted.generated.js +586 -55
- package/dist/agent-amount.generated.js +1 -1
- package/dist/agentcard.d.ts +4 -5
- package/dist/agentcard.js +4 -5
- package/dist/amadeus.d.ts +9 -3
- package/dist/amadeus.js +33 -4
- package/dist/api-module.d.ts +1 -7
- package/dist/api-module.js +3 -5
- package/dist/{voice-retry.js → api-retry.js} +1 -1
- package/dist/api-types.d.ts +53 -0
- package/dist/{voice-types.js → api-types.js} +7 -2
- package/dist/approvals.d.ts +166 -0
- package/dist/approvals.js +213 -0
- package/dist/aurus.d.ts +33 -0
- package/dist/aurus.js +75 -0
- package/dist/builtin-registry.generated.js +1 -1
- package/dist/buy.d.ts +20 -101
- package/dist/buy.js +19 -74
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +2864 -249
- package/dist/cardlink-vpos.d.ts +43 -0
- package/dist/cardlink-vpos.js +90 -0
- package/dist/cdp.d.ts +3 -13
- package/dist/cdp.js +244 -175
- package/dist/checkout-com.generated.js +5 -1
- package/dist/client.d.ts +170 -22
- package/dist/client.js +281 -40
- package/dist/creditguard.generated.d.ts +4 -4
- package/dist/cybersource-flex.generated.d.ts +39 -1
- package/dist/cybersource-flex.generated.js +100 -11
- package/dist/datatrans.generated.js +22 -1
- package/dist/eghl.d.ts +40 -0
- package/dist/eghl.js +72 -0
- package/dist/elavon-converge.generated.js +5 -3
- package/dist/gmo.generated.js +7 -2
- package/dist/hold.d.ts +8 -0
- package/dist/hold.js +43 -2
- package/dist/hosted-entry.js +4 -0
- package/dist/index.d.ts +9 -15
- package/dist/index.js +6 -10
- package/dist/lifecycle.d.ts +9 -61
- package/dist/lifecycle.js +24 -10
- package/dist/merchant-card-endpoints.generated.d.ts +6 -1
- package/dist/merchant-card-endpoints.generated.js +344 -19
- package/dist/merchant-hosted.d.ts +8 -1
- package/dist/nike-order.d.ts +51 -0
- package/dist/nike-order.js +81 -0
- package/dist/oceanpayment.d.ts +38 -0
- package/dist/oceanpayment.js +70 -0
- package/dist/paddle-tokenex.generated.js +111 -11
- package/dist/payplug.d.ts +41 -0
- package/dist/payplug.js +71 -0
- package/dist/payrails.generated.js +33 -8
- package/dist/pre-claim.d.ts +11 -0
- package/dist/pre-claim.js +130 -7
- package/dist/preflight-capabilities.generated.d.ts +0 -4
- package/dist/preflight-capabilities.generated.js +0 -5
- package/dist/preflight-catalog.json +200 -6
- package/dist/preflight-playwright.d.ts +2 -2
- package/dist/preflight-playwright.js +63 -14
- package/dist/preflight-schemas.json +18 -0
- package/dist/preflight.generated.d.ts +5 -4
- package/dist/preflight.generated.js +63 -3
- package/dist/preparation.d.ts +7 -0
- package/dist/preparation.js +21 -0
- package/dist/prepared-processor.d.ts +18 -5
- package/dist/prepared-processor.js +48 -8
- package/dist/primer.generated.js +2889 -268
- package/dist/processout.generated.d.ts +61 -0
- package/dist/processout.generated.js +577 -0
- package/dist/requests.js +1 -1
- package/dist/shiji-sts.generated.js +111 -11
- package/dist/stripe-checkout.generated.js +2761 -319
- package/dist/substitutions.generated.js +553 -26
- package/dist/tap-payments.generated.js +111 -11
- package/dist/twoc2p.generated.js +111 -11
- package/dist/url-masks/bluesnap.d.ts +2 -0
- package/dist/url-masks/bluesnap.js +15 -0
- package/dist/url-masks/oceanpayment.d.ts +2 -0
- package/dist/url-masks/oceanpayment.js +13 -0
- package/dist/url-masks/payplug.d.ts +2 -0
- package/dist/url-masks/payplug.js +13 -0
- package/dist/url-masks/vtex_payments.d.ts +2 -0
- package/dist/url-masks/vtex_payments.js +12 -0
- package/dist/url-masks.generated.d.ts +1 -1
- package/dist/url-masks.generated.js +6 -2
- package/dist/verifone.generated.js +42 -2
- package/dist/vtex-payments.generated.d.ts +127 -0
- package/dist/vtex-payments.generated.js +544 -0
- package/dist/webhooks.d.ts +2 -2
- package/dist/webhooks.js +2 -2
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +6 -8
- package/dist/languages.d.ts +0 -23
- package/dist/languages.js +0 -76
- package/dist/voice-pkce.d.ts +0 -49
- package/dist/voice-pkce.js +0 -147
- package/dist/voice-stream.generated.d.ts +0 -118
- package/dist/voice-stream.generated.js +0 -170
- package/dist/voice-types.d.ts +0 -735
- package/dist/voice.d.ts +0 -373
- package/dist/voice.js +0 -815
- package/readme.test.mjs +0 -53
- /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
package/dist/pre-claim.js
CHANGED
|
@@ -66,7 +66,7 @@ import { datatransPauseDecision, isDatatransHost } from './datatrans.js';
|
|
|
66
66
|
import { DATATRANS_INVALID_REQUEST } from './datatrans.generated.js';
|
|
67
67
|
import { INVALID_TOKENEX_IFRAME_REQUEST, TOKENEX_IFRAME_PSP, isTokenexIframePath, servesTokenexIframe, tokenexIframePauseDecision, } from './tokenex-iframe.js';
|
|
68
68
|
import { isPelecardHost, pelecardRefusal } from './pelecard.js';
|
|
69
|
-
import { ACI_CHECKOUT_REUSED } from './aci.js';
|
|
69
|
+
import { ACI_CHECKOUT_REUSED, SPLIT_CARD_REUSED } from './aci.js';
|
|
70
70
|
import { ACI_EXPIRY_UNSTRIPPABLE } from './aci.generated.js';
|
|
71
71
|
import { CREDITGUARD_PPS_HOSTS, classifyCreditguardRequest, creditguardCompanionAnswer, creditguardCompanionCall, creditguardNuveiRefusal, creditguardProcessRefusal, creditguardProcessTxId, } from './creditguard.generated.js';
|
|
72
72
|
import { isPrimerPciHost, primerRequestVerdict } from './primer.js';
|
|
@@ -78,6 +78,7 @@ import { isFiservCaptureHost, isFiservPausableBody } from './fiserv.js';
|
|
|
78
78
|
import { CYBERSOURCE_FLEX_PSP, isCybersourceFlexHost } from './cybersource-flex.js';
|
|
79
79
|
import { cardEndpointCarriesCard, cardEndpointDetail, cardEndpointFor, cardEndpointLive, cardEndpointPasses, cardEndpointResendFor, cardEndpointReviewed, } from './card-endpoint.js';
|
|
80
80
|
import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoint-page.js';
|
|
81
|
+
import { NIKE_ORDER_DETAIL } from './nike-order.js';
|
|
81
82
|
import { PAYRAILS_HOLDING_REASONS, PAYRAILS_SESSION_MESSAGE, isPayrailsHost, isPayrailsPaymentUrl, isPayrailsStaticHost, payrailsKeyRefusal, payrailsPaymentRefusal, payrailsRequestKind, payrailsSaveRefusal, } from './payrails.js';
|
|
82
83
|
import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone.js';
|
|
83
84
|
import { isPaymentHighwayCardLookup, isPaymentHighwayHost, isPaytrailCardMethodUrl, isPaytrailEntry, isPaytrailServed } from './paytrail.js';
|
|
@@ -86,9 +87,14 @@ import { isArmedTransbankUrl } from './transbank.js';
|
|
|
86
87
|
import { isRedsysEntry, isRedsysHost, redsysEntryUrl } from './redsys.js';
|
|
87
88
|
import { isArmedPrzelewy24Url, isPrzelewy24PayStep } from './przelewy24.js';
|
|
88
89
|
import { isArmedBarionUrl, isBarionPayStep } from './barion.js';
|
|
90
|
+
import { isArmedPayplugUrl, isPayplugPayStep } from './payplug.js';
|
|
91
|
+
import { isArmedCardlinkVposUrl, isCardlinkVposEntry } from './cardlink-vpos.js';
|
|
92
|
+
import { isArmedEghlUrl, isEghlPayStep } from './eghl.js';
|
|
89
93
|
import { isQuickpayCardLookup, isQuickpayEntry, quickpayEntryUrl } from './quickpay.js';
|
|
90
94
|
import { isArmedComgateUrl } from './comgate.js';
|
|
91
95
|
import { isArmedEximbayUrl, isEximbayEntry } from './eximbay.js';
|
|
96
|
+
import { isArmedOceanpaymentUrl, isOceanpaymentSendPay } from './oceanpayment.js';
|
|
97
|
+
import { aurusPreClaim } from './aurus.js';
|
|
92
98
|
import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
|
|
93
99
|
import { PAYPAL_CARD_FIELDS_REASON, PAYPAL_GUEST_CHECKOUT_REASON, isPaypalCardFieldsConfirm, isPaypalGraphql, paypalCardFieldsBody, paypalGraphqlCard, paypalGraphqlNamesCardOperation, } from './paypal-card-forms.js';
|
|
94
100
|
import { CYBERSOURCE_SECURE_ACCEPTANCE_REASON, CYBERSOURCE_SECURE_ACCEPTANCE_RECIPIENT, cybersourceSecureAcceptanceRequest, isCybersourceSecureAcceptanceUrl, isSecureAcceptanceFramePost, } from './cybersource-secure-acceptance.js';
|
|
@@ -287,6 +293,38 @@ async function cardEndpointUnsupported(ctx, profile, reviewed) {
|
|
|
287
293
|
await ctx.effects.abort();
|
|
288
294
|
return { kind: 'abort' };
|
|
289
295
|
}
|
|
296
|
+
// Nike's order after its card request (nike-order.ts, payment-core merchant-card-endpoint-nike-order.js), Felipe's amount
|
|
297
|
+
// rule C: the one request off a profile's endpoint the SDK holds. Once the adapters asked the cardholder to pay a card
|
|
298
|
+
// request through Nike's profile on this attachment, every write to Nike's API host is judged here. One reviewed order
|
|
299
|
+
// whose total and currency are the approved amount continues with its body unchanged, and only while the checkout still
|
|
300
|
+
// names that approval as under way (followUpMaySend); after it, only a byte-identical retry does. An order that fails the
|
|
301
|
+
// rule aborts, so Nike never authorizes the card, and the checkout ends failed with nothing charged
|
|
302
|
+
// (lifecycle.merchantOrderRefused); so does every order while no approved amount was read, and every later order under
|
|
303
|
+
// that approval. Another order after the one let go, or one the checkout no longer names as under way, aborts and leaves
|
|
304
|
+
// the checkout's state as it is. Anything else, every request of an attachment where no card went through Nike's
|
|
305
|
+
// profile included, defers. The adapters also run nikeOrderGate on a request paused at the browser target.
|
|
306
|
+
const nikeOrderPreClaim = (ctx) => nikeOrderGate(ctx);
|
|
307
|
+
/** The Nike order gate's decision, already performed, or undefined when the request is not its to judge. */
|
|
308
|
+
export function nikeOrderGate(ctx) {
|
|
309
|
+
const verdict = ctx.nikeOrders?.judge(ctx.request.url, ctx.request.method, () => ctx.request.body, (authorizationId) => followUpMaySend(ctx, { authorizationId })) ?? null;
|
|
310
|
+
if (!verdict)
|
|
311
|
+
return undefined;
|
|
312
|
+
return verdict.refusal === null ? nikeOrderReleased(ctx, verdict.authorizationId) : nikeOrderRefused(ctx, verdict.authorizationId, verdict.refusal);
|
|
313
|
+
}
|
|
314
|
+
// The refusals that stop one order and leave the checkout's state as it is.
|
|
315
|
+
const NIKE_ORDER_STOPS = new Set(['another_order', 'approval_not_current', 'earlier_order_refused']);
|
|
316
|
+
async function nikeOrderReleased(ctx, authorizationId) {
|
|
317
|
+
ctx.onEvent?.({ type: 'merchant_order_released', detail: { authorizationId, ...NIKE_ORDER_DETAIL } });
|
|
318
|
+
await ctx.effects.continueRequest().catch(() => { });
|
|
319
|
+
return { kind: 'continue' };
|
|
320
|
+
}
|
|
321
|
+
async function nikeOrderRefused(ctx, authorizationId, reason) {
|
|
322
|
+
if (!NIKE_ORDER_STOPS.has(reason))
|
|
323
|
+
ctx.lifecycle.merchantOrderRefused(`merchant_order_${reason}`);
|
|
324
|
+
ctx.onEvent?.({ type: 'merchant_order_refused', detail: { authorizationId, ...NIKE_ORDER_DETAIL, reason } });
|
|
325
|
+
await ctx.effects.abort();
|
|
326
|
+
return { kind: 'abort' };
|
|
327
|
+
}
|
|
290
328
|
// Mercado Pago's native Checkout Pro shares one origin between its BIN-lookup
|
|
291
329
|
// configuration and its card association. A request that is not for that origin
|
|
292
330
|
// defers synchronously; a POST to it is handled asynchronously (a document read
|
|
@@ -1034,6 +1072,12 @@ async function pelecardBlock(ctx, reason) {
|
|
|
1034
1072
|
// Everything else on ACI's hosts (the widget script, /bins, /v1/collect, GETs)
|
|
1035
1073
|
// is not a POST to a checkout and defers. Synchronous for the card-number
|
|
1036
1074
|
// request, so it pauses in the microtask it paused in.
|
|
1075
|
+
// A JSON split card the registry serves (aci.ts) takes the same path, keyed by
|
|
1076
|
+
// the URL its legs share: its card request defers to the arbiter unless that URL
|
|
1077
|
+
// already has one answered, or one running with no approval open
|
|
1078
|
+
// (split_card_reused), its code and expiry requests are held, a holder request
|
|
1079
|
+
// continues, and anything else is refused with the processor's code or
|
|
1080
|
+
// split_card_leg_unsupported. It has no /payment.
|
|
1037
1081
|
const aciPreClaim = (ctx) => {
|
|
1038
1082
|
const leg = ctx.aci?.classify(ctx.request.url, ctx.request.method, ctx.request.body);
|
|
1039
1083
|
if (!leg)
|
|
@@ -1050,8 +1094,15 @@ const aciPreClaim = (ctx) => {
|
|
|
1050
1094
|
return aciPayment(ctx);
|
|
1051
1095
|
// The card-number request: the arbiter pauses it for the vault claim flow,
|
|
1052
1096
|
// unless this checkout already has one running or answered (AciGate.begin).
|
|
1053
|
-
|
|
1054
|
-
|
|
1097
|
+
// While a JSON split card's approval is still open, its card request again is
|
|
1098
|
+
// the page's retry of the one it gave up on: the adapters bind that retry to
|
|
1099
|
+
// the open approval, or refuse it there as a second purchase, so the page's
|
|
1100
|
+
// retry gets the late approval's answer instead of a refusal here.
|
|
1101
|
+
if (ctx.aci.reused(leg)) {
|
|
1102
|
+
if ('group' in leg && ctx.awaitingApproval())
|
|
1103
|
+
return undefined;
|
|
1104
|
+
return aciRefuse(ctx, 'group' in leg ? SPLIT_CARD_REUSED : ACI_CHECKOUT_REUSED);
|
|
1105
|
+
}
|
|
1055
1106
|
return undefined;
|
|
1056
1107
|
};
|
|
1057
1108
|
async function aciContinue(ctx) {
|
|
@@ -1059,16 +1110,17 @@ async function aciContinue(ctx) {
|
|
|
1059
1110
|
return { kind: 'continue' };
|
|
1060
1111
|
}
|
|
1061
1112
|
async function aciHold(ctx, leg) {
|
|
1113
|
+
const prefix = 'group' in leg ? 'split_card' : 'aci';
|
|
1062
1114
|
const answer = await ctx.aci.hold(leg);
|
|
1063
1115
|
if (answer && !ctx.interceptionAborted() && !ctx.lifecycleAborted()) {
|
|
1064
1116
|
try {
|
|
1065
1117
|
await ctx.effects.answer(answer);
|
|
1066
|
-
ctx.onEvent?.({ type:
|
|
1118
|
+
ctx.onEvent?.({ type: `${prefix}_leg_absorbed`, detail: { url: redactUrl(ctx.request.url) } });
|
|
1067
1119
|
return { kind: 'answer' };
|
|
1068
1120
|
}
|
|
1069
1121
|
catch { /* the page's request is gone; nothing was forwarded */ }
|
|
1070
1122
|
}
|
|
1071
|
-
ctx.onEvent?.({ type: 'blocked', detail:
|
|
1123
|
+
ctx.onEvent?.({ type: 'blocked', detail: `${prefix}_leg_unanswered` });
|
|
1072
1124
|
await ctx.effects.abort();
|
|
1073
1125
|
return { kind: 'abort' };
|
|
1074
1126
|
}
|
|
@@ -1680,6 +1732,61 @@ export const barionPreClaim = (ctx) => {
|
|
|
1680
1732
|
return undefined;
|
|
1681
1733
|
return processorPageAbort(ctx);
|
|
1682
1734
|
};
|
|
1735
|
+
// PayPlug's payment page (payplug.ts). Once this build's registry serves PayPlug's entry (a capability the API serves
|
|
1736
|
+
// only to Agentcard's own company), every request to PayPlug's page host pauses. The merchant's redirect to the page,
|
|
1737
|
+
// a GET of /pay/<payment id> or /pay/test/<payment id> exactly, is no step of this gate's: it defers, and the terminal
|
|
1738
|
+
// arbiter pauses it for the claim flow like any card request, so the cardholder's device pays it. Every step toward
|
|
1739
|
+
// paying on the page itself aborts as an unsupported checkout: every POST to the page's host, the card post among
|
|
1740
|
+
// them. So PayPlug never sees the card the agent typed. The page's own GETs, its scripts, styles, images and frames,
|
|
1741
|
+
// continue. With PayPlug not served, the gate defers, and every request continues as before. Synchronous until it must
|
|
1742
|
+
// abort, like every gate. Exported for the gate registry's tests.
|
|
1743
|
+
export const payplugPreClaim = (ctx) => {
|
|
1744
|
+
const { url, method } = ctx.request;
|
|
1745
|
+
if (!isPayplugPayStep(url, method) || !isArmedPayplugUrl(ctx.vault, url))
|
|
1746
|
+
return undefined;
|
|
1747
|
+
return processorPageAbort(ctx);
|
|
1748
|
+
};
|
|
1749
|
+
// eGHL's payment page (eghl.ts). Once this build's registry serves eGHL's entry (a capability the API serves only to
|
|
1750
|
+
// Agentcard's own company), every request to eGHL's hosts pauses. The merchant's entry, a GET of /IPG/payment.aspx whose
|
|
1751
|
+
// query names the payment, is no step of this gate's: it defers, and the terminal arbiter pauses it for the claim flow
|
|
1752
|
+
// like any card request, so the cardholder's device pays it. Every step toward paying on the page itself aborts as an
|
|
1753
|
+
// unsupported checkout: every POST to eGHL's hosts, the card page's card post to the entry's own address among them. So
|
|
1754
|
+
// eGHL never sees the card the agent typed. The page's own GETs continue. With eGHL not served, the gate defers, and
|
|
1755
|
+
// every request continues as before. Synchronous until it must abort, like every gate. Exported for the gate registry's
|
|
1756
|
+
// tests.
|
|
1757
|
+
export const eghlPreClaim = (ctx) => {
|
|
1758
|
+
const { url, method } = ctx.request;
|
|
1759
|
+
if (!isEghlPayStep(url, method) || !isArmedEghlUrl(ctx.vault, url))
|
|
1760
|
+
return undefined;
|
|
1761
|
+
return processorPageAbort(ctx);
|
|
1762
|
+
};
|
|
1763
|
+
// Cardlink's VPOS (cardlink-vpos.ts). Once this build's registry serves the entry (a capability the API serves only to
|
|
1764
|
+
// Agentcard's own company), every request to the VPOS's hosts pauses, and this gate judges the POSTs:
|
|
1765
|
+
// - the merchant's entry, an entry body posted to /vpos/shophandlermpi on one of the three live hosts, defers, and the
|
|
1766
|
+
// terminal arbiter pauses it for the claim flow like any card request: the cardholder's device pays it;
|
|
1767
|
+
// - every other POST (the card page's post and its card check, which share the entry's URL and carry the page's own
|
|
1768
|
+
// fields, a body the adapter could not read, and anything on Cardlink's test hosts) aborts as an unsupported
|
|
1769
|
+
// checkout, so the VPOS never sees the card the agent typed.
|
|
1770
|
+
// A GET (the card page's script, its status poll) is not this gate's and continues. With the lane not served, the gate
|
|
1771
|
+
// defers, and every request continues as before. Synchronous until it must abort, like every gate. Exported for the gate
|
|
1772
|
+
// registry's tests.
|
|
1773
|
+
export const cardlinkVposPreClaim = (ctx) => {
|
|
1774
|
+
const { url, method } = ctx.request;
|
|
1775
|
+
if (String(method).toUpperCase() !== 'POST' || !isArmedCardlinkVposUrl(ctx.vault, url))
|
|
1776
|
+
return undefined;
|
|
1777
|
+
// The body is a getter an adapter can fail in (Playwright's postData() throws on a body it cannot decode): a body that
|
|
1778
|
+
// cannot be read is no entry this gate can tell from a card post, so it fails closed, aborted and reported like one.
|
|
1779
|
+
let body;
|
|
1780
|
+
try {
|
|
1781
|
+
body = ctx.request.body;
|
|
1782
|
+
}
|
|
1783
|
+
catch {
|
|
1784
|
+
return processorPageAbort(ctx);
|
|
1785
|
+
}
|
|
1786
|
+
if (isCardlinkVposEntry(url, method, body))
|
|
1787
|
+
return undefined;
|
|
1788
|
+
return processorPageAbort(ctx);
|
|
1789
|
+
};
|
|
1683
1790
|
// NMI's hosted payment page (ekashu.ts). Once this build's registry serves NMI's entry (a
|
|
1684
1791
|
// capability the API serves only to a company Agentcard turned NMI on for), every request to
|
|
1685
1792
|
// NMI's two hosts pauses, and this gate judges the POSTs:
|
|
@@ -1844,6 +1951,21 @@ export const eximbayPreClaim = (ctx) => {
|
|
|
1844
1951
|
return undefined;
|
|
1845
1952
|
return processorPageAbort(ctx);
|
|
1846
1953
|
};
|
|
1954
|
+
// Oceanpayment's hosted payment page (oceanpayment.ts). Once this build's registry serves Oceanpayment's card post (a
|
|
1955
|
+
// capability the API serves only to the company the gate turns Oceanpayment on for), every request to its two hosts
|
|
1956
|
+
// pauses, and this gate judges the ones that are not a GET:
|
|
1957
|
+
// - the page's card post, exactly as its form action writes it, defers, and the terminal arbiter pauses it for the
|
|
1958
|
+
// claim flow like any card request: the cardholder's device pays it;
|
|
1959
|
+
// - every other one (Cardinal's device-data call, which carries the typed number, a card post of another spelling, the
|
|
1960
|
+
// page's cancel) aborts as an unsupported checkout, so Oceanpayment never sees the card the agent typed.
|
|
1961
|
+
// A GET (the page, its scripts, its log) is not this gate's and continues. With Oceanpayment not served, the gate defers
|
|
1962
|
+
// and every request continues as before. Synchronous until it must abort, like every gate. Exported for its tests.
|
|
1963
|
+
export const oceanpaymentPreClaim = (ctx) => {
|
|
1964
|
+
const { url, method } = ctx.request;
|
|
1965
|
+
if (String(method).toUpperCase() === 'GET' || !isArmedOceanpaymentUrl(ctx.vault, url) || isOceanpaymentSendPay(url, method))
|
|
1966
|
+
return undefined;
|
|
1967
|
+
return processorPageAbort(ctx);
|
|
1968
|
+
};
|
|
1847
1969
|
// PayPal's legacy Hosted Fields (paypal-hosted-fields.ts). Its tokenization frame posts the card the
|
|
1848
1970
|
// agent typed to the Orders v2 confirm-payment-source on cors.api.paypal.com (or its sandbox), which
|
|
1849
1971
|
// no Vault mode takes over, so every company's adapters hand each POST there to this gate:
|
|
@@ -2499,8 +2621,9 @@ async function unsupportedContinue(ctx) {
|
|
|
2499
2621
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
2500
2622
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
2501
2623
|
* pauses each one itself. */
|
|
2502
|
-
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, tokenexIframePreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, sbpsPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, quickpayPreClaim, comgatePreClaim, przelewy24PreClaim, transbankPreClaim, eximbayPreClaim, barionPreClaim, paypalHostedFieldsPreClaim,
|
|
2503
|
-
paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim, payrexxPreClaim, hipayPreClaim, paytrailPreClaim
|
|
2624
|
+
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, nikeOrderPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, tokenexIframePreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, sbpsPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, quickpayPreClaim, comgatePreClaim, przelewy24PreClaim, transbankPreClaim, eximbayPreClaim, barionPreClaim, payplugPreClaim, cardlinkVposPreClaim, eghlPreClaim, paypalHostedFieldsPreClaim,
|
|
2625
|
+
paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim, payrexxPreClaim, hipayPreClaim, paytrailPreClaim,
|
|
2626
|
+
oceanpaymentPreClaim, aurusPreClaim];
|
|
2504
2627
|
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
2505
2628
|
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
2506
2629
|
* Returns synchronously while every gate defers synchronously (an ordinary card
|
|
@@ -1193,10 +1193,6 @@ export declare const CHECKOUT_PREFLIGHT_CAPABILITIES: {
|
|
|
1193
1193
|
readonly psp: "vgs";
|
|
1194
1194
|
readonly reason: "iframe_bound_tokenization";
|
|
1195
1195
|
readonly action: "human_checkout";
|
|
1196
|
-
}, {
|
|
1197
|
-
readonly psp: "bluesnap";
|
|
1198
|
-
readonly reason: "multi_request_tokenization_not_supported";
|
|
1199
|
-
readonly action: "human_checkout";
|
|
1200
1196
|
}, {
|
|
1201
1197
|
readonly psp: "stripe";
|
|
1202
1198
|
readonly reason: "unbound_token_to_payment_intent_continuation";
|
|
@@ -1840,11 +1840,6 @@ export const CHECKOUT_PREFLIGHT_CAPABILITIES = {
|
|
|
1840
1840
|
"reason": "iframe_bound_tokenization",
|
|
1841
1841
|
"action": "human_checkout"
|
|
1842
1842
|
},
|
|
1843
|
-
{
|
|
1844
|
-
"psp": "bluesnap",
|
|
1845
|
-
"reason": "multi_request_tokenization_not_supported",
|
|
1846
|
-
"action": "human_checkout"
|
|
1847
|
-
},
|
|
1848
1843
|
{
|
|
1849
1844
|
"psp": "stripe",
|
|
1850
1845
|
"reason": "unbound_token_to_payment_intent_continuation",
|
|
@@ -211,7 +211,26 @@
|
|
|
211
211
|
"origin": "https://js.braintreegateway.com",
|
|
212
212
|
"path_pattern": "/web/:version/js/*",
|
|
213
213
|
"hostname_pattern": "^js\\.braintreegateway\\.com$",
|
|
214
|
-
"pathname_pattern": "^\\/web\\/(?![^/]*-payments-sdk)[^/]+\\/js\\/",
|
|
214
|
+
"pathname_pattern": "^\\/web\\/(?![^/]*-payments-sdk)[^/]+\\/js\\/(?!(?:client|paypal|paypal-checkout|venmo|apple-pay|google-payment|data-collector|local-payment)(?:\\.min)?\\.js$)",
|
|
215
|
+
"sources": [
|
|
216
|
+
{
|
|
217
|
+
"kind": "official_documentation",
|
|
218
|
+
"reference": "https://developer.paypal.com/braintree/docs/start/hello-client/javascript/v3/",
|
|
219
|
+
"reviewed_on": "2026-09-11"
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "braintree-web-wallet",
|
|
225
|
+
"psp": "braintree",
|
|
226
|
+
"kinds": [
|
|
227
|
+
"script"
|
|
228
|
+
],
|
|
229
|
+
"origin": "https://js.braintreegateway.com",
|
|
230
|
+
"path_pattern": "/web/:version/js/{client,paypal,paypal-checkout,venmo,apple-pay,google-payment,data-collector,local-payment}[.min].js",
|
|
231
|
+
"card_free": "braintree-web's client, wallet and fraud components collect no card; Hosted Fields and Drop-in do.",
|
|
232
|
+
"hostname_pattern": "^js\\.braintreegateway\\.com$",
|
|
233
|
+
"pathname_pattern": "^\\/web\\/(?![^/]*-payments-sdk)[^/]+\\/js\\/(?:client|paypal|paypal-checkout|venmo|apple-pay|google-payment|data-collector|local-payment)(?:\\.min)?\\.js$",
|
|
215
234
|
"sources": [
|
|
216
235
|
{
|
|
217
236
|
"kind": "official_documentation",
|
|
@@ -2139,6 +2158,52 @@
|
|
|
2139
2158
|
"hostname_pattern": "^cdn\\.sandbox\\.[a-z0-9-]+\\.gr4vy\\.app$",
|
|
2140
2159
|
"pathname_pattern": "^\\/secure-fields\\/(?:latest|[0-9]{1,3}(?:\\.[0-9]{1,3}){0,2})\\/secure-fields\\.js$"
|
|
2141
2160
|
},
|
|
2161
|
+
{
|
|
2162
|
+
"id": "bambora-na-custom-checkout-fields",
|
|
2163
|
+
"psp": "bambora",
|
|
2164
|
+
"kinds": [
|
|
2165
|
+
"frame",
|
|
2166
|
+
"iframe",
|
|
2167
|
+
"document"
|
|
2168
|
+
],
|
|
2169
|
+
"origin": "https://libs.na.bambora.com",
|
|
2170
|
+
"path_pattern": "/customcheckout/1.:minor.:patch{,-:build}/iframe.html",
|
|
2171
|
+
"sources": [
|
|
2172
|
+
{
|
|
2173
|
+
"kind": "primary_source",
|
|
2174
|
+
"reference": "https://docs.na.worldline-solutions.com/build-your-integration/checkout-form/custom-checkout/reference",
|
|
2175
|
+
"reviewed_on": "2026-09-11"
|
|
2176
|
+
},
|
|
2177
|
+
{
|
|
2178
|
+
"kind": "saved_observation",
|
|
2179
|
+
"reference": "catalog-psp-scan-2026-10-23:kent-ca-custom-checkout-1.0.2-card-fields",
|
|
2180
|
+
"reviewed_on": "2026-10-23"
|
|
2181
|
+
}
|
|
2182
|
+
],
|
|
2183
|
+
"hostname_pattern": "^libs\\.na\\.bambora\\.com$",
|
|
2184
|
+
"pathname_pattern": "^\\/customcheckout\\/1\\.[0-9]{1,3}\\.[0-9]{1,3}(?:-[0-9]{1,4})?\\/iframe\\.html$"
|
|
2185
|
+
},
|
|
2186
|
+
{
|
|
2187
|
+
"id": "worldpay-wpg-hosted-payment-page",
|
|
2188
|
+
"psp": "worldpay",
|
|
2189
|
+
"kinds": [
|
|
2190
|
+
"document",
|
|
2191
|
+
"frame",
|
|
2192
|
+
"iframe"
|
|
2193
|
+
],
|
|
2194
|
+
"origin": "https://payments.worldpay.com",
|
|
2195
|
+
"path_pattern": "/app/{hpp,hpp-iframe}/*",
|
|
2196
|
+
"sources": [
|
|
2197
|
+
{
|
|
2198
|
+
"kind": "saved_observation",
|
|
2199
|
+
"reference": "catalog-psp-scan-2026-10-23:razer-worldpay-hosted-payment-page",
|
|
2200
|
+
"reviewed_on": "2026-10-23"
|
|
2201
|
+
}
|
|
2202
|
+
],
|
|
2203
|
+
"variant_unverified": "The implemented Worldpay adapter covers Access Checkout card sessions; Worldpay hosted payment pages under payments.worldpay.com/app/hpp take the card on Worldpay's own page and are not admitted.",
|
|
2204
|
+
"hostname_pattern": "^payments\\.worldpay\\.com$",
|
|
2205
|
+
"pathname_pattern": "^\\/app\\/hpp(?:-iframe)?\\/"
|
|
2206
|
+
},
|
|
2142
2207
|
{
|
|
2143
2208
|
"id": "paypal-hosted-fields-payments-sdk",
|
|
2144
2209
|
"psp": "paypal",
|
|
@@ -2520,6 +2585,92 @@
|
|
|
2520
2585
|
"hostname_pattern": "^flex\\.cybersource\\.com$",
|
|
2521
2586
|
"pathname_pattern": "^\\/microform\\/bundle\\/v2\\.[0-9]+\\.[0-9]+\\/iframe\\.html$"
|
|
2522
2587
|
},
|
|
2588
|
+
{
|
|
2589
|
+
"id": "cybersource-unified-checkout-script",
|
|
2590
|
+
"psp": "cybersource_flex",
|
|
2591
|
+
"kinds": [
|
|
2592
|
+
"script"
|
|
2593
|
+
],
|
|
2594
|
+
"origin": "https://up.cybersource.com",
|
|
2595
|
+
"path_pattern": "/uc/v1/assets/:asset/UnifiedCheckout.js",
|
|
2596
|
+
"sources": [
|
|
2597
|
+
{
|
|
2598
|
+
"kind": "saved_observation",
|
|
2599
|
+
"reference": "napaonline-2026-10-31:unified-checkout-v1.8.2-mce-orc-ga-frames",
|
|
2600
|
+
"reviewed_on": "2026-10-31"
|
|
2601
|
+
},
|
|
2602
|
+
{
|
|
2603
|
+
"kind": "official_documentation",
|
|
2604
|
+
"reference": "https://developer.cybersource.com/docs/cybs/en-us/unified-checkout/developer/all/rest/unified-checkout/uc-intro.html",
|
|
2605
|
+
"reviewed_on": "2026-10-31"
|
|
2606
|
+
}
|
|
2607
|
+
],
|
|
2608
|
+
"hostname_pattern": "^up\\.cybersource\\.com$",
|
|
2609
|
+
"pathname_pattern": "^\\/uc\\/v1\\/assets\\/[A-Za-z0-9_-]{16,1024}\\/UnifiedCheckout\\.js$"
|
|
2610
|
+
},
|
|
2611
|
+
{
|
|
2612
|
+
"id": "cybersource-unified-checkout-frame",
|
|
2613
|
+
"psp": "cybersource_flex",
|
|
2614
|
+
"kinds": [
|
|
2615
|
+
"frame",
|
|
2616
|
+
"iframe",
|
|
2617
|
+
"document"
|
|
2618
|
+
],
|
|
2619
|
+
"origin": "https://up.cybersource.com",
|
|
2620
|
+
"path_pattern": "/uc/v1/assets/:asset/{mce,orc,ga}/*.html",
|
|
2621
|
+
"sources": [
|
|
2622
|
+
{
|
|
2623
|
+
"kind": "saved_observation",
|
|
2624
|
+
"reference": "napaonline-2026-10-31:unified-checkout-v1.8.2-mce-orc-ga-frames",
|
|
2625
|
+
"reviewed_on": "2026-10-31"
|
|
2626
|
+
},
|
|
2627
|
+
{
|
|
2628
|
+
"kind": "official_documentation",
|
|
2629
|
+
"reference": "https://developer.cybersource.com/docs/cybs/en-us/unified-checkout/developer/all/rest/unified-checkout/uc-intro.html",
|
|
2630
|
+
"reviewed_on": "2026-10-31"
|
|
2631
|
+
}
|
|
2632
|
+
],
|
|
2633
|
+
"hostname_pattern": "^up\\.cybersource\\.com$",
|
|
2634
|
+
"pathname_pattern": "^\\/uc\\/v1\\/assets\\/[A-Za-z0-9_-]{16,1024}\\/(?:mce\\/mce|orc\\/orc|ga\\/ga)\\.html$"
|
|
2635
|
+
},
|
|
2636
|
+
{
|
|
2637
|
+
"id": "aurus-ecom-iframe",
|
|
2638
|
+
"psp": "aurus",
|
|
2639
|
+
"kinds": [
|
|
2640
|
+
"frame",
|
|
2641
|
+
"iframe",
|
|
2642
|
+
"document"
|
|
2643
|
+
],
|
|
2644
|
+
"origin": "https://pecst01.aurusepay.com",
|
|
2645
|
+
"path_pattern": "/storeservices/ecom/getiframe",
|
|
2646
|
+
"sources": [
|
|
2647
|
+
{
|
|
2648
|
+
"kind": "saved_observation",
|
|
2649
|
+
"reference": "oreillyauto-2026-10-31:aurus-ecom-getiframe-oneTimeToken",
|
|
2650
|
+
"reviewed_on": "2026-10-31"
|
|
2651
|
+
}
|
|
2652
|
+
],
|
|
2653
|
+
"hostname_pattern": "^pecst[0-9]{2}\\.aurusepay\\.com$",
|
|
2654
|
+
"pathname_pattern": "^\\/storeservices\\/ecom\\/getiframe$"
|
|
2655
|
+
},
|
|
2656
|
+
{
|
|
2657
|
+
"id": "aurus-ecom-init-script",
|
|
2658
|
+
"psp": "aurus",
|
|
2659
|
+
"kinds": [
|
|
2660
|
+
"script"
|
|
2661
|
+
],
|
|
2662
|
+
"origin": "https://pecst01.aurusepay.com",
|
|
2663
|
+
"path_pattern": "/storeservices/ecom/ecominitjs",
|
|
2664
|
+
"sources": [
|
|
2665
|
+
{
|
|
2666
|
+
"kind": "saved_observation",
|
|
2667
|
+
"reference": "oreillyauto-2026-10-31:aurus-ecom-getiframe-oneTimeToken",
|
|
2668
|
+
"reviewed_on": "2026-10-31"
|
|
2669
|
+
}
|
|
2670
|
+
],
|
|
2671
|
+
"hostname_pattern": "^pecst[0-9]{2}\\.aurusepay\\.com$",
|
|
2672
|
+
"pathname_pattern": "^\\/storeservices\\/ecom\\/ecominitjs$"
|
|
2673
|
+
},
|
|
2523
2674
|
{
|
|
2524
2675
|
"id": "juspay-hypercheckout-payment-page",
|
|
2525
2676
|
"psp": "juspay",
|
|
@@ -3519,6 +3670,54 @@
|
|
|
3519
3670
|
"hostname_pattern": "^testflex\\.cybersource\\.com$",
|
|
3520
3671
|
"pathname_pattern": "^\\/microform\\/bundle\\/v2\\.[0-9]+\\.[0-9]+\\/iframe\\.html$"
|
|
3521
3672
|
},
|
|
3673
|
+
{
|
|
3674
|
+
"id": "cybersource-unified-checkout-script-test",
|
|
3675
|
+
"psp": "cybersource_flex",
|
|
3676
|
+
"kinds": [
|
|
3677
|
+
"script"
|
|
3678
|
+
],
|
|
3679
|
+
"origin": "https://testup.cybersource.com",
|
|
3680
|
+
"path_pattern": "/uc/v1/assets/:asset/UnifiedCheckout.js",
|
|
3681
|
+
"sources": [
|
|
3682
|
+
{
|
|
3683
|
+
"kind": "saved_observation",
|
|
3684
|
+
"reference": "napaonline-2026-10-31:unified-checkout-v1.8.2-mce-orc-ga-frames",
|
|
3685
|
+
"reviewed_on": "2026-10-31"
|
|
3686
|
+
},
|
|
3687
|
+
{
|
|
3688
|
+
"kind": "official_documentation",
|
|
3689
|
+
"reference": "https://developer.cybersource.com/docs/cybs/en-us/unified-checkout/developer/all/rest/unified-checkout/uc-intro.html",
|
|
3690
|
+
"reviewed_on": "2026-10-31"
|
|
3691
|
+
}
|
|
3692
|
+
],
|
|
3693
|
+
"hostname_pattern": "^testup\\.cybersource\\.com$",
|
|
3694
|
+
"pathname_pattern": "^\\/uc\\/v1\\/assets\\/[A-Za-z0-9_-]{16,1024}\\/UnifiedCheckout\\.js$"
|
|
3695
|
+
},
|
|
3696
|
+
{
|
|
3697
|
+
"id": "cybersource-unified-checkout-frame-test",
|
|
3698
|
+
"psp": "cybersource_flex",
|
|
3699
|
+
"kinds": [
|
|
3700
|
+
"frame",
|
|
3701
|
+
"iframe",
|
|
3702
|
+
"document"
|
|
3703
|
+
],
|
|
3704
|
+
"origin": "https://testup.cybersource.com",
|
|
3705
|
+
"path_pattern": "/uc/v1/assets/:asset/{mce,orc,ga}/*.html",
|
|
3706
|
+
"sources": [
|
|
3707
|
+
{
|
|
3708
|
+
"kind": "saved_observation",
|
|
3709
|
+
"reference": "napaonline-2026-10-31:unified-checkout-v1.8.2-mce-orc-ga-frames",
|
|
3710
|
+
"reviewed_on": "2026-10-31"
|
|
3711
|
+
},
|
|
3712
|
+
{
|
|
3713
|
+
"kind": "official_documentation",
|
|
3714
|
+
"reference": "https://developer.cybersource.com/docs/cybs/en-us/unified-checkout/developer/all/rest/unified-checkout/uc-intro.html",
|
|
3715
|
+
"reviewed_on": "2026-10-31"
|
|
3716
|
+
}
|
|
3717
|
+
],
|
|
3718
|
+
"hostname_pattern": "^testup\\.cybersource\\.com$",
|
|
3719
|
+
"pathname_pattern": "^\\/uc\\/v1\\/assets\\/[A-Za-z0-9_-]{16,1024}\\/(?:mce\\/mce|orc\\/orc|ga\\/ga)\\.html$"
|
|
3720
|
+
},
|
|
3522
3721
|
{
|
|
3523
3722
|
"id": "cybersource-secure-acceptance-silent-order-post-test",
|
|
3524
3723
|
"psp": "cybersource_secure_acceptance",
|
|
@@ -6958,11 +7157,6 @@
|
|
|
6958
7157
|
"reason": "iframe_bound_tokenization",
|
|
6959
7158
|
"action": "human_checkout"
|
|
6960
7159
|
},
|
|
6961
|
-
{
|
|
6962
|
-
"psp": "bluesnap",
|
|
6963
|
-
"reason": "multi_request_tokenization_not_supported",
|
|
6964
|
-
"action": "human_checkout"
|
|
6965
|
-
},
|
|
6966
7160
|
{
|
|
6967
7161
|
"psp": "stripe",
|
|
6968
7162
|
"reason": "unbound_token_to_payment_intent_continuation",
|
|
@@ -20,9 +20,9 @@ export interface CheckoutCollectionOptions {
|
|
|
20
20
|
timeoutMs?: number;
|
|
21
21
|
/** Maximum frame count. Default and maximum 64. */
|
|
22
22
|
maxFrames?: number;
|
|
23
|
-
/** Maximum document and asset observations. Default and maximum 2,048. */
|
|
23
|
+
/** Maximum document and asset observations. Default and maximum 2,048. An asset no reviewed rule can match is skipped and not counted. */
|
|
24
24
|
maxSignals?: number;
|
|
25
|
-
/** Maximum transient observation and returned snapshot size. Default and maximum 262,144 bytes; minimum 512 bytes. */
|
|
25
|
+
/** Maximum transient observation and returned snapshot size. Default and maximum 262,144 bytes; minimum 512 bytes. Skipped assets cost nothing. */
|
|
26
26
|
maxSnapshotBytes?: number;
|
|
27
27
|
}
|
|
28
28
|
export type InspectCheckoutOptions = CheckoutPreflightOptions & {
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { normalizeCheckoutSignals, assessCheckoutSupport, cardEntryAttributable, } from './preflight.js';
|
|
1
|
+
import { normalizeCheckoutSignals, assessCheckoutSupport, cardEntryAttributable, getCheckoutPreflightCatalog, CHECKOUT_PREFLIGHT_LIMITS, } from './preflight.js';
|
|
2
2
|
const EVENTS = ['framenavigated', 'frameattached', 'framedetached'];
|
|
3
3
|
const DEADLINE = Symbol('checkout inspection deadline');
|
|
4
4
|
const DEFAULTS = { timeoutMs: 1_500, maxFrames: 64, maxSignals: 2_048, maxSnapshotBytes: 262_144 };
|
|
5
5
|
/** Frames at one nesting depth are read together. A remote CDP connection pays per round trip, not per frame. */
|
|
6
6
|
const CONCURRENCY = 8;
|
|
7
|
+
/** Script, iframe and form elements one frame's read examines, skipped ones included, so a page cannot keep the read walking. */
|
|
8
|
+
const ELEMENT_LIMIT = 16_384;
|
|
7
9
|
const encoder = new TextEncoder();
|
|
10
|
+
let reviewedHosts;
|
|
11
|
+
/** Hostname patterns of the reviewed rules. A URL on any other host can never become evidence. */
|
|
12
|
+
const reviewedHostPatterns = () => reviewedHosts ??= [...new Set(getCheckoutPreflightCatalog().rules.map(rule => rule.hostname_pattern))];
|
|
8
13
|
function bounded(value, fallback, maximum, minimum = 1) {
|
|
9
14
|
if (value === undefined)
|
|
10
15
|
return fallback;
|
|
@@ -185,7 +190,7 @@ export async function collectCheckoutSignals(page, options = {}) {
|
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
visibility.set(frame, visible);
|
|
188
|
-
const data = await beforeDeadline(() => frame.evaluate(({
|
|
193
|
+
const data = await beforeDeadline(() => frame.evaluate(({ signalRoom, byteBudget, hosts, elementLimit, urlLength, rawUrlLength }) => {
|
|
189
194
|
const collected = [];
|
|
190
195
|
let bytes = 0;
|
|
191
196
|
let signalLimit = false;
|
|
@@ -203,9 +208,37 @@ export async function collectCheckoutSignals(page, options = {}) {
|
|
|
203
208
|
const rect = element.getBoundingClientRect();
|
|
204
209
|
return rect.width > 0 && rect.height > 0;
|
|
205
210
|
};
|
|
211
|
+
// The normalizer drops a URL short enough to match without a trace when it is not https, carries credentials
|
|
212
|
+
// or a port, or sits on a host no reviewed rule names (preflight.ts). Such a URL can never become evidence,
|
|
213
|
+
// so the read skips it before it costs any signal or byte budget, and a store's own scripts (hundreds on a
|
|
214
|
+
// RequireJS checkout) no longer spend the budget before the read reaches the card frames. A URL kept here is
|
|
215
|
+
// still matched against the reviewed rules by the normalizer, so the page cannot add evidence this way. The
|
|
216
|
+
// patterns are tested case-insensitively: a hostname is lowercase, so this keeps every host a rule can match.
|
|
217
|
+
const reviewed = hosts.map(source => new RegExp(source, 'i'));
|
|
218
|
+
const named = new Map();
|
|
219
|
+
const silent = (url) => {
|
|
220
|
+
if (url.href.length > rawUrlLength || url.origin.length + url.pathname.length > urlLength)
|
|
221
|
+
return false;
|
|
222
|
+
if (url.protocol !== 'https:' || url.username || url.password || url.port)
|
|
223
|
+
return true;
|
|
224
|
+
let known = named.get(url.hostname);
|
|
225
|
+
if (known === undefined) {
|
|
226
|
+
known = reviewed.some(pattern => pattern.test(url.hostname));
|
|
227
|
+
named.set(url.hostname, known);
|
|
228
|
+
}
|
|
229
|
+
return !known;
|
|
230
|
+
};
|
|
231
|
+
const silentAddress = (address) => { try {
|
|
232
|
+
return silent(new URL(address));
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return false;
|
|
236
|
+
} };
|
|
237
|
+
// The frame's own address takes a signal slot only when it is appended, which a silent one never is.
|
|
238
|
+
const remaining = Math.max(0, signalRoom - (silentAddress(document.URL) ? 0 : 1));
|
|
206
239
|
const elements = document.querySelectorAll('script[src], iframe[src], form[action]');
|
|
207
240
|
for (let index = 0; index < elements.length; index++) {
|
|
208
|
-
if (index >=
|
|
241
|
+
if (index >= elementLimit) {
|
|
209
242
|
signalLimit = true;
|
|
210
243
|
break;
|
|
211
244
|
}
|
|
@@ -214,23 +247,29 @@ export async function collectCheckoutSignals(page, options = {}) {
|
|
|
214
247
|
const source = element.getAttribute(kind === 'form' ? 'action' : 'src');
|
|
215
248
|
if (!source)
|
|
216
249
|
continue;
|
|
250
|
+
let url = null;
|
|
251
|
+
try {
|
|
252
|
+
url = new URL(source, document.baseURI);
|
|
253
|
+
}
|
|
254
|
+
catch { /* an unparsable location is skipped below unless it is oversized */ }
|
|
255
|
+
if (url && silent(url))
|
|
256
|
+
continue;
|
|
217
257
|
if (source.length > byteBudget - bytes) {
|
|
218
258
|
byteLimit = true;
|
|
219
259
|
break;
|
|
220
260
|
}
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
url = new URL(source, document.baseURI).href;
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
261
|
+
if (!url)
|
|
226
262
|
continue;
|
|
263
|
+
if (collected.length >= remaining) {
|
|
264
|
+
signalLimit = true;
|
|
265
|
+
break;
|
|
227
266
|
}
|
|
228
|
-
bytes += url.length * 3 + 128;
|
|
267
|
+
bytes += url.href.length * 3 + 128;
|
|
229
268
|
if (bytes > byteBudget) {
|
|
230
269
|
byteLimit = true;
|
|
231
270
|
break;
|
|
232
271
|
}
|
|
233
|
-
collected.push({ kind, url, visible: kind === 'script' ? null : shown(element) });
|
|
272
|
+
collected.push({ kind, url: url.href, visible: kind === 'script' ? null : shown(element) });
|
|
234
273
|
}
|
|
235
274
|
// Card-entry detection reads field metadata only: the autocomplete token or the
|
|
236
275
|
// control's name and id. Values, labels and page text are never read.
|
|
@@ -248,11 +287,20 @@ export async function collectCheckoutSignals(page, options = {}) {
|
|
|
248
287
|
if (cardField && shown(input))
|
|
249
288
|
cardEntry = true;
|
|
250
289
|
}
|
|
290
|
+
// The frame's own address follows the same rule, so an ad or widget frame's long address is not charged
|
|
291
|
+
// either: it neither trips the byte budget nor is blanked. It still comes back whole, since the navigation
|
|
292
|
+
// and card-entry checks below need it, and a silent address is at most rawUrlLength characters long.
|
|
251
293
|
const documentUrl = document.URL;
|
|
252
|
-
|
|
294
|
+
const documentSilent = silentAddress(documentUrl);
|
|
295
|
+
const oversized = !documentSilent && documentUrl.length * 3 > byteBudget;
|
|
296
|
+
if (oversized)
|
|
253
297
|
byteLimit = true;
|
|
254
|
-
return { documentUrl:
|
|
255
|
-
}, {
|
|
298
|
+
return { documentUrl: oversized ? '' : documentUrl, documentSilent, collected, signalLimit, byteLimit, cardEntry };
|
|
299
|
+
}, {
|
|
300
|
+
signalRoom: Math.max(0, limits.maxSignals - signals.length), byteBudget: Math.max(0, limits.maxSnapshotBytes - transientBytes),
|
|
301
|
+
hosts: reviewedHostPatterns(), elementLimit: ELEMENT_LIMIT,
|
|
302
|
+
urlLength: CHECKOUT_PREFLIGHT_LIMITS.url_length, rawUrlLength: CHECKOUT_PREFLIGHT_LIMITS.raw_url_length,
|
|
303
|
+
}), deadline);
|
|
256
304
|
const releaseError = released ? await released : undefined;
|
|
257
305
|
if (releaseError !== undefined)
|
|
258
306
|
throw releaseError;
|
|
@@ -286,7 +334,8 @@ export async function collectCheckoutSignals(page, options = {}) {
|
|
|
286
334
|
if (!outcome)
|
|
287
335
|
continue;
|
|
288
336
|
const { data, frame_id, parent_frame_id, nested, visible } = outcome;
|
|
289
|
-
|
|
337
|
+
if (!data.documentSilent)
|
|
338
|
+
append({ kind: nested ? 'frame' : 'document', url: data.documentUrl, frame_id, parent_frame_id, visible });
|
|
290
339
|
for (const signal of data.collected) {
|
|
291
340
|
if (!append({ ...signal, frame_id, parent_frame_id, visible: visible === false ? false : signal.kind === 'script' ? visible : visible === null ? null : signal.visible }))
|
|
292
341
|
break;
|