@agent-cards/checkout 0.14.0 → 0.15.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 an Adyen Sessions checkout before Pay with `prepare({ psp: 'adyen', environment: 'production' | 'sandbox' })`. The cardholder approves and picks the card first; when the merchant's Sessions `/payments` request pauses, the device encrypts the approved card under the merchant's Adyen key and the request continues from your browser, so Adyen Web's own 60-second request timeout covers only the pause, the bind and the encryption. `sandbox` is Adyen's test host with a `test_` client key; `production` is the live host families with a `live_` key. A stored-card, single-blob or store-the-card request never uses the approval. The matching API, Vault and database migration are required.
6
+
5
7
  - Approve Spreedly's native hosted-card checkout with `prepare({ psp: 'spreedly', environment: 'shared' })`. The Vault tokenizes the selected card over the encrypted relay and returns the native response. Matching API, Vault, relay and database releases are required; tokenization alone does not confirm payment. Saved-card updates and other Spreedly flows remain unsupported.
6
8
 
7
9
  - Identify hosted, regional, and versioned processor variants seen on live checkouts: Adyen secured fields on the regional live hosts and any release, the Square card element frame, dLocal Smart Fields releases, Mercado Pago secure fields and the guest Checkout Pro card form, and the Rapyd hosted checkout page. The Tranzila terminal-root page, the Authorize.Net hosted payment form, and Worldpay hosted payment pages identify their processor and return `unknown` with `processor_variant_unverified`. The catalog is `2026-09-15.1`; issue native profiles against it.
package/README.md CHANGED
@@ -85,7 +85,7 @@ Then let your agent click "Pay" like it always does. `attachToCdp` pauses the
85
85
  request for approval and resumes it only while the merchant request remains
86
86
  live. Merchant timeouts still apply: Square's observed tokenization deadline
87
87
  is about 10 seconds, Braintree's native request timeout is 60 seconds, and Adyen Web's own request timeout abandons its Sessions payment call 60 seconds after Pay (observed on Adyen Web 6.41 and 6.44), each including approval and handoff. These are the processors' limits, not ones the SDK enforces: the SDK's own authorization wait stays 15 minutes. For human approval, use
88
- `controller.prepare()` before the first Pay action as shown below. Adyen is not on the `prepare()` list yet, so an Adyen approval has to land within Adyen Web's minute.
88
+ `controller.prepare()` before the first Pay action as shown below. For Adyen, `prepare()` moves the approval before Pay, so only the device-side encryption runs inside Adyen Web's minute.
89
89
 
90
90
  Playwright:
91
91
 
@@ -136,7 +136,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
136
136
  | Checkout.com | supported |
137
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
- | Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it. Sessions flow only: the paused request is `/checkoutshopper/v1/sessions/{id}/payments` on Adyen's own hosts; a merchant that posts the encrypted fields to its own server is not recognized, so nothing pauses there. Approval starts at Pay and must land before Adyen Web's own request timeout (observed at 60 seconds on Adyen Web 6.41 and 6.44; not enforced by the SDK); `prepare()` is not available for Adyen yet |
139
+ | Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it. Sessions flow only: the paused request is `/checkoutshopper/v1/sessions/{id}/payments` on Adyen's own hosts; a merchant that posts the encrypted fields to its own server is not recognized, so nothing pauses there. Without a preparation, approval starts at Pay and must land before Adyen Web's own request timeout (observed at 60 seconds on Adyen Web 6.41 and 6.44; not enforced by the SDK); `prepare({ psp: 'adyen' })` moves the approval before Pay |
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 |
141
141
 
142
142
  The recognizer list is fetched from the API at runtime (`vault.syncRegistry()`),
@@ -515,9 +515,18 @@ await page.getByRole('button', { name: 'Pay', exact: true }).click();
515
515
  | Mercado Pago | `shared` | `api.mercadopago.com/v1/card_tokens` with a fresh card body |
516
516
  | Recurly | `shared` | Form-encoded POST to `/js/v1/token` on `api.recurly.com` or `api.eu.recurly.com` |
517
517
  | Spreedly | `shared` | Native iframe JSON POST to `/v1/payment_methods/restricted.json` on `core.spreedly.com` |
518
+ | Adyen | `production` or `sandbox` | The Sessions `/checkoutshopper/v1/sessions/{id}/payments` POST on the matching Adyen host family (`sandbox` is `checkoutshopper-test.adyen.com` with a `test_` client key; `production` is the live hosts with a `live_` key), carrying a fresh card's four encrypted fields |
518
519
 
519
520
  Use `environment: 'shared'` for Bambora, Mercado Pago, Recurly and Spreedly 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.
520
521
 
522
+ Prepare an Adyen Sessions checkout before the agent clicks Pay:
523
+
524
+ ```ts
525
+ await controller.prepare({ psp: 'adyen', environment: 'production' }); // 'sandbox' for Adyen's test host
526
+ ```
527
+
528
+ After approval, click Pay once. adyen-web encrypts the agent's placeholder digits and posts its Sessions `/payments` request; the SDK pauses it and binds it to the approval, the cardholder's device (still on the approval page) encrypts the approved card under the merchant's Adyen key, and the request continues from your browser with only the four encrypted fields swapped and `brand` dropped. Adyen answers your browser, so the merchant's order state is the outcome to poll. The bound request must be a fresh `scheme` card on the approved host family with a client key of that environment; a stored-card, single-blob or store-the-card request never uses the approval. Adyen Web's own 60-second request timeout then covers only the pause, the bind and the device's encryption.
529
+
521
530
  Prepare a Spreedly checkout before submitting the merchant's card form:
522
531
 
523
532
  ```ts
@@ -0,0 +1,24 @@
1
+ /** Adyen Web Sessions payments request, before it consumes an approval.
2
+ * adyen-web (Drop-in and the Card component in the Sessions flow) posts one
3
+ * JSON body to /checkoutshopper/v1/sessions/{id}/payments on the merchant's
4
+ * Adyen host, with `clientKey` as its only query parameter, and carries the
5
+ * card as four JWE blobs under paymentMethod. Those four blobs are what the
6
+ * cardholder's device replaces; nothing else in the body is rewritten. Test
7
+ * and live are separate host families AND separate client-key prefixes, so a
8
+ * preparation names one of them and the request must agree on both. A body
9
+ * that pays with a stored card, carries a single encrypted blob, or asks
10
+ * Adyen to store the card is not the purchase the person approved and never
11
+ * consumes readiness. This module never rewrites a body.
12
+ */
13
+ export type AdyenPreparationEnvironment = 'production' | 'sandbox';
14
+ export declare const ADYEN_ENCRYPTED_CARD_FIELDS: readonly ["encryptedCardNumber", "encryptedExpiryMonth", "encryptedExpiryYear", "encryptedSecurityCode"];
15
+ /** Which preparation environment a hostname belongs to: Adyen's test host, one of its live host families, or neither. */
16
+ export declare function adyenPreparationEnvironment(hostname: string): AdyenPreparationEnvironment | null;
17
+ /** The URL a preparation advertises for the approved host family: every registry recognizes it, and it is never a real session. */
18
+ export declare function adyenPreparationUrl(environment: AdyenPreparationEnvironment): string;
19
+ /** A Sessions payments URL on the approved host family whose only query is a client key of that same environment. */
20
+ export declare function isPreparedAdyenUrl(rawUrl: string, environment: AdyenPreparationEnvironment): boolean;
21
+ /** A fresh scheme card carrying the four encrypted fields the device replaces; stored cards, single-blob encryption and store requests never consume consent. */
22
+ export declare function isPreparedAdyenBody(body: string | null | undefined): boolean;
23
+ /** Processor identity, environment and request shape are part of the device's prior consent. */
24
+ export declare function isPreparedAdyenRequest(rawUrl: string, method: string, body: string | null | undefined, environment: AdyenPreparationEnvironment): boolean;
@@ -0,0 +1,64 @@
1
+ export const ADYEN_ENCRYPTED_CARD_FIELDS = ['encryptedCardNumber', 'encryptedExpiryMonth', 'encryptedExpiryYear', 'encryptedSecurityCode'];
2
+ const SESSIONS_PAYMENTS = /^\/checkoutshopper\/v1\/sessions\/[A-Za-z0-9_-]{1,256}\/payments$/;
3
+ const TEST_HOST = /^checkoutshopper-test\.adyen\.com$/;
4
+ const LIVE_HOST = /^(?:checkoutshopper-live(?:-[a-z]+)?\.adyen\.com|(?:[a-z0-9-]+\.)*adyenpayments\.com)$/;
5
+ const CLIENT_KEY = /^(test|live)_[A-Za-z0-9]{32}$/;
6
+ const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
7
+ const text = (value, max = 4096) => typeof value === 'string' && value.length > 0 && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value);
8
+ /** Which preparation environment a hostname belongs to: Adyen's test host, one of its live host families, or neither. */
9
+ export function adyenPreparationEnvironment(hostname) {
10
+ const host = hostname.toLowerCase();
11
+ if (TEST_HOST.test(host))
12
+ return 'sandbox';
13
+ if (LIVE_HOST.test(host))
14
+ return 'production';
15
+ return null;
16
+ }
17
+ /** The URL a preparation advertises for the approved host family: every registry recognizes it, and it is never a real session. */
18
+ export function adyenPreparationUrl(environment) {
19
+ return `https://checkoutshopper-${environment === 'production' ? 'live' : 'test'}.adyen.com/checkoutshopper/v1/sessions/prepared/payments`;
20
+ }
21
+ /** A Sessions payments URL on the approved host family whose only query is a client key of that same environment. */
22
+ export function isPreparedAdyenUrl(rawUrl, environment) {
23
+ let url;
24
+ try {
25
+ url = new URL(rawUrl);
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ if (url.protocol !== 'https:' || url.port || url.username || url.password || url.hash)
31
+ return false;
32
+ if (adyenPreparationEnvironment(url.hostname) !== environment || !SESSIONS_PAYMENTS.test(url.pathname))
33
+ return false;
34
+ const names = [...url.searchParams.keys()];
35
+ if (names.length !== 1 || names[0] !== 'clientKey')
36
+ return false;
37
+ const key = url.searchParams.get('clientKey') ?? '';
38
+ const match = CLIENT_KEY.exec(key);
39
+ return !!match && match[0] === key && (match[1] === 'live') === (environment === 'production');
40
+ }
41
+ /** A fresh scheme card carrying the four encrypted fields the device replaces; stored cards, single-blob encryption and store requests never consume consent. */
42
+ export function isPreparedAdyenBody(body) {
43
+ if (typeof body !== 'string' || !body || body.length > 65536)
44
+ return false;
45
+ let parsed;
46
+ try {
47
+ parsed = JSON.parse(body);
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ if (!record(parsed) || parsed.storePaymentMethod === true)
53
+ return false;
54
+ const method = parsed.paymentMethod;
55
+ if (!record(method) || method.type !== 'scheme')
56
+ return false;
57
+ if ('storedPaymentMethodId' in method || 'encryptedCard' in method || 'recurringDetailReference' in method)
58
+ return false;
59
+ return ADYEN_ENCRYPTED_CARD_FIELDS.every(field => text(method[field]));
60
+ }
61
+ /** Processor identity, environment and request shape are part of the device's prior consent. */
62
+ export function isPreparedAdyenRequest(rawUrl, method, body, environment) {
63
+ return method.toUpperCase() === 'POST' && isPreparedAdyenUrl(rawUrl, environment) && isPreparedAdyenBody(body);
64
+ }
package/dist/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type CheckoutMode, type Recognizer } from './registry.js';
2
2
  import type { Substitutions } from './substitute.js';
3
- import { type PreparationProcessor } from './prepared-processor.js';
3
+ import { type PreparationMode, type PreparationProcessor } from './prepared-processor.js';
4
4
  export interface PausedRequest {
5
5
  url: string;
6
6
  method: string;
@@ -147,6 +147,8 @@ export interface PreparedCheckout {
147
147
  readonly status: 'ready';
148
148
  readonly psp: PreparationProcessor;
149
149
  readonly environment: 'production' | 'sandbox' | 'shared';
150
+ /** How the approved card reaches the processor: ciphertext the device produces (Adyen, `cse`) or the device's own request (`token`). */
151
+ readonly mode: PreparationMode;
150
152
  readonly expiresAt: string;
151
153
  readonly cardId: string;
152
154
  readonly user: string;
package/dist/client.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { BUILTIN_REGISTRY, cardUrlPatterns as deriveCardUrlPatterns, findRecognizer, } from './registry.js';
2
2
  import { isMercadoTokenRequest, parseMercadoCheckoutContext, validateMercadoProcessorContext } from './mercado-checkout.generated.js';
3
- import { matchesPreparedRequest, validPreparationEnvironment } from './prepared-processor.js';
3
+ import { matchesPreparedRequest, preparationMode, validPreparationEnvironment } from './prepared-processor.js';
4
4
  import { hasOwnedShopMarker, parseOwnedShopOrder, parseOwnedShopReceipt } from './owned-shop.generated.js';
5
5
  import { classifyStripeCheckoutRequest, encodeStripeCheckoutContext, hasStripeCheckoutMarker, parseStripeCheckoutContext, parseStripeCheckoutResponse, STRIPE_CHECKOUT_CONTEXT_HEADER, } from './stripe-checkout.generated.js';
6
6
  /**
@@ -440,7 +440,7 @@ export class VaultClient {
440
440
  // The separate stop signal prevents dispatch after a slow OAuth exchange.
441
441
  const created = await this.post('/v2/checkout/preparations', {
442
442
  user: input.user, merchant: input.merchant, amount: input.amount, currency: input.currency.toLowerCase(),
443
- ...(input.cardId ? { card_id: input.cardId } : {}), psp: input.psp, mode: 'token',
443
+ ...(input.cardId ? { card_id: input.cardId } : {}), psp: input.psp, mode: preparationMode(input.psp),
444
444
  environment: input.environment, checkout_key: input.checkoutKey, merchant_origin: input.merchantOrigin,
445
445
  }, AbortSignal.timeout(30_000), signal);
446
446
  if (!created || typeof created.id !== 'string' || !/^cprep_[A-Za-z0-9_-]{1,128}$/.test(created.id))
@@ -471,11 +471,11 @@ export class VaultClient {
471
471
  || state.payment_status !== 'not_started' || state.amount_authority !== 'agent'
472
472
  || state.user !== input.user || state.merchant !== input.merchant || state.merchant_origin !== input.merchantOrigin
473
473
  || !Number.isSafeInteger(state.amount) || (typeof input.amount === 'number' && state.amount !== input.amount) || state.currency !== input.currency.toLowerCase()
474
- || state.psp !== input.psp || state.mode !== 'token' || state.environment !== input.environment
474
+ || state.psp !== input.psp || state.mode !== preparationMode(input.psp) || state.environment !== input.environment
475
475
  || state.checkout_key !== input.checkoutKey)
476
476
  throw fail('ready_unconfirmed', id);
477
477
  const prepared = Object.freeze({
478
- id: preparationId, status: 'ready', psp: input.psp, environment: input.environment, expiresAt: state.ready_expires_at,
478
+ id: preparationId, status: 'ready', psp: input.psp, environment: input.environment, mode: preparationMode(input.psp), expiresAt: state.ready_expires_at,
479
479
  cardId: state.card_id, user: input.user, merchant: input.merchant, amount: state.amount,
480
480
  amountDisplay: typeof state.amount_display === 'string' ? state.amount_display : null,
481
481
  currency: input.currency.toLowerCase(), merchantOrigin: input.merchantOrigin, checkoutKey: input.checkoutKey,
@@ -582,7 +582,7 @@ export class VaultClient {
582
582
  // clientSideEncrypted entry (an older registry, a hand-built one) still
583
583
  // refuses here, exactly as before.
584
584
  const mode = rec.mode ?? 'token';
585
- if (preparation && (rec.psp !== preparation.psp || mode !== 'token'))
585
+ if (preparation && (rec.psp !== preparation.psp || mode !== preparationMode(preparation.psp)))
586
586
  throw new CheckoutPreparationError(preparation.id, 'checkout_changed');
587
587
  if (rec.clientSideEncrypted && mode !== 'cse')
588
588
  throw new CardEncryptedError(rec.psp);
@@ -350,7 +350,7 @@ export declare const CHECKOUT_PREFLIGHT_CAPABILITIES: {
350
350
  readonly requirements: readonly ["verify_merchant_billing"];
351
351
  };
352
352
  };
353
- readonly limitations: readonly ["Four encrypted card fields in the Adyen Sessions payments body are required.", "Single-blob encryption, wallets, and stored-card variants are not covered."];
353
+ readonly limitations: readonly ["Four encrypted card fields in the Adyen Sessions payments body are required.", "Single-blob encryption, wallets, and stored-card variants are not covered.", "Adyen Web abandons its Sessions payment request 60 seconds after Pay. A prepared checkout moves the approval before Pay, so only the device-side encryption runs inside that window."];
354
354
  readonly unsupported_variants: readonly [{
355
355
  readonly variant: "single_blob_encryption";
356
356
  readonly status: "unsupported";
@@ -371,7 +371,7 @@ export declare const CHECKOUT_PREFLIGHT_CAPABILITIES: {
371
371
  readonly challenge_surface: "merchant_browser";
372
372
  readonly merchant_confirmation_required: true;
373
373
  readonly live_validation: "not_established_by_catalog";
374
- readonly sources: readonly ["src/definitions.js", "src/operations.js", "src/backend.js"];
374
+ readonly sources: readonly ["src/definitions.js", "src/operations.js", "src/backend.js", "src/adyen.ts", "../checkout/src/prepared-processor.ts"];
375
375
  }, {
376
376
  readonly psp: "tranzila";
377
377
  readonly status: "supported";
@@ -532,7 +532,8 @@ export const CHECKOUT_PREFLIGHT_CAPABILITIES = {
532
532
  },
533
533
  "limitations": [
534
534
  "Four encrypted card fields in the Adyen Sessions payments body are required.",
535
- "Single-blob encryption, wallets, and stored-card variants are not covered."
535
+ "Single-blob encryption, wallets, and stored-card variants are not covered.",
536
+ "Adyen Web abandons its Sessions payment request 60 seconds after Pay. A prepared checkout moves the approval before Pay, so only the device-side encryption runs inside that window."
536
537
  ],
537
538
  "unsupported_variants": [
538
539
  {
@@ -561,7 +562,9 @@ export const CHECKOUT_PREFLIGHT_CAPABILITIES = {
561
562
  "sources": [
562
563
  "src/definitions.js",
563
564
  "src/operations.js",
564
- "src/backend.js"
565
+ "src/backend.js",
566
+ "src/adyen.ts",
567
+ "../checkout/src/prepared-processor.ts"
565
568
  ]
566
569
  },
567
570
  {
@@ -2984,7 +2984,8 @@
2984
2984
  },
2985
2985
  "limitations": [
2986
2986
  "Four encrypted card fields in the Adyen Sessions payments body are required.",
2987
- "Single-blob encryption, wallets, and stored-card variants are not covered."
2987
+ "Single-blob encryption, wallets, and stored-card variants are not covered.",
2988
+ "Adyen Web abandons its Sessions payment request 60 seconds after Pay. A prepared checkout moves the approval before Pay, so only the device-side encryption runs inside that window."
2988
2989
  ],
2989
2990
  "unsupported_variants": [
2990
2991
  {
@@ -3013,7 +3014,9 @@
3013
3014
  "sources": [
3014
3015
  "src/definitions.js",
3015
3016
  "src/operations.js",
3016
- "src/backend.js"
3017
+ "src/backend.js",
3018
+ "src/adyen.ts",
3019
+ "../checkout/src/prepared-processor.ts"
3017
3020
  ]
3018
3021
  },
3019
3022
  {
@@ -497,7 +497,7 @@ export declare function getCheckoutPreflightCatalog(): {
497
497
  readonly requirements: readonly ["verify_merchant_billing"];
498
498
  };
499
499
  };
500
- readonly limitations: readonly ["Four encrypted card fields in the Adyen Sessions payments body are required.", "Single-blob encryption, wallets, and stored-card variants are not covered."];
500
+ readonly limitations: readonly ["Four encrypted card fields in the Adyen Sessions payments body are required.", "Single-blob encryption, wallets, and stored-card variants are not covered.", "Adyen Web abandons its Sessions payment request 60 seconds after Pay. A prepared checkout moves the approval before Pay, so only the device-side encryption runs inside that window."];
501
501
  readonly unsupported_variants: readonly [{
502
502
  readonly variant: "single_blob_encryption";
503
503
  readonly status: "unsupported";
@@ -518,7 +518,7 @@ export declare function getCheckoutPreflightCatalog(): {
518
518
  readonly challenge_surface: "merchant_browser";
519
519
  readonly merchant_confirmation_required: true;
520
520
  readonly live_validation: "not_established_by_catalog";
521
- readonly sources: readonly ["src/definitions.js", "src/operations.js", "src/backend.js"];
521
+ readonly sources: readonly ["src/definitions.js", "src/operations.js", "src/backend.js", "src/adyen.ts", "../checkout/src/prepared-processor.ts"];
522
522
  }, {
523
523
  readonly psp: "tranzila";
524
524
  readonly status: "supported";
@@ -1,6 +1,9 @@
1
- export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly';
1
+ export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen';
2
2
  export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
3
- /** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. */
3
+ /** How the approved card reaches the processor: the device's own request (token), or ciphertext the device produces for this browser to send (cse, Adyen). */
4
+ export type PreparationMode = 'token' | 'cse';
5
+ export declare function preparationMode(psp: string): PreparationMode;
6
+ /** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. Adyen can: test and live are separate hosts and key prefixes. */
4
7
  export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
5
8
  export declare function preparationEndpoint(psp: PreparationProcessor, environment: PreparationEnvironment): string;
6
9
  /** Processor identity and environment are part of the device's prior consent. */
@@ -1,15 +1,21 @@
1
+ import { adyenPreparationUrl, isPreparedAdyenRequest } from './adyen.generated.js';
1
2
  import { braintreeEnvironment, isPreparedBraintreeRequest, readTokenizationJson } from './braintree.js';
2
3
  import { isPreparedRecurlyRequest } from './recurly.generated.js';
3
4
  import { isPreparedSpreedlyRequest, isSpreedlyTokenRequest, SPREEDLY_TOKEN_ENDPOINT } from './spreedly.generated.js';
4
- /** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. */
5
+ export function preparationMode(psp) {
6
+ return psp === 'adyen' ? 'cse' : 'token';
7
+ }
8
+ /** Shared endpoint processors cannot attest test/live mode from their URL or key prefix. Adyen can: test and live are separate hosts and key prefixes. */
5
9
  export function validPreparationEnvironment(psp, environment) {
6
10
  if (psp === 'bambora' || psp === 'mercado_pago' || psp === 'recurly' || psp === 'spreedly')
7
11
  return environment === 'shared';
8
- return ['square', 'braintree', 'worldpay'].includes(psp) && ['production', 'sandbox'].includes(environment);
12
+ return ['square', 'braintree', 'worldpay', 'adyen'].includes(psp) && ['production', 'sandbox'].includes(environment);
9
13
  }
10
14
  export function preparationEndpoint(psp, environment) {
11
15
  if (!validPreparationEnvironment(psp, environment))
12
16
  throw new Error('unsupported_preparation_processor');
17
+ if (psp === 'adyen')
18
+ return adyenPreparationUrl(environment);
13
19
  if (psp === 'bambora')
14
20
  return 'https://api.bam.shift4api.net/scripts/tokenization/tokens';
15
21
  if (psp === 'mercado_pago')
@@ -70,6 +76,8 @@ function freshCardBody(psp, body) {
70
76
  export function matchesPreparedRequest(psp, environment, requestUrl, method, body) {
71
77
  if (method.toUpperCase() !== 'POST' || !validPreparationEnvironment(psp, environment))
72
78
  return false;
79
+ if (psp === 'adyen')
80
+ return isPreparedAdyenRequest(requestUrl, method, body ?? null, environment);
73
81
  if (psp === 'braintree')
74
82
  return braintreeEnvironment(requestUrl) === environment && isPreparedBraintreeRequest(body ?? null);
75
83
  if (psp === 'spreedly')
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "catalog_version": "2026-09-15.1",
4
- "catalog_sha256": "d47070197ee35cf4d02493c5f6c05446f11ff29a767061aa23c9670d871da5c1",
4
+ "catalog_sha256": "e1bbae82b91134c8be5919fddd5d60ea56f6897b570f8ccb2f305cd5b0a1c085",
5
5
  "baseline_version": "kernel-native-docs-2026-09-11.1",
6
6
  "baseline_sha256": "d86722f49633d8223fccae54cd3d485df4375aee06aa156a273cc689b7c8507c",
7
7
  "native_build": null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-cards/checkout",
3
- "version": "0.14.0",
3
+ "version": "0.15.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",