@agent-cards/checkout 0.10.0 → 0.11.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.
- package/CHANGELOG.md +2 -0
- package/PREFLIGHT.md +1 -1
- package/README.md +15 -4
- package/dist/cdp.js +2 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +5 -3
- package/dist/preflight-capabilities.generated.d.ts +17 -2
- package/dist/preflight-capabilities.generated.js +21 -2
- package/dist/preflight-catalog.json +24 -5
- package/dist/preflight-schemas.json +4 -4
- package/dist/preflight.generated.d.ts +18 -3
- package/dist/preflight.generated.js +2 -2
- package/dist/prepared-processor.d.ts +1 -1
- package/dist/prepared-processor.js +7 -1
- package/dist/recurly.generated.d.ts +1 -0
- package/dist/recurly.generated.js +87 -0
- package/dist/stripe-checkout.d.ts +3 -1
- package/dist/stripe-checkout.generated.d.ts +12 -1
- package/dist/stripe-checkout.generated.js +81 -9
- package/dist/stripe-checkout.js +19 -8
- package/examples/preflight/kernel-native/inventory.json +2 -2
- package/examples/preflight/kernel-profile.empty.json +1 -1
- package/examples/preflight/mollie-hosted.result.json +2 -2
- package/examples/preflight/stripe-script.direct.result.json +2 -2
- package/examples/preflight/stripe-script.result.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Approve a Recurly checkout before its native card request starts. Call `prepare` with `psp: 'recurly'` and `environment: 'shared'`, then submit the merchant's form once. The same selected card and approved purchase bind a new-card POST on the US or EU endpoint. The matching API, database migration and Vault release are required. Token issuance and merchant payment completion remain separate results.
|
|
6
|
+
|
|
5
7
|
- Keep the selected card's issuer in guest Mercado Pago Checkout Pro in Mexico. The Vault resolves the card's eight-digit prefix against the merchant's captured checkout configuration over browser TLS. The SDK returns the original card-token response and corrects one native card association. Missing configuration, ambiguous issuers, changed checkout context, and a different card brand or type stop continuation. The matching API, Vault and encrypted relay release is required. Other Mercado Pago checkout families and merchant payment completion need separate validation.
|
|
6
8
|
|
|
7
9
|
- Prepare controlled hosted Stripe Checkout in TEST mode with an explicit `stripeCheckout` attachment option and Autopilot grant. Dummy tokenization stays local; the browser's native final confirmation uses the generated shared core and enclave execution path. The matching backend and executor flags default off. This does not enable production Autopilot or establish a completed merchant order.
|
package/PREFLIGHT.md
CHANGED
|
@@ -208,7 +208,7 @@ Declare processor support and flow support separately. A flow entry cannot estab
|
|
|
208
208
|
|
|
209
209
|
Take PSP identifiers, payment modes, flow identifiers, operation identifiers, and operation revisions from the packaged catalog. Verify each combination against the named adapter version before adding a `supported` entry. Use `unsupported` only for a known exclusion. A profile with a wrong schema, incompatible catalog, mismatched adapter version, or expired timestamp preserves `unknown`.
|
|
210
210
|
|
|
211
|
-
The JSON contract uses `schema_version: 1`. The catalog bundled with this release uses `catalog_version: "2026-09-
|
|
211
|
+
The JSON contract uses `schema_version: 1`. The catalog bundled with this release uses `catalog_version: "2026-09-14.1"`. Keep the snapshot, classifier, and profile on the same catalog version. Re-normalize observations with the installed package after changing the catalog. Review the adapter declarations before issuing a compatible profile; changing the version string alone does not validate new behavior.
|
|
212
212
|
|
|
213
213
|
Kernel can integrate the JSON contract immediately and supply its validated native capabilities later. No deployed Agentcard endpoint or payment request is required for that work.
|
|
214
214
|
|
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
|
|
|
134
134
|
| Stripe | tokenization replay and direct card-bearing PaymentIntent confirms are implemented; direct confirms read the intent's amount back from Stripe; a hint sent as `amount` + `currency` must agree with it. Browser token-to-intent continuation is unsupported and held. Validate the exact merchant flow before pilot use |
|
|
135
135
|
| Braintree card tokenization | Prepared checkout supported; one live Haymarket Books ebook purchase with SDK `0.5.0` confirmed merchant fulfillment and SDK `completed` using a merchant receipt resolver. Independent processor capture/settlement, live 3DS and PayPal wallet flows remain unverified. |
|
|
136
136
|
| Checkout.com | supported |
|
|
137
|
-
| Mercado Pago | Card tokenization and prepared checkout are implemented. Guest Checkout Pro in Mexico also corrects the issuer for one native card association when the selected card has the same brand and type.
|
|
137
|
+
| Mercado Pago | Card tokenization and prepared checkout are implemented. Guest Checkout Pro in Mexico also corrects the issuer for one native card association when the selected card has the same brand and type. One live MXN 40 Lotería Chida purchase with published SDK 0.10.0 completed automatically through Pay; the merchant confirmed paid status and PDF fulfillment. The historical SDK result remains unknown because the private receipt adapter rejected a relative download URL; a separate read with the corrected adapter confirms that same paid receipt. Independent processor capture/settlement and other country or integration paths remain unverified. |
|
|
138
138
|
| VGS Collect (Very Good Security; Wolt) | not supported: VGS's proxy aliases only submissions from its own iframe, so a replay from the cardholder's device is refused by the merchant (verified on Wolt, 2026-09-03). Not recognized, so the agent's browser is not paused there |
|
|
139
139
|
| Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it |
|
|
140
140
|
| Tranzila | supported (mode `hosted_form`): the cardholder finishes on Tranzila's own page; the paused form navigation resolves to a synthetic page, and you poll the merchant's order state |
|
|
@@ -467,8 +467,17 @@ await page.getByRole('button', { name: 'Pay', exact: true }).click();
|
|
|
467
467
|
| Worldpay | `production` or `sandbox` | Matching Access Worldpay host and `/sessions/card` |
|
|
468
468
|
| Bambora | `shared` | `/scripts/tokenization/tokens` on `api.bam.shift4api.net` or `api.na.bambora.com` |
|
|
469
469
|
| Mercado Pago | `shared` | `api.mercadopago.com/v1/card_tokens` with a fresh card body |
|
|
470
|
+
| Recurly | `shared` | Form-encoded POST to `/js/v1/token` on `api.recurly.com` or `api.eu.recurly.com` |
|
|
470
471
|
|
|
471
|
-
Use `environment: 'shared'` for Bambora
|
|
472
|
+
Use `environment: 'shared'` for Bambora, Mercado Pago and Recurly because the same endpoint serves test and live requests. Agentcard cannot establish the processor's test mode from that URL or a credential prefix. Configure test mode through the merchant's processor account when testing. Agentcard's own `sandbox` flag remains separate. Prepared Worldpay, Bambora, Mercado Pago and Recurly requests reject saved-card and recurring request bodies; a refused request retires the local preparation. Reconcile any existing merchant attempt before creating a new attachment.
|
|
473
|
+
|
|
474
|
+
Prepare Recurly before clicking the merchant's payment button:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
await controller.prepare({ psp: 'recurly', environment: 'shared' });
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
After approval, submit the merchant's form once. Recurly's native request timer starts with that submission. The SDK preserves the captured US or EU endpoint and accepts a new-card form only. Legacy JSONP, saved tokens, bank accounts, alternative payments and proactive authentication requests require separate support. Prepared requests also refuse nonempty Worldpay or Cybersource risk results because those sessions can depend on the original card. A nonempty co-badged network preference is also refused until the selected card’s supported networks can be checked. A card token does not confirm a donation, subscription or purchase; confirm the merchant's result before reporting payment success.
|
|
472
481
|
|
|
473
482
|
### Use Checkout Pro in Mexico
|
|
474
483
|
|
|
@@ -570,10 +579,12 @@ When the cardholder has enabled an eligible spending rule in their vault, the sa
|
|
|
570
579
|
|
|
571
580
|
Use `executionMode: 'user_approval'` to require the existing confirmation flow. Without a selected card or grant, the backend can use exactly one eligible rule; ambiguous card selection keeps confirmation. A `grantId` restricts selection to that rule. The initial executor supports only the configured controlled Stripe test flow, and production remains disabled.
|
|
572
581
|
|
|
573
|
-
For
|
|
582
|
+
For controlled hosted Stripe Checkout, both attachment functions accept `stripeCheckout: { sessionId, publishableKey, environment? }`. The default is TEST, requiring `cs_test_` and `pk_test_`. LIVE requires explicit `environment: 'production'`, a `cs_live_` Session and its `pk_live_` key. Both modes require `executionMode: 'autopilot'`, an explicit `grantId`, and a positive numeric USD `amount` in cents. The top-level document must be that Session on `checkout.stripe.com`. Direct `authorize()` calls use `stripeCheckoutEnvironment: 'production'` for the same explicit LIVE opt-in; the attachment functions pass it automatically.
|
|
574
583
|
|
|
575
584
|
The SDK answers dummy-card tokenization locally, then sends the browser's final native confirmation through the shared core and enclave. The local response is preparation only: it creates no authorization and makes no processor request. The final result includes `checkoutSessionId` only after the selected grant, restricted terminal response, and captured amount have been checked. Continue to confirm the merchant order through the checkout controller.
|
|
576
585
|
|
|
577
|
-
|
|
586
|
+
The shared core carries a validated billing email from that tokenization request into the final confirmation before authorization. The email stays bound to the same Session and local PaymentMethod reference. Missing email stays missing; duplicate or conflicting values are refused. The SDK does not fill an email from the Agentcard account or invent one for the merchant.
|
|
587
|
+
|
|
588
|
+
This integration requires matching backend and measured executor releases. TEST and LIVE have separate admission controls. LIVE also requires a newly authorized production grant on the selected real card, a fixed merchant account/profile with exactly one complete fixed-price USD line item and an independently reviewed operation qualification reference; the SDK option supplies none of these. LIVE deployment remains unqualified and disabled until that evidence exists. Exact observed totals do not establish atomic amount enforcement by Stripe. Subscriptions, saved-card flows and authentication continuations remain excluded. An unavailable preparation or uncertain result is declined or held for reconciliation; it cannot switch to a competing human-approval payment.
|
|
578
589
|
|
|
579
590
|
The adapters also send the observed top-level HTTPS `merchantOrigin`, such as `https://shop.example`, without a path, query or trailing slash. Direct `authorize()` callers can supply that origin explicitly. It is a routing hint; the protected adapter independently verifies the processor account, payee and amount. If a browser cannot provide its top-level URL, an explicit `merchantOrigin` option can supply the hint; otherwise the regular approval path remains available.
|
package/dist/cdp.js
CHANGED
|
@@ -711,6 +711,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
|
|
|
711
711
|
cardId: preparation?.cardId ?? opts.cardId,
|
|
712
712
|
executionMode: opts.executionMode,
|
|
713
713
|
grantId: opts.grantId,
|
|
714
|
+
stripeCheckoutEnvironment: opts.stripeCheckout?.environment,
|
|
714
715
|
merchantOrigin,
|
|
715
716
|
pageOrigin,
|
|
716
717
|
pageAmount,
|
|
@@ -1076,6 +1077,7 @@ export async function attachToPlaywright(page, opts) {
|
|
|
1076
1077
|
cardId: preparation?.cardId ?? opts.cardId,
|
|
1077
1078
|
executionMode: opts.executionMode,
|
|
1078
1079
|
grantId: opts.grantId,
|
|
1080
|
+
stripeCheckoutEnvironment: opts.stripeCheckout?.environment,
|
|
1079
1081
|
merchantOrigin,
|
|
1080
1082
|
pageOrigin,
|
|
1081
1083
|
pageAmount,
|
package/dist/client.d.ts
CHANGED
|
@@ -166,6 +166,8 @@ export declare class CheckoutPreparationError extends Error {
|
|
|
166
166
|
constructor(preparationId: string | null, reason: string);
|
|
167
167
|
}
|
|
168
168
|
export interface AuthorizeInput extends ExecutionMetadata {
|
|
169
|
+
/** Native Checkout defaults to TEST; LIVE requires this explicit opt-in. */
|
|
170
|
+
stripeCheckoutEnvironment?: 'test' | 'production';
|
|
169
171
|
/** Observed top-level HTTPS merchant origin; a routing hint, never payment authority. */
|
|
170
172
|
merchantOrigin?: string;
|
|
171
173
|
/** Your identifier for the person whose card should pay. */
|
package/dist/client.js
CHANGED
|
@@ -520,14 +520,16 @@ export class VaultClient {
|
|
|
520
520
|
|| input.executionMode === 'autopilot' || input.grantId))
|
|
521
521
|
throw new Error('mercado_checkout_context_invalid');
|
|
522
522
|
const nativeCheckout = hasStripeCheckoutMarker(input.request);
|
|
523
|
+
if (input.stripeCheckoutEnvironment !== undefined && !nativeCheckout)
|
|
524
|
+
throw new Error('Native Stripe Checkout environment requires its bound request.');
|
|
523
525
|
const checkoutContext = nativeCheckout ? parseStripeCheckoutContext(input.request) : null;
|
|
524
526
|
if (nativeCheckout) {
|
|
525
527
|
if (!checkoutContext || input.preparation || input.executionMode !== 'autopilot' || !input.grantId
|
|
526
528
|
|| !Number.isSafeInteger(input.amount) || Number(input.amount) <= 0 || input.currency !== 'usd'
|
|
527
529
|
|| input.merchantOrigin !== 'https://checkout.stripe.com')
|
|
528
|
-
throw new Error('Native Stripe Checkout requires an explicit
|
|
530
|
+
throw new Error('Native Stripe Checkout requires an explicit mode-bound autopilot configuration.');
|
|
529
531
|
const phase = classifyStripeCheckoutRequest(input.request, {
|
|
530
|
-
sessionId: checkoutContext.session_id, syntheticPaymentMethodId: checkoutContext.synthetic_payment_method_id,
|
|
532
|
+
environment: input.stripeCheckoutEnvironment, sessionId: checkoutContext.session_id, syntheticPaymentMethodId: checkoutContext.synthetic_payment_method_id,
|
|
531
533
|
amountCents: input.amount,
|
|
532
534
|
});
|
|
533
535
|
if (phase?.phase !== 'final')
|
|
@@ -814,7 +816,7 @@ export class VaultClient {
|
|
|
814
816
|
if (checkoutContext) {
|
|
815
817
|
let receipt;
|
|
816
818
|
try {
|
|
817
|
-
receipt = parseStripeCheckoutResponse(JSON.parse(response.body), { sessionId: checkoutContext.session_id });
|
|
819
|
+
receipt = parseStripeCheckoutResponse(JSON.parse(response.body), { environment: input.stripeCheckoutEnvironment, sessionId: checkoutContext.session_id });
|
|
818
820
|
}
|
|
819
821
|
catch { /* Never return an unvalidated processor body to the page. */ }
|
|
820
822
|
if (response.status !== 200 || !receipt || s.amount_verified !== true
|
|
@@ -51,7 +51,7 @@ export declare const CHECKOUT_PREFLIGHT_CAPABILITIES: {
|
|
|
51
51
|
readonly mode: "token";
|
|
52
52
|
readonly operations: readonly [{
|
|
53
53
|
readonly id: "stripe.device_checkout_confirm.v1";
|
|
54
|
-
readonly path_pattern: "^/v1/payment_pages/
|
|
54
|
+
readonly path_pattern: "^/v1/payment_pages/cs_(?:test|live)_[A-Za-z0-9]+/confirm$";
|
|
55
55
|
readonly effect: "payment_confirmation";
|
|
56
56
|
readonly amount_binding: "expected_amount_test_evidence_currency_unqualified";
|
|
57
57
|
readonly psp: "stripe";
|
|
@@ -1002,7 +1002,7 @@ export declare const CHECKOUT_PREFLIGHT_CAPABILITIES: {
|
|
|
1002
1002
|
readonly requirements: readonly ["verify_merchant_billing"];
|
|
1003
1003
|
};
|
|
1004
1004
|
};
|
|
1005
|
-
readonly limitations: readonly ["Only the listed request format is recognized; a recognized processor is not verified merchant coverage.", "The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints."];
|
|
1005
|
+
readonly limitations: readonly ["Only the listed request format is recognized; a recognized processor is not verified merchant coverage.", "The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints.", "Prepare approval before submitting a timed native card form. Both regions require the shared environment because test and live keys use the same endpoints."];
|
|
1006
1006
|
readonly unsupported_variants: readonly [{
|
|
1007
1007
|
readonly variant: "legacy_jsonp_get";
|
|
1008
1008
|
readonly status: "unsupported";
|
|
@@ -1013,6 +1013,21 @@ export declare const CHECKOUT_PREFLIGHT_CAPABILITIES: {
|
|
|
1013
1013
|
readonly status: "unsupported";
|
|
1014
1014
|
readonly constraint: "direct_sdk";
|
|
1015
1015
|
readonly reason: "Bank-account and alternative-payment token routes are outside the new-card path.";
|
|
1016
|
+
}, {
|
|
1017
|
+
readonly variant: "card_bound_risk_preflight";
|
|
1018
|
+
readonly status: "unsupported";
|
|
1019
|
+
readonly constraint: "direct_sdk";
|
|
1020
|
+
readonly reason: "Worldpay and Cybersource risk sessions can depend on the original card. Prepared requests refuse those results until collection can use the approved card.";
|
|
1021
|
+
}, {
|
|
1022
|
+
readonly variant: "prepared_co_badged_network_choice";
|
|
1023
|
+
readonly status: "unsupported";
|
|
1024
|
+
readonly constraint: "direct_sdk";
|
|
1025
|
+
readonly reason: "A prepared request cannot reuse the original card’s network preference without checking the selected card’s supported networks.";
|
|
1026
|
+
}, {
|
|
1027
|
+
readonly variant: "proactive_authentication";
|
|
1028
|
+
readonly status: "unsupported";
|
|
1029
|
+
readonly constraint: "direct_sdk";
|
|
1030
|
+
readonly reason: "Card-bearing risk authentication and proactive 3-D Secure action tokens require separate support.";
|
|
1016
1031
|
}];
|
|
1017
1032
|
readonly processor_amount: "none";
|
|
1018
1033
|
readonly challenge_surface: "merchant_browser";
|
|
@@ -80,7 +80,7 @@ export const CHECKOUT_PREFLIGHT_CAPABILITIES = {
|
|
|
80
80
|
"operations": [
|
|
81
81
|
{
|
|
82
82
|
"id": "stripe.device_checkout_confirm.v1",
|
|
83
|
-
"path_pattern": "^/v1/payment_pages/
|
|
83
|
+
"path_pattern": "^/v1/payment_pages/cs_(?:test|live)_[A-Za-z0-9]+/confirm$",
|
|
84
84
|
"effect": "payment_confirmation",
|
|
85
85
|
"amount_binding": "expected_amount_test_evidence_currency_unqualified",
|
|
86
86
|
"psp": "stripe",
|
|
@@ -1545,7 +1545,8 @@ export const CHECKOUT_PREFLIGHT_CAPABILITIES = {
|
|
|
1545
1545
|
},
|
|
1546
1546
|
"limitations": [
|
|
1547
1547
|
"Only the listed request format is recognized; a recognized processor is not verified merchant coverage.",
|
|
1548
|
-
"The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints."
|
|
1548
|
+
"The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints.",
|
|
1549
|
+
"Prepare approval before submitting a timed native card form. Both regions require the shared environment because test and live keys use the same endpoints."
|
|
1549
1550
|
],
|
|
1550
1551
|
"unsupported_variants": [
|
|
1551
1552
|
{
|
|
@@ -1559,6 +1560,24 @@ export const CHECKOUT_PREFLIGHT_CAPABILITIES = {
|
|
|
1559
1560
|
"status": "unsupported",
|
|
1560
1561
|
"constraint": "direct_sdk",
|
|
1561
1562
|
"reason": "Bank-account and alternative-payment token routes are outside the new-card path."
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
"variant": "card_bound_risk_preflight",
|
|
1566
|
+
"status": "unsupported",
|
|
1567
|
+
"constraint": "direct_sdk",
|
|
1568
|
+
"reason": "Worldpay and Cybersource risk sessions can depend on the original card. Prepared requests refuse those results until collection can use the approved card."
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
"variant": "prepared_co_badged_network_choice",
|
|
1572
|
+
"status": "unsupported",
|
|
1573
|
+
"constraint": "direct_sdk",
|
|
1574
|
+
"reason": "A prepared request cannot reuse the original card’s network preference without checking the selected card’s supported networks."
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
"variant": "proactive_authentication",
|
|
1578
|
+
"status": "unsupported",
|
|
1579
|
+
"constraint": "direct_sdk",
|
|
1580
|
+
"reason": "Card-bearing risk authentication and proactive 3-D Secure action tokens require separate support."
|
|
1562
1581
|
}
|
|
1563
1582
|
],
|
|
1564
1583
|
"processor_amount": "none",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"catalog_version": "2026-09-
|
|
3
|
+
"catalog_version": "2026-09-14.1",
|
|
4
4
|
"scope": "preflight_hint",
|
|
5
5
|
"integration_version_note": "bundled describes the capabilities accompanying this helper, not a claim about another installed SDK version.",
|
|
6
6
|
"limits": {
|
|
@@ -1966,7 +1966,7 @@
|
|
|
1966
1966
|
"operations": [
|
|
1967
1967
|
{
|
|
1968
1968
|
"id": "stripe.device_checkout_confirm.v1",
|
|
1969
|
-
"path_pattern": "^/v1/payment_pages/
|
|
1969
|
+
"path_pattern": "^/v1/payment_pages/cs_(?:test|live)_[A-Za-z0-9]+/confirm$",
|
|
1970
1970
|
"effect": "payment_confirmation",
|
|
1971
1971
|
"amount_binding": "expected_amount_test_evidence_currency_unqualified",
|
|
1972
1972
|
"psp": "stripe",
|
|
@@ -3431,7 +3431,8 @@
|
|
|
3431
3431
|
},
|
|
3432
3432
|
"limitations": [
|
|
3433
3433
|
"Only the listed request format is recognized; a recognized processor is not verified merchant coverage.",
|
|
3434
|
-
"The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints."
|
|
3434
|
+
"The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints.",
|
|
3435
|
+
"Prepare approval before submitting a timed native card form. Both regions require the shared environment because test and live keys use the same endpoints."
|
|
3435
3436
|
],
|
|
3436
3437
|
"unsupported_variants": [
|
|
3437
3438
|
{
|
|
@@ -3445,6 +3446,24 @@
|
|
|
3445
3446
|
"status": "unsupported",
|
|
3446
3447
|
"constraint": "direct_sdk",
|
|
3447
3448
|
"reason": "Bank-account and alternative-payment token routes are outside the new-card path."
|
|
3449
|
+
},
|
|
3450
|
+
{
|
|
3451
|
+
"variant": "card_bound_risk_preflight",
|
|
3452
|
+
"status": "unsupported",
|
|
3453
|
+
"constraint": "direct_sdk",
|
|
3454
|
+
"reason": "Worldpay and Cybersource risk sessions can depend on the original card. Prepared requests refuse those results until collection can use the approved card."
|
|
3455
|
+
},
|
|
3456
|
+
{
|
|
3457
|
+
"variant": "prepared_co_badged_network_choice",
|
|
3458
|
+
"status": "unsupported",
|
|
3459
|
+
"constraint": "direct_sdk",
|
|
3460
|
+
"reason": "A prepared request cannot reuse the original card’s network preference without checking the selected card’s supported networks."
|
|
3461
|
+
},
|
|
3462
|
+
{
|
|
3463
|
+
"variant": "proactive_authentication",
|
|
3464
|
+
"status": "unsupported",
|
|
3465
|
+
"constraint": "direct_sdk",
|
|
3466
|
+
"reason": "Card-bearing risk authentication and proactive 3-D Secure action tokens require separate support."
|
|
3448
3467
|
}
|
|
3449
3468
|
],
|
|
3450
3469
|
"processor_amount": "none",
|
|
@@ -3722,7 +3741,7 @@
|
|
|
3722
3741
|
"direct_sdk_profile": {
|
|
3723
3742
|
"schema_version": 1,
|
|
3724
3743
|
"profile_version": "preflight-2",
|
|
3725
|
-
"catalog_version": "2026-09-
|
|
3744
|
+
"catalog_version": "2026-09-14.1",
|
|
3726
3745
|
"integration": {
|
|
3727
3746
|
"id": "direct_sdk",
|
|
3728
3747
|
"version": "bundled"
|
|
@@ -3881,7 +3900,7 @@
|
|
|
3881
3900
|
"kernel_profile_template": {
|
|
3882
3901
|
"schema_version": 1,
|
|
3883
3902
|
"profile_version": "kernel-template-1",
|
|
3884
|
-
"catalog_version": "2026-09-
|
|
3903
|
+
"catalog_version": "2026-09-14.1",
|
|
3885
3904
|
"integration": {
|
|
3886
3905
|
"id": "kernel_native",
|
|
3887
3906
|
"version": "REPLACE-WITH-ADAPTER-VERSION"
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,119}$"
|
|
163
163
|
},
|
|
164
164
|
"catalog_version": {
|
|
165
|
-
"const": "2026-09-
|
|
165
|
+
"const": "2026-09-14.1"
|
|
166
166
|
},
|
|
167
167
|
"integration": {
|
|
168
168
|
"type": "object",
|
|
@@ -358,7 +358,7 @@
|
|
|
358
358
|
"const": 1
|
|
359
359
|
},
|
|
360
360
|
"catalog_version": {
|
|
361
|
-
"const": "2026-09-
|
|
361
|
+
"const": "2026-09-14.1"
|
|
362
362
|
},
|
|
363
363
|
"signals": {
|
|
364
364
|
"type": "array",
|
|
@@ -550,7 +550,7 @@
|
|
|
550
550
|
"pattern": "^[A-Za-z0-9][A-Za-z0-9._+-]{0,119}$"
|
|
551
551
|
},
|
|
552
552
|
"catalog_version": {
|
|
553
|
-
"const": "2026-09-
|
|
553
|
+
"const": "2026-09-14.1"
|
|
554
554
|
},
|
|
555
555
|
"integration": {
|
|
556
556
|
"type": "object",
|
|
@@ -753,7 +753,7 @@
|
|
|
753
753
|
"const": 1
|
|
754
754
|
},
|
|
755
755
|
"catalog_version": {
|
|
756
|
-
"const": "2026-09-
|
|
756
|
+
"const": "2026-09-14.1"
|
|
757
757
|
},
|
|
758
758
|
"integration": {
|
|
759
759
|
"type": "object",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Advisory page discovery. These rules never admit a payment destination. */
|
|
2
2
|
export declare const CHECKOUT_PREFLIGHT_VERSION = "1";
|
|
3
|
-
export declare const CHECKOUT_PREFLIGHT_CATALOG_VERSION = "2026-09-
|
|
3
|
+
export declare const CHECKOUT_PREFLIGHT_CATALOG_VERSION = "2026-09-14.1";
|
|
4
4
|
export declare const CHECKOUT_PREFLIGHT_LIMITS: Readonly<{
|
|
5
5
|
frames: 64;
|
|
6
6
|
signals: 2048;
|
|
@@ -196,7 +196,7 @@ export declare function getCheckoutPreflightCatalog(): {
|
|
|
196
196
|
readonly mode: "token";
|
|
197
197
|
readonly operations: readonly [{
|
|
198
198
|
readonly id: "stripe.device_checkout_confirm.v1";
|
|
199
|
-
readonly path_pattern: "^/v1/payment_pages/
|
|
199
|
+
readonly path_pattern: "^/v1/payment_pages/cs_(?:test|live)_[A-Za-z0-9]+/confirm$";
|
|
200
200
|
readonly effect: "payment_confirmation";
|
|
201
201
|
readonly amount_binding: "expected_amount_test_evidence_currency_unqualified";
|
|
202
202
|
readonly psp: "stripe";
|
|
@@ -1147,7 +1147,7 @@ export declare function getCheckoutPreflightCatalog(): {
|
|
|
1147
1147
|
readonly requirements: readonly ["verify_merchant_billing"];
|
|
1148
1148
|
};
|
|
1149
1149
|
};
|
|
1150
|
-
readonly limitations: readonly ["Only the listed request format is recognized; a recognized processor is not verified merchant coverage.", "The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints."];
|
|
1150
|
+
readonly limitations: readonly ["Only the listed request format is recognized; a recognized processor is not verified merchant coverage.", "The adapter covers the normal XHR POST new-card tokenization path on Recurly US and EU endpoints.", "Prepare approval before submitting a timed native card form. Both regions require the shared environment because test and live keys use the same endpoints."];
|
|
1151
1151
|
readonly unsupported_variants: readonly [{
|
|
1152
1152
|
readonly variant: "legacy_jsonp_get";
|
|
1153
1153
|
readonly status: "unsupported";
|
|
@@ -1158,6 +1158,21 @@ export declare function getCheckoutPreflightCatalog(): {
|
|
|
1158
1158
|
readonly status: "unsupported";
|
|
1159
1159
|
readonly constraint: "direct_sdk";
|
|
1160
1160
|
readonly reason: "Bank-account and alternative-payment token routes are outside the new-card path.";
|
|
1161
|
+
}, {
|
|
1162
|
+
readonly variant: "card_bound_risk_preflight";
|
|
1163
|
+
readonly status: "unsupported";
|
|
1164
|
+
readonly constraint: "direct_sdk";
|
|
1165
|
+
readonly reason: "Worldpay and Cybersource risk sessions can depend on the original card. Prepared requests refuse those results until collection can use the approved card.";
|
|
1166
|
+
}, {
|
|
1167
|
+
readonly variant: "prepared_co_badged_network_choice";
|
|
1168
|
+
readonly status: "unsupported";
|
|
1169
|
+
readonly constraint: "direct_sdk";
|
|
1170
|
+
readonly reason: "A prepared request cannot reuse the original card’s network preference without checking the selected card’s supported networks.";
|
|
1171
|
+
}, {
|
|
1172
|
+
readonly variant: "proactive_authentication";
|
|
1173
|
+
readonly status: "unsupported";
|
|
1174
|
+
readonly constraint: "direct_sdk";
|
|
1175
|
+
readonly reason: "Card-bearing risk authentication and proactive 3-D Secure action tokens require separate support.";
|
|
1161
1176
|
}];
|
|
1162
1177
|
readonly processor_amount: "none";
|
|
1163
1178
|
readonly challenge_surface: "merchant_browser";
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
-
// source-sha256:
|
|
2
|
+
// source-sha256: 69919c5fe4133b090eddae027fb12c5ad7c51a8c63e8719325c174aeee78c30a
|
|
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';
|
|
6
|
-
export const CHECKOUT_PREFLIGHT_CATALOG_VERSION = '2026-09-
|
|
6
|
+
export const CHECKOUT_PREFLIGHT_CATALOG_VERSION = '2026-09-14.1';
|
|
7
7
|
export const CHECKOUT_PREFLIGHT_LIMITS = Object.freeze({ frames: 64, signals: 2048, snapshot_bytes: 262144, url_length: 4096 });
|
|
8
8
|
// Identification-only IDs, checked against the canonical catalog by the core tests.
|
|
9
9
|
// No card destination, request method, or request body pattern lives here.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago';
|
|
1
|
+
export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly';
|
|
2
2
|
export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
|
|
3
3
|
/** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. */
|
|
4
4
|
export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { braintreeEnvironment, isPreparedBraintreeRequest, readTokenizationJson } from './braintree.js';
|
|
2
|
+
import { isPreparedRecurlyRequest } from './recurly.generated.js';
|
|
2
3
|
/** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. */
|
|
3
4
|
export function validPreparationEnvironment(psp, environment) {
|
|
4
|
-
if (psp === 'bambora' || psp === 'mercado_pago')
|
|
5
|
+
if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly')
|
|
5
6
|
return environment === 'shared';
|
|
6
7
|
return ['square', 'braintree', 'worldpay'].includes(psp) && ['production', 'sandbox'].includes(environment);
|
|
7
8
|
}
|
|
@@ -12,6 +13,8 @@ export function preparationEndpoint(psp, environment) {
|
|
|
12
13
|
return 'https://api.bam.shift4api.net/scripts/tokenization/tokens';
|
|
13
14
|
if (psp === 'mercado_pago')
|
|
14
15
|
return 'https://api.mercadopago.com/v1/card_tokens';
|
|
16
|
+
if (psp === 'recurly')
|
|
17
|
+
return 'https://api.recurly.com/js/v1/token';
|
|
15
18
|
if (psp === 'worldpay')
|
|
16
19
|
return environment === 'production'
|
|
17
20
|
? 'https://access.worldpay.com/sessions/card' : 'https://try.access.worldpay.com/sessions/card';
|
|
@@ -70,6 +73,9 @@ export function matchesPreparedRequest(psp, environment, requestUrl, method, bod
|
|
|
70
73
|
const request = new URL(requestUrl), endpoint = new URL(preparationEndpoint(psp, environment));
|
|
71
74
|
if (request.username || request.password || request.hash)
|
|
72
75
|
return false;
|
|
76
|
+
if (psp === 'recurly')
|
|
77
|
+
return [endpoint.href, 'https://api.eu.recurly.com/js/v1/token'].includes(requestUrl)
|
|
78
|
+
&& isPreparedRecurlyRequest(body ?? null);
|
|
73
79
|
if (psp === 'bambora') {
|
|
74
80
|
if (![endpoint.href, 'https://api.na.bambora.com/scripts/tokenization/tokens'].includes(requestUrl))
|
|
75
81
|
return false;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isPreparedRecurlyRequest(body: string | null): boolean;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Generated from @agent-cards/payment-core. Do not edit.
|
|
2
|
+
// source-sha256: 6a1932841621aca1a7371165e0b54a161c4dd5bb077acc190a0c5399229fa350
|
|
3
|
+
/** Normal Recurly.js 4 new-card POST, before it consumes an approval.
|
|
4
|
+
* Native fields and indexed form serialization: recurly/recurly-js commit
|
|
5
|
+
* 7cd9a2b131bf12d7cebfbc7eea1ba86aa54c60e7, lib/recurly/{token,request,fraud}.js
|
|
6
|
+
* and risk/{risk,three-d-secure/three-d-secure}.js. Worldpay and Cybersource
|
|
7
|
+
* risk sessions depend on the original card; proactive Braintree also produces
|
|
8
|
+
* another token type. All nonempty risk results are refused until collection
|
|
9
|
+
* can use the approved card. This function never rewrites the body.
|
|
10
|
+
*/
|
|
11
|
+
const SCALARS = new Set(['number', 'month', 'year', 'cvv', 'type', 'key', 'version', 'deviceId', 'sessionId', 'instanceId',
|
|
12
|
+
'first_name', 'last_name', 'address1', 'address2', 'company', 'country', 'city', 'state', 'postal_code', 'phone',
|
|
13
|
+
'vat_number', 'tax_identifier', 'tax_identifier_type', 'fraud_session_id', 'card_network_preference']);
|
|
14
|
+
const BROWSER = new Set(['color_depth', 'java_enabled', 'language', 'referrer_url', 'screen_height', 'screen_width', 'time_zone_offset', 'user_agent']);
|
|
15
|
+
const FRAUD_PROCESSORS = new Set(['kount', 'litle_threat_metrix', 'braintree', 'fraudnet']);
|
|
16
|
+
const contiguous = (values) => [...values.keys()].sort((a, b) => Number(a) - Number(b)).every((value, index) => value === String(index));
|
|
17
|
+
export function isPreparedRecurlyRequest(body) {
|
|
18
|
+
if (typeof body !== 'string' || !body || body.length > 65536)
|
|
19
|
+
return false;
|
|
20
|
+
const chunks = body.split('&');
|
|
21
|
+
if (chunks.length > 200)
|
|
22
|
+
return false;
|
|
23
|
+
const fields = new Map();
|
|
24
|
+
const fraud = new Map();
|
|
25
|
+
try {
|
|
26
|
+
for (const chunk of chunks) {
|
|
27
|
+
const separator = chunk.indexOf('=');
|
|
28
|
+
if (separator < 1)
|
|
29
|
+
return false;
|
|
30
|
+
const name = decodeURIComponent(chunk.slice(0, separator).replace(/\+/g, ' '));
|
|
31
|
+
const value = decodeURIComponent(chunk.slice(separator + 1).replace(/\+/g, ' '));
|
|
32
|
+
if (fields.has(name) || value.length > 4096 || /[\u0000-\u001f\u007f]/.test(value))
|
|
33
|
+
return false;
|
|
34
|
+
fields.set(name, value);
|
|
35
|
+
if (SCALARS.has(name))
|
|
36
|
+
continue;
|
|
37
|
+
const browser = /^browser\[([a-z_]+)\]$/.exec(name);
|
|
38
|
+
if (browser && BROWSER.has(browser[1]))
|
|
39
|
+
continue;
|
|
40
|
+
const structured = /^fraud\[([0-9]|1[0-9])\]\[(.+)\]$/.exec(name);
|
|
41
|
+
if (!structured)
|
|
42
|
+
return false;
|
|
43
|
+
const [, index, leaf] = structured;
|
|
44
|
+
if (!['processor', 'session_id'].includes(leaf) && !/^udf\]\[([0-9]|1[0-9])\]\[(label|value)$/.test(leaf))
|
|
45
|
+
return false;
|
|
46
|
+
if (!fraud.has(index))
|
|
47
|
+
fraud.set(index, new Map());
|
|
48
|
+
fraud.get(index).set(leaf, value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const number = fields.get('number') ?? '', month = fields.get('month') ?? '', year = fields.get('year') ?? '';
|
|
55
|
+
if (!/^[0-9]{12,19}$/.test(number) || !/^[0-9]{1,2}$/.test(month) || Number(month) < 1 || Number(month) > 12
|
|
56
|
+
|| !/^(?:[0-9]{2}|20[0-9]{2})$/.test(year))
|
|
57
|
+
return false;
|
|
58
|
+
if (fields.has('cvv') && fields.get('cvv') !== '' && !/^[0-9]{3,4}$/.test(fields.get('cvv')))
|
|
59
|
+
return false;
|
|
60
|
+
if (fields.has('type') && fields.get('type') !== 'credit_card')
|
|
61
|
+
return false;
|
|
62
|
+
// A co-badged network choice belongs to the entered card's supported brands.
|
|
63
|
+
if (fields.get('card_network_preference'))
|
|
64
|
+
return false;
|
|
65
|
+
if (fields.has('key') && !fields.get('key')?.trim())
|
|
66
|
+
return false;
|
|
67
|
+
if (!contiguous(fraud))
|
|
68
|
+
return false;
|
|
69
|
+
for (const item of fraud.values()) {
|
|
70
|
+
if (!FRAUD_PROCESSORS.has(item.get('processor') ?? '') || !item.get('session_id'))
|
|
71
|
+
return false;
|
|
72
|
+
const udf = new Map();
|
|
73
|
+
for (const key of item.keys()) {
|
|
74
|
+
const field = /^udf\]\[([0-9]|1[0-9])\]\[(label|value)$/.exec(key);
|
|
75
|
+
if (!field)
|
|
76
|
+
continue;
|
|
77
|
+
if (item.get('processor') !== 'kount')
|
|
78
|
+
return false;
|
|
79
|
+
if (!udf.has(field[1]))
|
|
80
|
+
udf.set(field[1], new Set());
|
|
81
|
+
udf.get(field[1]).add(field[2]);
|
|
82
|
+
}
|
|
83
|
+
if (!contiguous(udf) || [...udf.values()].some(keys => keys.size !== 2))
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PausedRequest } from './client.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Test by default. Production requires explicit opt-in and an independently admitted live grant. */
|
|
3
3
|
export interface StripeCheckoutOptions {
|
|
4
|
+
environment?: 'test' | 'production';
|
|
4
5
|
sessionId: string;
|
|
5
6
|
publishableKey: string;
|
|
6
7
|
}
|
|
@@ -53,6 +54,7 @@ export declare class StripeCheckoutClaimConflictError extends StripeCheckoutReje
|
|
|
53
54
|
*/
|
|
54
55
|
export declare class StripeCheckoutGate {
|
|
55
56
|
private state;
|
|
57
|
+
private billing?;
|
|
56
58
|
private readonly binding?;
|
|
57
59
|
constructor(options: {
|
|
58
60
|
stripeCheckout?: StripeCheckoutOptions;
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
declare var STRIPE_CHECKOUT_OPERATION: string;
|
|
2
2
|
declare var STRIPE_CHECKOUT_CONTEXT_HEADER: string;
|
|
3
|
+
declare function validateStripeCheckoutIdentity(value: any): Readonly<{
|
|
4
|
+
environment: any;
|
|
5
|
+
livemode: boolean;
|
|
6
|
+
}>;
|
|
7
|
+
declare function validateStripeCheckoutCard(card: any, environment?: string): boolean;
|
|
3
8
|
declare function isStripeCheckoutHostedMode(value: any): boolean;
|
|
4
9
|
declare function encodeStripeCheckoutContext(value: any): string;
|
|
5
10
|
declare function hasStripeCheckoutMarker(request: any): boolean;
|
|
@@ -9,6 +14,12 @@ declare function parseStripeCheckoutContext(request: any): {
|
|
|
9
14
|
synthetic_payment_method_id: any;
|
|
10
15
|
} | null | undefined;
|
|
11
16
|
declare function isStripeCheckoutEndpoint(raw: any): boolean;
|
|
17
|
+
declare function captureStripeCheckoutBilling(request: any, options: any): Readonly<{
|
|
18
|
+
session_id: any;
|
|
19
|
+
synthetic_payment_method_id: any;
|
|
20
|
+
email: string | null;
|
|
21
|
+
}>;
|
|
22
|
+
declare function attachStripeCheckoutBilling(request: any, captured: any, options: any): any;
|
|
12
23
|
declare function classifyStripeCheckoutRequest(request: any, options?: {}): {
|
|
13
24
|
phase: string;
|
|
14
25
|
session_id: any;
|
|
@@ -44,4 +55,4 @@ declare function prepareStripeCheckoutReplay(request: any, card: any, options?:
|
|
|
44
55
|
};
|
|
45
56
|
where: string[];
|
|
46
57
|
};
|
|
47
|
-
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay };
|
|
58
|
+
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, attachStripeCheckoutBilling, captureStripeCheckoutBilling, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay, validateStripeCheckoutCard, validateStripeCheckoutIdentity };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @ts-nocheck
|
|
2
2
|
// Generated from @agent-cards/payment-core. Do not edit.
|
|
3
|
-
// artifact-sha256:
|
|
3
|
+
// artifact-sha256: 2f7e340ba22bc33539f11e384a3e8362ae3fa510a9f8b540783c02bebcbc1ebf
|
|
4
4
|
// src/definitions.js
|
|
5
5
|
function deepFreeze(value) {
|
|
6
6
|
if (value && typeof value === "object" && !Object.isFrozen(value)) {
|
|
@@ -524,8 +524,8 @@ var replayPolicy = Object.freeze({ contentTypeFor, HEADER_NEVER_FORWARD });
|
|
|
524
524
|
// src/stripe-checkout.js
|
|
525
525
|
var STRIPE_CHECKOUT_OPERATION = "stripe.device_checkout_confirm.v1";
|
|
526
526
|
var STRIPE_CHECKOUT_CONTEXT_HEADER = "x-agentcard-stripe-checkout-context";
|
|
527
|
-
var SESSION = /^
|
|
528
|
-
var KEY = /^
|
|
527
|
+
var SESSION = /^cs_(?:test|live)_[A-Za-z0-9]{1,200}(?![\s\S])/;
|
|
528
|
+
var KEY = /^pk_(?:test|live)_[A-Za-z0-9_]{1,256}(?![\s\S])/;
|
|
529
529
|
var SYNTHETIC = /^pm_agentcard_checkout_[A-Za-z0-9_-]{16,80}(?![\s\S])/;
|
|
530
530
|
var reject = () => {
|
|
531
531
|
throw new Error("stripe_checkout_rejected");
|
|
@@ -533,6 +533,43 @@ var reject = () => {
|
|
|
533
533
|
var object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
534
534
|
var opaque = (value) => typeof value === "string" && value.length <= 8192 && !/[\x00-\x1f\x7f]/.test(value);
|
|
535
535
|
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
536
|
+
function validateStripeCheckoutIdentity(value) {
|
|
537
|
+
if (!object(value) || !["test", "production"].includes(value.environment ?? "test") || value.environment === null || !["sessionId", "publishableKey", "livemode"].some((key) => Object.hasOwn(value, key)))
|
|
538
|
+
reject();
|
|
539
|
+
const environment = value.environment ?? "test", livemode = environment === "production";
|
|
540
|
+
const mode = livemode ? "live" : "test";
|
|
541
|
+
if (Object.hasOwn(value, "sessionId") && (typeof value.sessionId !== "string" || !SESSION.test(value.sessionId) || !value.sessionId.startsWith(`cs_${mode}_`)) || Object.hasOwn(value, "publishableKey") && (typeof value.publishableKey !== "string" || !KEY.test(value.publishableKey) || !value.publishableKey.startsWith(`pk_${mode}_`)) || Object.hasOwn(value, "livemode") && value.livemode !== livemode)
|
|
542
|
+
reject();
|
|
543
|
+
return Object.freeze({ environment, livemode });
|
|
544
|
+
}
|
|
545
|
+
var TEST_CARDS = ["4242424242424242", "4000000000000002", "4000002500003155"];
|
|
546
|
+
var SYNTHETIC_CARDS = [...TEST_CARDS, "4111111111111111"];
|
|
547
|
+
function validateStripeCheckoutCard(card, environment = "test") {
|
|
548
|
+
if (!["test", "production"].includes(environment) || !object(card) || typeof card.number !== "string" || typeof card.cvc !== "string" || !/^(?:0?[1-9]|1[0-2])(?![\s\S])/.test(String(card.month)) || !/^(?:\d{2}|\d{4})(?![\s\S])/.test(String(card.year)) || card.name !== void 0 && !opaque(card.name))
|
|
549
|
+
reject();
|
|
550
|
+
if (environment === "test") {
|
|
551
|
+
if (!TEST_CARDS.includes(card.number) || card.cvc !== "123")
|
|
552
|
+
reject();
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
if (!/^\d{13,19}(?![\s\S])/.test(card.number) || /^(\d)\1+(?![\s\S])/.test(card.number) || !/^\d{3,4}(?![\s\S])/.test(card.cvc) || SYNTHETIC_CARDS.includes(card.number))
|
|
556
|
+
reject();
|
|
557
|
+
let sum = 0, double = false;
|
|
558
|
+
for (let i = card.number.length - 1; i >= 0; i--) {
|
|
559
|
+
let digit = Number(card.number[i]);
|
|
560
|
+
if (double) {
|
|
561
|
+
digit *= 2;
|
|
562
|
+
if (digit > 9)
|
|
563
|
+
digit -= 9;
|
|
564
|
+
}
|
|
565
|
+
sum += digit;
|
|
566
|
+
double = !double;
|
|
567
|
+
}
|
|
568
|
+
if (sum % 10 !== 0)
|
|
569
|
+
reject();
|
|
570
|
+
}
|
|
571
|
+
return true;
|
|
572
|
+
}
|
|
536
573
|
function isStripeCheckoutHostedMode(value) {
|
|
537
574
|
return value === "hosted" || value === "hosted_page";
|
|
538
575
|
}
|
|
@@ -600,7 +637,7 @@ function endpoint(raw) {
|
|
|
600
637
|
}
|
|
601
638
|
function isStripeCheckoutEndpoint(raw) {
|
|
602
639
|
const url = endpoint(raw);
|
|
603
|
-
const match = url && /^\/v1\/payment_pages\/(
|
|
640
|
+
const match = url && /^\/v1\/payment_pages\/(cs_(?:test|live)_[A-Za-z0-9]+)\/confirm$/.exec(url.pathname);
|
|
604
641
|
return !!match && SESSION.test(match[1]);
|
|
605
642
|
}
|
|
606
643
|
var finalFields = /* @__PURE__ */ new Set([
|
|
@@ -608,6 +645,7 @@ var finalFields = /* @__PURE__ */ new Set([
|
|
|
608
645
|
"key",
|
|
609
646
|
"expected_amount",
|
|
610
647
|
"expected_payment_method_type",
|
|
648
|
+
"payment_method_data[billing_details][email]",
|
|
611
649
|
"init_checksum",
|
|
612
650
|
"js_checksum",
|
|
613
651
|
"version",
|
|
@@ -687,7 +725,7 @@ function keyFor(request, values, expected) {
|
|
|
687
725
|
reject();
|
|
688
726
|
let headerKey;
|
|
689
727
|
if (authorization.length) {
|
|
690
|
-
const match = /^Bearer (
|
|
728
|
+
const match = /^Bearer (pk_(?:test|live)_[A-Za-z0-9_]{1,256})$/.exec(authorization[0][1]);
|
|
691
729
|
if (!match)
|
|
692
730
|
reject();
|
|
693
731
|
headerKey = match[1];
|
|
@@ -697,6 +735,35 @@ function keyFor(request, values, expected) {
|
|
|
697
735
|
reject();
|
|
698
736
|
return key;
|
|
699
737
|
}
|
|
738
|
+
var billingEmail = (value) => value === null || typeof value === "string" && value.length > 0 && value.length <= 320 && opaque(value);
|
|
739
|
+
function captureStripeCheckoutBilling(request, options) {
|
|
740
|
+
const phase = classifyStripeCheckoutRequest(request, options);
|
|
741
|
+
if (phase?.phase !== "tokenization" || !SYNTHETIC.test(options?.syntheticPaymentMethodId))
|
|
742
|
+
reject();
|
|
743
|
+
const email = form(request, tokenFields).values.get("billing_details[email]") ?? null;
|
|
744
|
+
if (!billingEmail(email))
|
|
745
|
+
reject();
|
|
746
|
+
return Object.freeze({
|
|
747
|
+
session_id: phase.session_id,
|
|
748
|
+
synthetic_payment_method_id: options.syntheticPaymentMethodId,
|
|
749
|
+
email
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
function attachStripeCheckoutBilling(request, captured, options) {
|
|
753
|
+
const phase = classifyStripeCheckoutRequest(request, options);
|
|
754
|
+
if (phase?.phase !== "final" || !object(captured) || Object.keys(captured).length !== 3 || !["session_id", "synthetic_payment_method_id", "email"].every((key) => Object.hasOwn(captured, key)) || captured.session_id !== phase.session_id || captured.synthetic_payment_method_id !== phase.synthetic_payment_method_id || !billingEmail(captured.email))
|
|
755
|
+
reject();
|
|
756
|
+
const field = "payment_method_data[billing_details][email]";
|
|
757
|
+
const existing = form(request, finalFields).values.get(field);
|
|
758
|
+
if (existing !== void 0 && existing !== captured.email)
|
|
759
|
+
reject();
|
|
760
|
+
if (captured.email === null || existing !== void 0)
|
|
761
|
+
return { ...request };
|
|
762
|
+
const body = `${request.body}&${encodeURIComponent(field)}=${encodeURIComponent(captured.email)}`;
|
|
763
|
+
const result = { ...request, body };
|
|
764
|
+
classifyStripeCheckoutRequest(result, options);
|
|
765
|
+
return result;
|
|
766
|
+
}
|
|
700
767
|
function classifyStripeCheckoutRequest(request, options = {}) {
|
|
701
768
|
const url = endpoint(request?.url);
|
|
702
769
|
if (!url) {
|
|
@@ -719,6 +786,7 @@ function classifyStripeCheckoutRequest(request, options = {}) {
|
|
|
719
786
|
reject();
|
|
720
787
|
const { values } = form(request, final ? finalFields : tokenFields);
|
|
721
788
|
const key = keyFor(request, values, options.publishableKey);
|
|
789
|
+
validateStripeCheckoutIdentity({ environment: options.environment, sessionId, publishableKey: key });
|
|
722
790
|
const attributionSession = values.get("client_attribution_metadata[checkout_session_id]");
|
|
723
791
|
if (attributionSession !== void 0 && attributionSession !== sessionId)
|
|
724
792
|
reject();
|
|
@@ -730,6 +798,9 @@ function classifyStripeCheckoutRequest(request, options = {}) {
|
|
|
730
798
|
}
|
|
731
799
|
const amount = values.get("expected_amount");
|
|
732
800
|
const syntheticId = values.get("payment_method");
|
|
801
|
+
const email = values.get("payment_method_data[billing_details][email]");
|
|
802
|
+
if (email !== void 0 && !billingEmail(email))
|
|
803
|
+
reject();
|
|
733
804
|
if (!/^[1-9]\d{0,11}$/.test(amount ?? "") || !Number.isSafeInteger(Number(amount)) || options.amountCents !== void 0 && options.amountCents !== Number(amount) || values.get("expected_payment_method_type") !== "card" || !SYNTHETIC.test(syntheticId) || ["init_checksum", "js_checksum", "version", "rv_timestamp"].some((key2) => !values.get(key2)) || options.syntheticPaymentMethodId !== void 0 && options.syntheticPaymentMethodId !== syntheticId || marker && marker.synthetic_payment_method_id !== syntheticId)
|
|
734
805
|
reject();
|
|
735
806
|
return {
|
|
@@ -753,7 +824,7 @@ function createStripeCheckoutTokenizationStub(request, options) {
|
|
|
753
824
|
id: options.syntheticPaymentMethodId,
|
|
754
825
|
object: "payment_method",
|
|
755
826
|
type: "card",
|
|
756
|
-
livemode:
|
|
827
|
+
livemode: validateStripeCheckoutIdentity({ environment: options.environment, sessionId: phase.session_id }).livemode,
|
|
757
828
|
billing_details: { ...billing, address },
|
|
758
829
|
card: {
|
|
759
830
|
brand: "visa",
|
|
@@ -783,11 +854,12 @@ function parseStripeCheckoutResponse(raw, options) {
|
|
|
783
854
|
let value;
|
|
784
855
|
try {
|
|
785
856
|
value = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
857
|
+
validateStripeCheckoutIdentity({ environment: options?.environment, sessionId: options?.sessionId, livemode: value?.livemode });
|
|
786
858
|
}
|
|
787
859
|
catch {
|
|
788
860
|
return null;
|
|
789
861
|
}
|
|
790
|
-
if (!object(value) || !SESSION.test(options?.sessionId) || value.session_id !== options.sessionId || Object.keys(value).some((key) => !terminalFields.has(key)) || !["id", "object", "state", "status", "payment_status", "currency", "livemode", "session_id", "mode", "ui_mode"].every((key) => Object.hasOwn(value, key)) || typeof value.id !== "string" || !/^[A-Za-z0-9_]{1,256}(?![\s\S])/.test(value.id) || value.object !== "checkout.session" || value.state !== "succeeded" || value.status !== "complete" || value.payment_status !== "paid" || value.currency !== "usd" || value.
|
|
862
|
+
if (!object(value) || !SESSION.test(options?.sessionId) || value.session_id !== options.sessionId || Object.keys(value).some((key) => !terminalFields.has(key)) || !["id", "object", "state", "status", "payment_status", "currency", "livemode", "session_id", "mode", "ui_mode"].every((key) => Object.hasOwn(value, key)) || typeof value.id !== "string" || !/^[A-Za-z0-9_]{1,256}(?![\s\S])/.test(value.id) || value.object !== "checkout.session" || value.state !== "succeeded" || value.status !== "complete" || value.payment_status !== "paid" || value.currency !== "usd" || value.mode !== "payment" || !isStripeCheckoutHostedMode(value.ui_mode))
|
|
791
863
|
return null;
|
|
792
864
|
for (const key of ["success_url", "return_url"]) {
|
|
793
865
|
if (!Object.hasOwn(value, key) || value[key] === null)
|
|
@@ -822,7 +894,7 @@ function prepareStripeCheckoutReplay(request, card, options = {}) {
|
|
|
822
894
|
sessionId: marker.session_id,
|
|
823
895
|
syntheticPaymentMethodId: marker.synthetic_payment_method_id
|
|
824
896
|
});
|
|
825
|
-
if (phase?.phase !== "final" || options.amountCents !== void 0 && phase.amount_cents !== options.amountCents || options.sessionId !== void 0 && options.sessionId !== marker.session_id || !card
|
|
897
|
+
if (phase?.phase !== "final" || options.amountCents !== void 0 && phase.amount_cents !== options.amountCents || options.sessionId !== void 0 && options.sessionId !== marker.session_id || !validateStripeCheckoutCard(card, options.environment))
|
|
826
898
|
reject();
|
|
827
899
|
const replacements = {
|
|
828
900
|
"payment_method_data[type]": "card",
|
|
@@ -840,4 +912,4 @@ function prepareStripeCheckoutReplay(request, card, options = {}) {
|
|
|
840
912
|
where: Object.keys(replacements)
|
|
841
913
|
};
|
|
842
914
|
}
|
|
843
|
-
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay };
|
|
915
|
+
export { STRIPE_CHECKOUT_CONTEXT_HEADER, STRIPE_CHECKOUT_OPERATION, attachStripeCheckoutBilling, captureStripeCheckoutBilling, classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, hasStripeCheckoutMarker, isStripeCheckoutEndpoint, isStripeCheckoutHostedMode, parseStripeCheckoutContext, parseStripeCheckoutResponse, prepareStripeCheckoutReplay, validateStripeCheckoutCard, validateStripeCheckoutIdentity };
|
package/dist/stripe-checkout.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, isStripeCheckoutEndpoint, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
|
|
1
|
+
import { classifyStripeCheckoutRequest, createStripeCheckoutTokenizationStub, encodeStripeCheckoutContext, isStripeCheckoutEndpoint, STRIPE_CHECKOUT_CONTEXT_HEADER, captureStripeCheckoutBilling, attachStripeCheckoutBilling, validateStripeCheckoutIdentity, } from './stripe-checkout.generated.js';
|
|
2
2
|
class StripeCheckoutRejectionError extends Error {
|
|
3
3
|
stage;
|
|
4
4
|
reason;
|
|
@@ -26,22 +26,23 @@ export class StripeCheckoutClaimConflictError extends StripeCheckoutRejectionErr
|
|
|
26
26
|
*/
|
|
27
27
|
export class StripeCheckoutGate {
|
|
28
28
|
state = 'fresh';
|
|
29
|
+
billing;
|
|
29
30
|
binding;
|
|
30
31
|
constructor(options) {
|
|
31
32
|
if (!options.stripeCheckout)
|
|
32
33
|
return;
|
|
33
|
-
const { sessionId, publishableKey } = options.stripeCheckout;
|
|
34
|
+
const { sessionId, publishableKey, environment } = options.stripeCheckout;
|
|
35
|
+
validateStripeCheckoutIdentity({ environment, sessionId, publishableKey });
|
|
34
36
|
if (options.executionMode !== 'autopilot' || !/^apg_[A-Za-z0-9_-]{1,128}(?![\s\S])/.test(options.grantId ?? '')
|
|
35
|
-
|| !Number.isSafeInteger(options.amount) || Number(options.amount) <= 0 || options.currency !== 'usd'
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.binding = Object.freeze({ sessionId, publishableKey, amountCents: options.amount,
|
|
37
|
+
|| !Number.isSafeInteger(options.amount) || Number(options.amount) <= 0 || options.currency !== 'usd')
|
|
38
|
+
throw new Error('Native Stripe Checkout requires a mode-bound session, grant and numeric USD amount.');
|
|
39
|
+
this.binding = Object.freeze({ environment, sessionId, publishableKey, amountCents: options.amount,
|
|
39
40
|
syntheticPaymentMethodId: `pm_agentcard_checkout_${crypto.randomUUID().replaceAll('-', '')}` });
|
|
40
41
|
}
|
|
41
42
|
matches(url) { return !!this.binding && isStripeCheckoutEndpoint(url); }
|
|
42
43
|
isEnabled() { return !!this.binding; }
|
|
43
44
|
isPrepared() { return this.state !== 'fresh'; }
|
|
44
|
-
invalidate() { this.state = 'stopped'; }
|
|
45
|
+
invalidate() { this.state = 'stopped'; this.billing = undefined; }
|
|
45
46
|
/** Observability only: this endpoint label never participates in a payment decision. */
|
|
46
47
|
describeRejection(error, rawUrl, stage, phase = 'unknown') {
|
|
47
48
|
let family = 'other';
|
|
@@ -102,6 +103,7 @@ export class StripeCheckoutGate {
|
|
|
102
103
|
this.state = 'stopped';
|
|
103
104
|
let response;
|
|
104
105
|
try {
|
|
106
|
+
this.billing = captureStripeCheckoutBilling(request, this.binding);
|
|
105
107
|
response = createStripeCheckoutTokenizationStub(request, this.binding);
|
|
106
108
|
}
|
|
107
109
|
catch {
|
|
@@ -114,8 +116,17 @@ export class StripeCheckoutGate {
|
|
|
114
116
|
throw new StripeCheckoutClaimConflictError('final');
|
|
115
117
|
if (phase.phase !== 'final' || this.state !== 'stubbed')
|
|
116
118
|
throw new StripeCheckoutRejectionError('Native Stripe Checkout has no matching prepared request.', 'claim', 'preparation_missing', diagnosticPhase);
|
|
119
|
+
let final;
|
|
120
|
+
try {
|
|
121
|
+
final = attachStripeCheckoutBilling(request, this.billing, this.binding);
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
this.invalidate();
|
|
125
|
+
throw new StripeCheckoutRejectionError('stripe_checkout_rejected', 'classification', 'request_validation_failed', 'final');
|
|
126
|
+
}
|
|
117
127
|
this.state = 'submitted';
|
|
118
|
-
|
|
128
|
+
this.billing = undefined;
|
|
129
|
+
return { phase: 'final', request: { ...final, headers: { ...final.headers,
|
|
119
130
|
[STRIPE_CHECKOUT_CONTEXT_HEADER]: encodeStripeCheckoutContext({ version: 1,
|
|
120
131
|
session_id: this.binding.sessionId, synthetic_payment_method_id: this.binding.syntheticPaymentMethodId }) } } };
|
|
121
132
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"catalog_version": "2026-09-
|
|
4
|
-
"catalog_sha256": "
|
|
3
|
+
"catalog_version": "2026-09-14.1",
|
|
4
|
+
"catalog_sha256": "c5bf9f8f7372def80d2abf0e9f2942c1b33514ed516b66d3f60e98c11d43bf5c",
|
|
5
5
|
"baseline_version": "kernel-native-docs-2026-09-11.1",
|
|
6
6
|
"baseline_sha256": "d86722f49633d8223fccae54cd3d485df4375aee06aa156a273cc689b7c8507c",
|
|
7
7
|
"native_build": null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"snapshot": {
|
|
3
3
|
"schema_version": 1,
|
|
4
|
-
"catalog_version": "2026-09-
|
|
4
|
+
"catalog_version": "2026-09-14.1",
|
|
5
5
|
"signals": [
|
|
6
6
|
{
|
|
7
7
|
"rule_id": "mollie-hosted-credit-card",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"result": {
|
|
35
35
|
"schema_version": 1,
|
|
36
|
-
"catalog_version": "2026-09-
|
|
36
|
+
"catalog_version": "2026-09-14.1",
|
|
37
37
|
"integration": {
|
|
38
38
|
"id": "kernel_native",
|
|
39
39
|
"version": "example-native-adapter"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"snapshot": {
|
|
3
3
|
"schema_version": 1,
|
|
4
|
-
"catalog_version": "2026-09-
|
|
4
|
+
"catalog_version": "2026-09-14.1",
|
|
5
5
|
"signals": [
|
|
6
6
|
{
|
|
7
7
|
"rule_id": "stripe-js-v3",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"result": {
|
|
28
28
|
"schema_version": 1,
|
|
29
|
-
"catalog_version": "2026-09-
|
|
29
|
+
"catalog_version": "2026-09-14.1",
|
|
30
30
|
"integration": {
|
|
31
31
|
"id": "direct_sdk",
|
|
32
32
|
"version": "bundled"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"snapshot": {
|
|
3
3
|
"schema_version": 1,
|
|
4
|
-
"catalog_version": "2026-09-
|
|
4
|
+
"catalog_version": "2026-09-14.1",
|
|
5
5
|
"signals": [
|
|
6
6
|
{
|
|
7
7
|
"rule_id": "stripe-js-v3",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"result": {
|
|
28
28
|
"schema_version": 1,
|
|
29
|
-
"catalog_version": "2026-09-
|
|
29
|
+
"catalog_version": "2026-09-14.1",
|
|
30
30
|
"integration": {
|
|
31
31
|
"id": "kernel_native",
|
|
32
32
|
"version": "example-native-adapter"
|
package/package.json
CHANGED