@agent-cards/sdk 0.23.2 → 0.23.4
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 +16 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +17 -12
- package/dist/adyen-merchant-hosted.generated.js +134 -127
- package/dist/card-endpoint.d.ts +9 -1
- package/dist/card-endpoint.js +15 -2
- package/dist/cdp.js +19 -13
- package/dist/client.js +11 -8
- package/dist/lifecycle.d.ts +5 -3
- package/dist/lifecycle.js +4 -2
- package/dist/merchant-card-endpoints.generated.js +238 -136
- package/dist/merchant-hosted.js +2 -1
- package/dist/merchant-total.js +4 -1
- package/dist/paypal-hosted-fields.d.ts +52 -0
- package/dist/paypal-hosted-fields.js +80 -0
- package/dist/pre-claim.d.ts +1 -0
- package/dist/pre-claim.js +59 -6
- package/dist/preflight-catalog.json +37 -2
- package/dist/preflight-schemas.json +2 -0
- package/dist/preflight.generated.js +26 -4
- package/dist/substitutions.generated.js +134 -127
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +3 -3
package/dist/merchant-hosted.js
CHANGED
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* the agent's dummy card never reaches the merchant and nothing is charged;
|
|
18
18
|
* 'enabled' lets a prepared checkout pay it. The API can hold a profile in
|
|
19
19
|
* 'observe' for a client but never enable it past this build: a profile is
|
|
20
|
-
* 'enabled' only when both say so.
|
|
20
|
+
* 'enabled' only when both say so. Cinemark and Dick's own storefront ship
|
|
21
|
+
* 'enabled'; every other profile ships in 'observe'.
|
|
21
22
|
*/
|
|
22
23
|
import { MERCHANT_PROFILE_RECOGNIZERS as SHARED_PROFILES, classifyDeclaredMerchantRequest as sharedClassifyDeclared, declaredTemplatedUrl as sharedDeclaredTemplatedUrl, declaredUrlGlobs as sharedDeclaredUrlGlobs, declaredUrlRelation as sharedDeclaredUrlRelation, declaredEndpointRefusal as sharedDeclaredEndpointRefusal, classifyMerchantRequest as sharedClassify, merchantBodyKeyPathSha256 as sharedKeyPathSha256, merchantProfileEnvironment as sharedEnvironment, merchantProfileRulesMatch as sharedRulesMatch, merchantProfileTemplatedUrl as sharedTemplatedUrl, merchantProfileUrlGlobs as sharedUrlGlobs, merchantProfileUrlRelation as sharedUrlRelation, sha256Hex as sharedSha256Hex, } from './adyen-merchant-hosted.generated.js';
|
|
23
24
|
import { SubstitutionError, substituteMerchantHosted as sharedSubstitute } from './substitutions.generated.js';
|
package/dist/merchant-total.js
CHANGED
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
*
|
|
13
13
|
* What this proves: the total is what the agent's browser session saw. It catches a
|
|
14
14
|
* mistyped amount and a cart that changed under the agent; it cannot tell a merchant's
|
|
15
|
-
* response from one the agent wrote
|
|
15
|
+
* response from one the agent wrote. So a profile priced this way stays in observe unless
|
|
16
|
+
* Agentcard accepted that risk for it by name (Cinemark and Dick's own storefront, Oct 7,
|
|
17
|
+
* 2026), and then it pays only for the company Agentcard enables it for, and only after the
|
|
18
|
+
* cardholder approves each payment.
|
|
16
19
|
*
|
|
17
20
|
* What the SDK owes payment-core (the header of merchant-amount.js): every exchange of
|
|
18
21
|
* the profile's shapes, projected, with the time it left; a wait for any such request
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PayPal's legacy Hosted Fields, which this SDK refuses for every company.
|
|
3
|
+
*
|
|
4
|
+
* A merchant page that loads PayPal's JS SDK with `components=hosted-fields` (Planet Golf UK's X-Cart
|
|
5
|
+
* checkout, read live on 2026-10-06 with SDK 5.0.580) shows each card field as a braintree-web
|
|
6
|
+
* `-payments-sdk` frame on assets.braintreegateway.com. The agent types its placeholder card there, and
|
|
7
|
+
* at submit the 0x0 tokenization frame sends that card to PayPal as plaintext JSON:
|
|
8
|
+
*
|
|
9
|
+
* POST https://cors.api.paypal.com/v2/checkout/orders/<order id>/confirm-payment-source
|
|
10
|
+
* {"payment_source":{"card":{"number","expiry":"YYYY-MM","security_code","name","billing_address",
|
|
11
|
+
* "attributes":{"verification":{"method":"SCA_ALWAYS"}}}},"application_context":{"vault":false}}
|
|
12
|
+
*
|
|
13
|
+
* (cors.api.sandbox.paypal.com in PayPal's sandbox). No Vault mode takes this request over, so no
|
|
14
|
+
* company can pay it, and an SDK without this guard let the placeholder card reach PayPal, which
|
|
15
|
+
* declined it. Both adapters arm the confirm whatever the synced registry serves, and the pre-claim
|
|
16
|
+
* gate (paypalHostedFieldsPreClaim, pre-claim.ts) judges each POST to it:
|
|
17
|
+
* - a card number at payment_source.card.number: abort, so the confirm never carries the card to PayPal,
|
|
18
|
+
* and the checkout reads `unsupported` with reason `unsupported_paypal_hosted_fields` and holds, as
|
|
19
|
+
* every unsupported refusal does: another way to pay on the same page needs a new attachment;
|
|
20
|
+
* - no card number (a PayPal wallet, a saved card, a token): continue untouched;
|
|
21
|
+
* - a body the adapter could not read, an empty one, or one that is not a JSON object: abort that
|
|
22
|
+
* request alone, since nothing proves it carries no card (the frame always sends JSON).
|
|
23
|
+
* A confirm the synced registry recognizes as a card request belongs to that recognizer instead; no
|
|
24
|
+
* build serves one today. The frame's XHR reports the abort to the page at once (braintree-web's request
|
|
25
|
+
* reads readyState 4 with status 0 as an error), so Hosted Fields' submit fails instead of waiting out
|
|
26
|
+
* its 60 second limit.
|
|
27
|
+
*
|
|
28
|
+
* The confirm is the only request judged here. A merchant that passes installments handlers
|
|
29
|
+
* (onInstallmentsRequested and onInstallmentsAvailable) to HostedFields.render also has the number frame
|
|
30
|
+
* post each valid typed number to v1/credit/calculated-financing-options on the same hosts, as the agent
|
|
31
|
+
* types it (braintree-web 3.32.0-payments-sdk-dev, hosted-fields-input-frame). That lookup pays and
|
|
32
|
+
* declines nothing, and neither adapter intercepts it. Planet Golf UK's card funding is not installment
|
|
33
|
+
* eligible (funding eligibility read 2026-10-06).
|
|
34
|
+
*/
|
|
35
|
+
export declare const PAYPAL_HOSTED_FIELDS_REASON: "unsupported_paypal_hosted_fields";
|
|
36
|
+
/** The two hosts PayPal's JS SDK sends the Hosted Fields confirm to (getPayPalAPIDomain()). */
|
|
37
|
+
export declare const PAYPAL_HOSTED_FIELDS_HOSTS: readonly string[];
|
|
38
|
+
/**
|
|
39
|
+
* CDP Fetch patterns for the confirm on both hosts, armed on every attachment. A pattern matches the
|
|
40
|
+
* canonical URL Chrome sends, whose host is lower case, and its trailing `*` keeps a confirm with a
|
|
41
|
+
* query or a trailing slash paused. Only the confirm path is armed, never the whole host.
|
|
42
|
+
*/
|
|
43
|
+
export declare const PAYPAL_HOSTED_FIELDS_PATTERNS: readonly string[];
|
|
44
|
+
/** Whether this URL is the Hosted Fields confirm on one of PayPal's two hosts (https, standard port, no credentials). */
|
|
45
|
+
export declare function isPaypalHostedFieldsConfirm(url: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* What a confirm's body carries, read as PayPal reads it (the last of a repeated key wins in both):
|
|
48
|
+
* 'card' when payment_source.card.number holds anything but null or an empty string, 'no_card' for
|
|
49
|
+
* any other JSON object, and 'unreadable' for a body the adapter could not read (null), an empty
|
|
50
|
+
* one, or one that is not a JSON object.
|
|
51
|
+
*/
|
|
52
|
+
export declare function paypalHostedFieldsBody(body: string | null): 'card' | 'no_card' | 'unreadable';
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PayPal's legacy Hosted Fields, which this SDK refuses for every company.
|
|
3
|
+
*
|
|
4
|
+
* A merchant page that loads PayPal's JS SDK with `components=hosted-fields` (Planet Golf UK's X-Cart
|
|
5
|
+
* checkout, read live on 2026-10-06 with SDK 5.0.580) shows each card field as a braintree-web
|
|
6
|
+
* `-payments-sdk` frame on assets.braintreegateway.com. The agent types its placeholder card there, and
|
|
7
|
+
* at submit the 0x0 tokenization frame sends that card to PayPal as plaintext JSON:
|
|
8
|
+
*
|
|
9
|
+
* POST https://cors.api.paypal.com/v2/checkout/orders/<order id>/confirm-payment-source
|
|
10
|
+
* {"payment_source":{"card":{"number","expiry":"YYYY-MM","security_code","name","billing_address",
|
|
11
|
+
* "attributes":{"verification":{"method":"SCA_ALWAYS"}}}},"application_context":{"vault":false}}
|
|
12
|
+
*
|
|
13
|
+
* (cors.api.sandbox.paypal.com in PayPal's sandbox). No Vault mode takes this request over, so no
|
|
14
|
+
* company can pay it, and an SDK without this guard let the placeholder card reach PayPal, which
|
|
15
|
+
* declined it. Both adapters arm the confirm whatever the synced registry serves, and the pre-claim
|
|
16
|
+
* gate (paypalHostedFieldsPreClaim, pre-claim.ts) judges each POST to it:
|
|
17
|
+
* - a card number at payment_source.card.number: abort, so the confirm never carries the card to PayPal,
|
|
18
|
+
* and the checkout reads `unsupported` with reason `unsupported_paypal_hosted_fields` and holds, as
|
|
19
|
+
* every unsupported refusal does: another way to pay on the same page needs a new attachment;
|
|
20
|
+
* - no card number (a PayPal wallet, a saved card, a token): continue untouched;
|
|
21
|
+
* - a body the adapter could not read, an empty one, or one that is not a JSON object: abort that
|
|
22
|
+
* request alone, since nothing proves it carries no card (the frame always sends JSON).
|
|
23
|
+
* A confirm the synced registry recognizes as a card request belongs to that recognizer instead; no
|
|
24
|
+
* build serves one today. The frame's XHR reports the abort to the page at once (braintree-web's request
|
|
25
|
+
* reads readyState 4 with status 0 as an error), so Hosted Fields' submit fails instead of waiting out
|
|
26
|
+
* its 60 second limit.
|
|
27
|
+
*
|
|
28
|
+
* The confirm is the only request judged here. A merchant that passes installments handlers
|
|
29
|
+
* (onInstallmentsRequested and onInstallmentsAvailable) to HostedFields.render also has the number frame
|
|
30
|
+
* post each valid typed number to v1/credit/calculated-financing-options on the same hosts, as the agent
|
|
31
|
+
* types it (braintree-web 3.32.0-payments-sdk-dev, hosted-fields-input-frame). That lookup pays and
|
|
32
|
+
* declines nothing, and neither adapter intercepts it. Planet Golf UK's card funding is not installment
|
|
33
|
+
* eligible (funding eligibility read 2026-10-06).
|
|
34
|
+
*/
|
|
35
|
+
export const PAYPAL_HOSTED_FIELDS_REASON = 'unsupported_paypal_hosted_fields';
|
|
36
|
+
/** The two hosts PayPal's JS SDK sends the Hosted Fields confirm to (getPayPalAPIDomain()). */
|
|
37
|
+
export const PAYPAL_HOSTED_FIELDS_HOSTS = Object.freeze(['cors.api.paypal.com', 'cors.api.sandbox.paypal.com']);
|
|
38
|
+
const CONFIRM_PATH = /^\/v2\/checkout\/orders\/[^/]+\/confirm-payment-source\/?$/;
|
|
39
|
+
/**
|
|
40
|
+
* CDP Fetch patterns for the confirm on both hosts, armed on every attachment. A pattern matches the
|
|
41
|
+
* canonical URL Chrome sends, whose host is lower case, and its trailing `*` keeps a confirm with a
|
|
42
|
+
* query or a trailing slash paused. Only the confirm path is armed, never the whole host.
|
|
43
|
+
*/
|
|
44
|
+
export const PAYPAL_HOSTED_FIELDS_PATTERNS = Object.freeze(PAYPAL_HOSTED_FIELDS_HOSTS.map((host) => `https://${host}/v2/checkout/orders/*/confirm-payment-source*`));
|
|
45
|
+
/** Whether this URL is the Hosted Fields confirm on one of PayPal's two hosts (https, standard port, no credentials). */
|
|
46
|
+
export function isPaypalHostedFieldsConfirm(url) {
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = new URL(url);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return parsed.protocol === 'https:' && !parsed.port && !parsed.username && !parsed.password
|
|
55
|
+
&& PAYPAL_HOSTED_FIELDS_HOSTS.includes(parsed.hostname.toLowerCase()) && CONFIRM_PATH.test(parsed.pathname);
|
|
56
|
+
}
|
|
57
|
+
const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
58
|
+
/**
|
|
59
|
+
* What a confirm's body carries, read as PayPal reads it (the last of a repeated key wins in both):
|
|
60
|
+
* 'card' when payment_source.card.number holds anything but null or an empty string, 'no_card' for
|
|
61
|
+
* any other JSON object, and 'unreadable' for a body the adapter could not read (null), an empty
|
|
62
|
+
* one, or one that is not a JSON object.
|
|
63
|
+
*/
|
|
64
|
+
export function paypalHostedFieldsBody(body) {
|
|
65
|
+
if (typeof body !== 'string' || body === '')
|
|
66
|
+
return 'unreadable';
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(body);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return 'unreadable';
|
|
73
|
+
}
|
|
74
|
+
if (!record(parsed))
|
|
75
|
+
return 'unreadable';
|
|
76
|
+
const source = parsed.payment_source;
|
|
77
|
+
const card = record(source) ? source.card : undefined;
|
|
78
|
+
const number = record(card) ? card.number : undefined;
|
|
79
|
+
return number === undefined || number === null || number === '' ? 'no_card' : 'card';
|
|
80
|
+
}
|
package/dist/pre-claim.d.ts
CHANGED
|
@@ -200,6 +200,7 @@ export declare const MERCHANT_BODY_UNREADABLE = "the paused request carries no b
|
|
|
200
200
|
export declare const verifonePreClaim: PreClaimGate;
|
|
201
201
|
export declare const fiservPreClaim: PreClaimGate;
|
|
202
202
|
export declare const ekashuPreClaim: PreClaimGate;
|
|
203
|
+
export declare const paypalHostedFieldsPreClaim: PreClaimGate;
|
|
203
204
|
/** The processor gates, in order. A new family adds one entry here; the terminal
|
|
204
205
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
205
206
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
package/dist/pre-claim.js
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
// hosts also carry card payments the vault can never pay, and Elavon
|
|
15
15
|
// Converge's hpp/process URL, which also carries wallet, debit, follow-up and
|
|
16
16
|
// card-saving bodies, WSPay's confirm URL, which also carries bodies with
|
|
17
|
-
// no new card,
|
|
18
|
-
// requests the vault must never pay
|
|
17
|
+
// no new card, Shiji STS's getTokenId and getCvvId, which also carry
|
|
18
|
+
// requests the vault must never pay, and PayPal's legacy Hosted Fields
|
|
19
|
+
// confirm, whose card no Vault mode takes over.
|
|
19
20
|
// The first three used to be
|
|
20
21
|
// written inline in Fetch.requestPaused and again in the page.route handler, so
|
|
21
22
|
// a decision could drift between the two adapters and a new family had to be
|
|
@@ -73,6 +74,7 @@ import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoi
|
|
|
73
74
|
import { PAYRAILS_HOLDING_REASONS, PAYRAILS_SESSION_MESSAGE, isPayrailsHost, isPayrailsStaticHost, payrailsKeyRefusal, payrailsRequestKind, payrailsSaveRefusal, } from './payrails.js';
|
|
74
75
|
import { classifyVerifoneProfileRequest, verifoneTemplatedUrl } from './verifone.js';
|
|
75
76
|
import { ekashuEntryUrl, isEkashuEntry, isEkashuHost } from './ekashu.js';
|
|
77
|
+
import { PAYPAL_HOSTED_FIELDS_REASON, isPaypalHostedFieldsConfirm, paypalHostedFieldsBody } from './paypal-hosted-fields.js';
|
|
76
78
|
/**
|
|
77
79
|
* The URL an event may name for a request. For an armed merchant profile's
|
|
78
80
|
* endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
|
|
@@ -103,9 +105,15 @@ export function eventUrl(vault, url) {
|
|
|
103
105
|
// POST to exactly a profile's card request URL is held only when it carries a card
|
|
104
106
|
// (cardEndpointCarriesCard: anything at the profile's card number field, or a Luhn-valid
|
|
105
107
|
// number anywhere in the body). Several of these URLs also take requests with no card,
|
|
106
|
-
// such as an IHG stay paid with points, a
|
|
107
|
-
//
|
|
108
|
-
//
|
|
108
|
+
// such as an IHG stay paid with points, a saved card or a SynXis room guaranteed with
|
|
109
|
+
// a token: those defer, and continue untouched as before any profile existed.
|
|
110
|
+
// LookFantastic's order after BR-DGE's hosted fields, the path its live checkout runs
|
|
111
|
+
// (live check of Oct 6, 2026), is not one of them: it carries BR-DGE's masked card
|
|
112
|
+
// number at the profile's card number field, so it is held like any card request, and
|
|
113
|
+
// while the profile is in observe it is refused as an unrecognized payment endpoint,
|
|
114
|
+
// since its body is not the one the profile reviewed. When BR-DGE's answer holds no
|
|
115
|
+
// number the page sends null there, and that order continues untouched on the token
|
|
116
|
+
// BR-DGE made from the card the agent typed. A body the profile reads as
|
|
109
117
|
// a card-free payment (its payment_method names another method: FastSpring's PayPal and
|
|
110
118
|
// wallets post the same URL) also continues untouched, in every status, with an event that
|
|
111
119
|
// says so, since no approval could cover it; one that names another method but carries a
|
|
@@ -1477,6 +1485,51 @@ async function ekashuAbort(ctx) {
|
|
|
1477
1485
|
await ctx.effects.abort();
|
|
1478
1486
|
return { kind: 'abort' };
|
|
1479
1487
|
}
|
|
1488
|
+
// PayPal's legacy Hosted Fields (paypal-hosted-fields.ts). Its tokenization frame posts the card the
|
|
1489
|
+
// agent typed to the Orders v2 confirm-payment-source on cors.api.paypal.com (or its sandbox), which
|
|
1490
|
+
// no Vault mode takes over, so every company's adapters hand each POST there to this gate:
|
|
1491
|
+
// - a body with a card number at payment_source.card.number aborts as an unsupported checkout,
|
|
1492
|
+
// reason `unsupported_paypal_hosted_fields`, with no claim, create or prompt, so the confirm never
|
|
1493
|
+
// carries the card to PayPal. The attachment then reads `unsupported` and holds: the merchant's
|
|
1494
|
+
// integration decides the shape, so the page's next confirm would carry the card again;
|
|
1495
|
+
// - a body the adapter could not read, an empty one, or one that is not a JSON object aborts that
|
|
1496
|
+
// request alone with a `blocked` event, `unreadable_paypal_request`, and does not hold the
|
|
1497
|
+
// attachment: nothing proves it carries no card;
|
|
1498
|
+
// - any other body (a PayPal wallet, a saved card, a token) defers, and the arbiter continues it
|
|
1499
|
+
// untouched.
|
|
1500
|
+
// A confirm the synced registry recognizes as a card request defers too, so the arbiter pauses it
|
|
1501
|
+
// for that recognizer; no build serves one today. A GET, a preflight or another path is not this
|
|
1502
|
+
// gate's. Synchronous until it must abort, like every gate. Exported for the gate registry's tests.
|
|
1503
|
+
export const paypalHostedFieldsPreClaim = (ctx) => {
|
|
1504
|
+
const { url, method } = ctx.request;
|
|
1505
|
+
if (String(method).toUpperCase() !== 'POST' || !isPaypalHostedFieldsConfirm(url) || ctx.vault.isCardRequest(url, method))
|
|
1506
|
+
return undefined;
|
|
1507
|
+
let body;
|
|
1508
|
+
// A reader that throws (Playwright's postData() can) proves nothing either way.
|
|
1509
|
+
try {
|
|
1510
|
+
body = ctx.request.body;
|
|
1511
|
+
}
|
|
1512
|
+
catch {
|
|
1513
|
+
body = null;
|
|
1514
|
+
}
|
|
1515
|
+
const carried = paypalHostedFieldsBody(body);
|
|
1516
|
+
if (carried === 'no_card')
|
|
1517
|
+
return undefined;
|
|
1518
|
+
return carried === 'card' ? paypalHostedFieldsAbort(ctx) : paypalHostedFieldsUnreadable(ctx);
|
|
1519
|
+
};
|
|
1520
|
+
async function paypalHostedFieldsAbort(ctx) {
|
|
1521
|
+
const { url, method } = ctx.request;
|
|
1522
|
+
ctx.preparationGate.invalidate('unsupported_checkout');
|
|
1523
|
+
ctx.lifecycle.unsupported(PAYPAL_HOSTED_FIELDS_REASON);
|
|
1524
|
+
ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: eventUrl(ctx.vault, url), method, reason: PAYPAL_HOSTED_FIELDS_REASON } });
|
|
1525
|
+
await ctx.effects.abort();
|
|
1526
|
+
return { kind: 'abort' };
|
|
1527
|
+
}
|
|
1528
|
+
async function paypalHostedFieldsUnreadable(ctx) {
|
|
1529
|
+
ctx.onEvent?.({ type: 'blocked', detail: 'unreadable_paypal_request' });
|
|
1530
|
+
await ctx.effects.abort();
|
|
1531
|
+
return { kind: 'abort' };
|
|
1532
|
+
}
|
|
1480
1533
|
// The terminal arbiter, always last. A request that no gate claimed and that is
|
|
1481
1534
|
// neither a native Stripe step nor a recognized card request continues, unless
|
|
1482
1535
|
// it matches a declared payment endpoint we do not support (then it aborts as an
|
|
@@ -1510,7 +1563,7 @@ async function unsupportedContinue(ctx) {
|
|
|
1510
1563
|
* arbiter runs after all of them and is not part of this list. A gate appended
|
|
1511
1564
|
* after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
|
|
1512
1565
|
* pauses each one itself. */
|
|
1513
|
-
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim];
|
|
1566
|
+
export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, paypalHostedFieldsPreClaim];
|
|
1514
1567
|
/** Run each gate in order; the first to return a decision wins. If none does,
|
|
1515
1568
|
* the terminal arbiter decides between continue, abort (unsupported) and pause.
|
|
1516
1569
|
* Returns synchronously while every gate defers synchronously (an ordinary card
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
"origin": "https://js.braintreegateway.com",
|
|
220
220
|
"path_pattern": "/web/:version/js/*",
|
|
221
221
|
"hostname_pattern": "^js\\.braintreegateway\\.com$",
|
|
222
|
-
"pathname_pattern": "^\\/web\\/[^/]+\\/js\\/",
|
|
222
|
+
"pathname_pattern": "^\\/web\\/(?![^/]*-payments-sdk)[^/]+\\/js\\/",
|
|
223
223
|
"sources": [
|
|
224
224
|
{
|
|
225
225
|
"kind": "official_documentation",
|
|
@@ -238,7 +238,7 @@
|
|
|
238
238
|
"origin": "https://assets.braintreegateway.com",
|
|
239
239
|
"path_pattern": "/web/*",
|
|
240
240
|
"hostname_pattern": "^assets\\.braintreegateway\\.com$",
|
|
241
|
-
"pathname_pattern": "^\\/web\\/",
|
|
241
|
+
"pathname_pattern": "^\\/web\\/(?![^/]*-payments-sdk)",
|
|
242
242
|
"sources": [
|
|
243
243
|
{
|
|
244
244
|
"kind": "official_documentation",
|
|
@@ -2137,6 +2137,41 @@
|
|
|
2137
2137
|
"hostname_pattern": "^cdn\\.sandbox\\.[a-z0-9-]+\\.gr4vy\\.app$",
|
|
2138
2138
|
"pathname_pattern": "^\\/secure-fields\\.js$"
|
|
2139
2139
|
},
|
|
2140
|
+
{
|
|
2141
|
+
"id": "paypal-hosted-fields-payments-sdk",
|
|
2142
|
+
"psp": "paypal",
|
|
2143
|
+
"kinds": [
|
|
2144
|
+
"frame",
|
|
2145
|
+
"iframe"
|
|
2146
|
+
],
|
|
2147
|
+
"origin": "https://assets.braintreegateway.com",
|
|
2148
|
+
"path_pattern": "/web/:version-payments-sdk{,-:build}/html/hosted-fields-{input-frame,payments-sdk-tokenization-frame}{,.min}.html",
|
|
2149
|
+
"sources": [
|
|
2150
|
+
{
|
|
2151
|
+
"kind": "primary_source",
|
|
2152
|
+
"reference": "https://assets.braintreegateway.com/web/3.32.0-payments-sdk-dev/html/hosted-fields-input-frame.min.html",
|
|
2153
|
+
"reviewed_on": "2026-10-07"
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
"kind": "primary_source",
|
|
2157
|
+
"reference": "https://assets.braintreegateway.com/web/3.32.0-payments-sdk-dev/html/hosted-fields-payments-sdk-tokenization-frame.min.html",
|
|
2158
|
+
"reviewed_on": "2026-10-07"
|
|
2159
|
+
},
|
|
2160
|
+
{
|
|
2161
|
+
"kind": "primary_source",
|
|
2162
|
+
"reference": "https://www.paypal.com/sdk/js?components=hosted-fields",
|
|
2163
|
+
"reviewed_on": "2026-10-07"
|
|
2164
|
+
},
|
|
2165
|
+
{
|
|
2166
|
+
"kind": "saved_observation",
|
|
2167
|
+
"reference": "planet-golf-uk-2026-10-06:paypal-hosted-fields-payments-sdk-frames",
|
|
2168
|
+
"reviewed_on": "2026-10-07"
|
|
2169
|
+
}
|
|
2170
|
+
],
|
|
2171
|
+
"variant_unverified": "PayPal legacy Hosted Fields frames send the card to the Orders v2 confirm-payment-source on cors.api.paypal.com; no Vault mode takes that request over.",
|
|
2172
|
+
"hostname_pattern": "^assets\\.braintreegateway\\.com$",
|
|
2173
|
+
"pathname_pattern": "^\\/web\\/[0-9]+\\.[0-9]+\\.[0-9]+-payments-sdk(?:-[a-z]+)?\\/html\\/hosted-fields-(?:input-frame|payments-sdk-tokenization-frame)(?:\\.min)?\\.html$"
|
|
2174
|
+
},
|
|
2140
2175
|
{
|
|
2141
2176
|
"id": "fiserv-commercehub-payment-fields",
|
|
2142
2177
|
"psp": "fiserv",
|
|
@@ -468,6 +468,7 @@
|
|
|
468
468
|
"mercado-pago-checkout-pro-card-form",
|
|
469
469
|
"gravy-secure-fields-script",
|
|
470
470
|
"gravy-secure-fields-script-sandbox",
|
|
471
|
+
"paypal-hosted-fields-payments-sdk",
|
|
471
472
|
"fiserv-commercehub-payment-fields",
|
|
472
473
|
"fiserv-commercehub-checkout-script",
|
|
473
474
|
"fiserv-secure-data-capture-frame",
|
|
@@ -1098,6 +1099,7 @@
|
|
|
1098
1099
|
"mercado-pago-checkout-pro-card-form",
|
|
1099
1100
|
"gravy-secure-fields-script",
|
|
1100
1101
|
"gravy-secure-fields-script-sandbox",
|
|
1102
|
+
"paypal-hosted-fields-payments-sdk",
|
|
1101
1103
|
"fiserv-commercehub-payment-fields",
|
|
1102
1104
|
"fiserv-commercehub-checkout-script",
|
|
1103
1105
|
"fiserv-secure-data-capture-frame",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
-
// source-sha256:
|
|
2
|
+
// source-sha256: 9d383b5155c791fc68947784fe3e2c16d03d3056572fb353f98305358fe75bc9
|
|
3
3
|
import { CHECKOUT_PREFLIGHT_CAPABILITIES } from './preflight-capabilities.generated.js';
|
|
4
4
|
/** Advisory page discovery. These rules never admit a payment destination. */
|
|
5
5
|
export const CHECKOUT_PREFLIGHT_VERSION = '1';
|
|
@@ -20,8 +20,11 @@ const RULES = [
|
|
|
20
20
|
{ id: 'mollie-components-script', psp: 'mollie', kinds: ['script'], hostname: /^js\.mollie\.com$/, pathname: /^\/v2\/mollie\.js$/, origin: 'https://js.mollie.com', path_pattern: '/v2/mollie.js' },
|
|
21
21
|
{ id: 'mollie-components-v1-script', psp: 'mollie', kinds: ['script'], hostname: /^js\.mollie\.com$/, pathname: /^\/v1\/mollie\.js$/, origin: 'https://js.mollie.com', path_pattern: '/v1/mollie.js' },
|
|
22
22
|
{ id: 'mollie-hosted-credit-card', psp: 'mollie', kinds: ['document', 'frame', 'iframe'], hostname: /^www\.mollie\.com$/, pathname: /^\/checkout\/credit-card\/session\/[A-Za-z0-9_-]+\/?$/, origin: 'https://www.mollie.com', path_pattern: '/checkout/credit-card/session/:session', flow: 'mollie.hosted_components' },
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// A braintree-web build whose version carries -payments-sdk (3.32.0-payments-sdk-dev) is PayPal's
|
|
24
|
+
// (paypal-hosted-fields-payments-sdk below): its card goes to PayPal, never to Braintree, so neither
|
|
25
|
+
// Braintree rule takes one.
|
|
26
|
+
{ id: 'braintree-web-sdk', psp: 'braintree', kinds: ['script'], hostname: /^js\.braintreegateway\.com$/, pathname: /^\/web\/(?![^/]*-payments-sdk)[^/]+\/js\//, origin: 'https://js.braintreegateway.com', path_pattern: '/web/:version/js/*' },
|
|
27
|
+
{ id: 'braintree-hosted-fields', psp: 'braintree', kinds: ['frame', 'iframe'], hostname: /^assets\.braintreegateway\.com$/, pathname: /^\/web\/(?![^/]*-payments-sdk)/, origin: 'https://assets.braintreegateway.com', path_pattern: '/web/*' },
|
|
25
28
|
{ id: 'paysafe-js', psp: 'paysafe', kinds: ['script', 'frame', 'iframe'], hostname: /^hosted\.paysafe\.com$/, pathname: /^\/(?:js|checkout)\//, origin: 'https://hosted.paysafe.com', path_pattern: '/{js,checkout}/*' },
|
|
26
29
|
{ id: 'worldpay-checkout-sdk', psp: 'worldpay', kinds: ['script', 'frame', 'iframe'], hostname: /^access\.worldpay\.com$/, pathname: /^\/access-checkout\//, origin: 'https://access.worldpay.com', path_pattern: '/access-checkout/*' },
|
|
27
30
|
{ id: 'authorize-net-accept', psp: 'authorize_net', kinds: ['script'], hostname: /^js\.authorize\.net$/, pathname: /^\/v[13]\/Accept(?:UI)?\.js$/, origin: 'https://js.authorize.net', path_pattern: '/v{1,3}/Accept{UI}.js' },
|
|
@@ -58,6 +61,24 @@ RULES.push({ id: 'tranzila-terminal-hosted-page', psp: 'tranzila', kinds: ['docu
|
|
|
58
61
|
{ id: 'gravy-secure-fields-script', psp: 'gravy', kinds: ['script'], hostname: /^cdn\.[a-z0-9-]+\.gr4vy\.app$/, pathname: /^\/secure-fields\.js$/, origin: 'https://cdn.:merchant.gr4vy.app', path_pattern: '/secure-fields.js',
|
|
59
62
|
sources: [documented('https://docs.gr4vy.com/guides/payments/secure-fields')] }, { id: 'gravy-secure-fields-script-sandbox', psp: 'gravy', kinds: ['script'], hostname: /^cdn\.sandbox\.[a-z0-9-]+\.gr4vy\.app$/, pathname: /^\/secure-fields\.js$/, origin: 'https://cdn.sandbox.:merchant.gr4vy.app', path_pattern: '/secure-fields.js',
|
|
60
63
|
sources: [documented('https://docs.gr4vy.com/guides/payments/secure-fields')] });
|
|
64
|
+
// PayPal's legacy Hosted Fields (its JS SDK's components=hosted-fields), reviewed 2026-10-07 from Planet Golf
|
|
65
|
+
// UK's payment step (read live 2026-10-06, SDK 5.0.580) and the two frame files it loads. The SDK renders each
|
|
66
|
+
// card field as braintree-web's 3.32.0-payments-sdk-dev hosted-fields-input-frame.min.html (sha256
|
|
67
|
+
// 5b6a746e...ff401b0bc) and adds a 0x0 hosted-fields-payments-sdk-tokenization-frame.min.html (sha256
|
|
68
|
+
// be804070...a610a6fa6e), both on assets.braintreegateway.com. The tokenization frame posts the typed card to
|
|
69
|
+
// the Orders v2 confirm-payment-source on cors.api.paypal.com, which no Vault mode takes over, and the checkout
|
|
70
|
+
// SDK refuses that post (checkout/src/paypal-hosted-fields.ts). So the frames name paypal and the result stays
|
|
71
|
+
// unknown through `variant_unverified`. A flow with server_constraint 'unsupported' cannot say so here: three
|
|
72
|
+
// visible input frames would each count as an active flow frame and read as ambiguous_checkout.
|
|
73
|
+
const paypalHostedFieldsSource = (reference) => ({ kind: 'primary_source', reference, reviewed_on: '2026-10-07' });
|
|
74
|
+
RULES.push({ id: 'paypal-hosted-fields-payments-sdk', psp: 'paypal', kinds: ['frame', 'iframe'], hostname: /^assets\.braintreegateway\.com$/,
|
|
75
|
+
pathname: /^\/web\/[0-9]+\.[0-9]+\.[0-9]+-payments-sdk(?:-[a-z]+)?\/html\/hosted-fields-(?:input-frame|payments-sdk-tokenization-frame)(?:\.min)?\.html$/,
|
|
76
|
+
origin: 'https://assets.braintreegateway.com', path_pattern: '/web/:version-payments-sdk{,-:build}/html/hosted-fields-{input-frame,payments-sdk-tokenization-frame}{,.min}.html',
|
|
77
|
+
sources: [paypalHostedFieldsSource('https://assets.braintreegateway.com/web/3.32.0-payments-sdk-dev/html/hosted-fields-input-frame.min.html'),
|
|
78
|
+
paypalHostedFieldsSource('https://assets.braintreegateway.com/web/3.32.0-payments-sdk-dev/html/hosted-fields-payments-sdk-tokenization-frame.min.html'),
|
|
79
|
+
paypalHostedFieldsSource('https://www.paypal.com/sdk/js?components=hosted-fields'),
|
|
80
|
+
{ kind: 'saved_observation', reference: 'planet-golf-uk-2026-10-06:paypal-hosted-fields-payments-sdk-frames', reviewed_on: '2026-10-07' }],
|
|
81
|
+
variant_unverified: 'PayPal legacy Hosted Fields frames send the card to the Orders v2 confirm-payment-source on cors.api.paypal.com; no Vault mode takes that request over.' });
|
|
61
82
|
// Fiserv Commerce Hub, read 2026-09-23 from Home Depot's OrangePay frame and Fiserv's
|
|
62
83
|
// docs: payment-fields.js renders one Secure Data Capture iframe per card field, and
|
|
63
84
|
// the Checkout JS SDK is Commerce Hub's other browser entry. Fiserv ships dark (its
|
|
@@ -755,7 +776,8 @@ export function assessCheckoutSupport(input, options = {}) {
|
|
|
755
776
|
&& (signal.kind === 'script' ? signal.visible !== false : signal.visible === true));
|
|
756
777
|
if (incompatible.length) {
|
|
757
778
|
result.reason_codes.push('processor_variant_unverified');
|
|
758
|
-
|
|
779
|
+
// Several frames of one variant (four on Planet Golf's PayPal Hosted Fields) carry one limitation.
|
|
780
|
+
result.limitations.push(...unique(incompatible.map(({ rule }) => rule.variant_unverified)));
|
|
759
781
|
return result;
|
|
760
782
|
}
|
|
761
783
|
}
|