@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 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-11.2"`. 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.
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. A completed merchant payment remains unverified. |
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 and Mercado Pago 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 and Mercado Pago 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.
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 the controlled hosted Stripe Checkout TEST integration, both attachment functions accept `stripeCheckout: { sessionId, publishableKey }`. This requires a `cs_test_` Session, its `pk_test_` key, `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`.
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
- This integration requires matching backend and measured executor releases, with separate TEST flags explicitly enabled. An unavailable prepared request is declined or held for reconciliation; it cannot switch to human approval. LIVE Sessions, subscriptions, saved-card flows, and authentication continuations are not supported by this option. It cannot enable production Autopilot.
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 TEST autopilot configuration.');
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/cs_test_[A-Za-z0-9]+/confirm$";
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/cs_test_[A-Za-z0-9]+/confirm$",
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-11.2",
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/cs_test_[A-Za-z0-9]+/confirm$",
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-11.2",
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-11.2",
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-11.2"
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-11.2"
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-11.2"
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-11.2"
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-11.2";
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/cs_test_[A-Za-z0-9]+/confirm$";
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: ced5aecd22684c680a53b9e33dff1c5333cd36823f6fe3b4b8e84f10f6f73c53
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-11.2';
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
- /** Explicit development enrollment; this option cannot enable production execution. */
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: 41b6fde86ad6193d1d7cb505c7dda522c2d664a9939ee0a4a0b3028cfa8bfa06
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 = /^cs_test_[A-Za-z0-9]{1,200}(?![\s\S])/;
528
- var KEY = /^pk_test_[A-Za-z0-9_]{1,256}(?![\s\S])/;
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\/(cs_test_[A-Za-z0-9]+)\/confirm$/.exec(url.pathname);
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 (pk_test_[A-Za-z0-9_]{1,256})$/.exec(authorization[0][1]);
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: false,
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.livemode !== false || value.mode !== "payment" || !isStripeCheckoutHostedMode(value.ui_mode))
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 || !["4242424242424242", "4000000000000002", "4000002500003155"].includes(card.number) || card.cvc !== "123" || !/^(?:0?[1-9]|1[0-2])$/.test(String(card.month)) || !/^(?:\d{2}|\d{4})$/.test(String(card.year)) || card.name !== void 0 && !opaque(card.name))
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 };
@@ -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
- || !/^cs_test_[A-Za-z0-9]{1,200}(?![\s\S])/.test(sessionId) || !/^pk_test_[A-Za-z0-9_]{1,256}(?![\s\S])/.test(publishableKey))
37
- throw new Error('Native Stripe Checkout requires a TEST session, grant and numeric USD amount.');
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
- return { phase: 'final', request: { ...request, headers: { ...request.headers,
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-11.2",
4
- "catalog_sha256": "cf77f91526c17c55c73e14c28bca14a664e6727c0e00a439763250d80d11a967",
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
  "schema_version": 1,
3
3
  "profile_version": "kernel-template-1",
4
- "catalog_version": "2026-09-11.2",
4
+ "catalog_version": "2026-09-14.1",
5
5
  "integration": {
6
6
  "id": "kernel_native",
7
7
  "version": "example-native-adapter"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "snapshot": {
3
3
  "schema_version": 1,
4
- "catalog_version": "2026-09-11.2",
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-11.2",
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-11.2",
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-11.2",
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-11.2",
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-11.2",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-cards/checkout",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Let browser agents pay with the user's own card, without your infrastructure ever touching card data.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",