@agent-cards/sdk 0.20.0 → 0.21.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/dist/gmo.js ADDED
@@ -0,0 +1,119 @@
1
+ // GMO Payment Gateway, PG Multipayment token v2 (mp-token.js), as the SDK sees it.
2
+ //
3
+ // A GMO merchant page that calls Multipayment.init(shopId) with one argument (teamLab's ticket
4
+ // sites do) sends the card as mp-token.js's getToken: one fetch POST to
5
+ // https://token.mul-pay.jp/payment/CreateToken.json (test host stg.token.mul-pay.jp), as
6
+ // application/json with X-MP-Client-Type: js-legacy, whose body is AES ciphertext of the card with
7
+ // its key wrapped under GMO's shared public key. The agent's page encrypts the placeholder card, so
8
+ // nothing in the paused body can be filled in. The cardholder's device rebuilds the whole envelope
9
+ // with the real card and sends it to GMO itself, and the page receives only GMO's answer: one
10
+ // single-use token, valid 30 minutes and spendable only by the shop's own site (token mode).
11
+ //
12
+ // The registry entry ships dark (capability gmo_shared_pk). This build declares the capability on
13
+ // syncRegistry, and the API serves the entry only to an org Agentcard enabled for GMO, so for every
14
+ // other org nothing here fires: isCardRequest never recognizes a GMO URL and GMO's requests go
15
+ // through untouched, exactly as before.
16
+ //
17
+ // For an org in the dark launch, a POST the registry recognizes on a GMO token host is judged by
18
+ // payment-core's one guard (gmo.generated.ts isGmoSharedPkRequest), the check the API runs at
19
+ // create and the device runs before it rebuilds anything:
20
+ // - mp-token.js's SHARED_PK getToken for one token pauses for approval;
21
+ // - anything else on that URL can never be paid by the vault: UNIQUE_PK (a merchant-registered
22
+ // key nobody can check), GMO Elements (js-iframe), several tokens, a shop id of the other
23
+ // environment's shape, or any serialization mp-token.js does not write. It is aborted as an
24
+ // unsupported checkout before any create or prompt.
25
+ // Token v1 (ext/js/token.js) sends the card as a JSONP GET that no vault mode covers. It is aborted
26
+ // the same way, so the placeholder is never tokenized silently.
27
+ import { GMO_PSP, GMO_TOKEN_URLS, isGmoSharedPkRequest, isGmoTokenHost, sanitizeGmoTokenResponse, } from './gmo.generated.js';
28
+ export { GMO_PSP, isGmoTokenHost };
29
+ /** The dark-launch capability a build declares when it pauses only what isGmoSharedPkRequest accepts. */
30
+ export const GMO_CAPABILITY = 'gmo_shared_pk';
31
+ /** The hosts GMO token v1 (ext/js/token.js) sends its JSONP GET to: production, then test. */
32
+ const GMO_TOKEN_V1_HOSTS = ['p01.mul-pay.jp', 'stg.token.mul-pay.jp'];
33
+ const GMO_TOKEN_V1_PATH = '/ext/api/gettoken';
34
+ /**
35
+ * Fetch interception patterns for GMO token v1's JSONP GET, armed only for an org the API served GMO
36
+ * to. A CDP pattern matches the URL as written, path case included, while isGmoTokenV1Request
37
+ * accepts any path case, a trailing dot on the host and any port. So each v1 host is armed whole,
38
+ * in those four spellings, and the pre-claim gate classifies every paused request with
39
+ * isGmoTokenV1Request: the v1 GET is aborted, and anything else on the host continues untouched.
40
+ * Chrome writes the host in lower case, and it blocks a subresource URL that carries credentials.
41
+ */
42
+ export const GMO_TOKEN_V1_PATTERNS = GMO_TOKEN_V1_HOSTS.flatMap((host) => [`https://${host}/*`, `https://${host}:*`, `https://${host}./*`, `https://${host}.:*`]);
43
+ /**
44
+ * Whether the vault's current registry recognizes GMO's card request: true only after
45
+ * syncRegistry() for an org the API serves the dark entry to.
46
+ */
47
+ export function recognizesGmo(vault) {
48
+ if (!vault || typeof vault.isCardRequest !== 'function')
49
+ return false;
50
+ try {
51
+ return vault.isCardRequest(GMO_TOKEN_URLS.production, 'POST') || vault.isCardRequest(GMO_TOKEN_URLS.sandbox, 'POST');
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ /**
58
+ * GMO token v1's card request: a GET (or, with no method, a URL a route matcher is asked about)
59
+ * to /ext/api/getToken on either v1 host, in any case or with a trailing dot, since the servers
60
+ * behind it need not care.
61
+ */
62
+ export function isGmoTokenV1Request(rawUrl, method) {
63
+ if (method !== undefined && method.toUpperCase() !== 'GET')
64
+ return false;
65
+ let url;
66
+ try {
67
+ url = new URL(rawUrl);
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ if (url.protocol !== 'https:')
73
+ return false;
74
+ const host = url.hostname.toLowerCase().replace(/\.$/, '');
75
+ return GMO_TOKEN_V1_HOSTS.includes(host) && url.pathname.toLowerCase() === GMO_TOKEN_V1_PATH;
76
+ }
77
+ /**
78
+ * Whether a POST the registry recognized on a GMO token host is anything but mp-token.js's
79
+ * SHARED_PK getToken for one token: null when the guard accepts it (it pauses for approval), else
80
+ * the reason it is aborted. A guard that throws refuses.
81
+ */
82
+ export function gmoRequestRefusal(request) {
83
+ try {
84
+ return isGmoSharedPkRequest({ url: request.url, method: request.method.toUpperCase(), headers: request.headers ?? {}, body: request.body })
85
+ ? null : 'unsupported_gmo_request';
86
+ }
87
+ catch {
88
+ return 'unsupported_gmo_request';
89
+ }
90
+ }
91
+ /**
92
+ * The shop a GMO token request names (encryptionParameters.apiKey), as part of that request's
93
+ * identity. GMO mints a token for that one shop, so a retry that names another shop is a new
94
+ * question and never takes the first request's approval. Undefined when the URL is not a GMO token
95
+ * host; null when the body names no shop that can be read.
96
+ */
97
+ export function gmoRequestShop(url, body) {
98
+ if (!isGmoTokenHost(url))
99
+ return undefined;
100
+ try {
101
+ const shop = JSON.parse(body)?.encryptionParameters?.apiKey;
102
+ return typeof shop === 'string' ? shop : null;
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ /**
109
+ * The only answer the page may receive for an approved GMO request: status 200, and GMO's body
110
+ * reduced to the five fields mp-token.js reads with exactly one 64-hex token
111
+ * (sanitizeGmoTokenResponse), under a JSON content type. Null for anything else, including
112
+ * several tokens, which one approval never pays for.
113
+ */
114
+ export function gmoTokenAnswer(status, body) {
115
+ if (status !== 200)
116
+ return null;
117
+ const sanitized = sanitizeGmoTokenResponse(body);
118
+ return sanitized ? { status: 200, headers: { 'content-type': 'application/json' }, body: sanitized } : null;
119
+ }
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  export { Agentcard, Agentcard as VaultClient, CheckoutModule } from './agentcard.js';
2
2
  export type { CheckoutAttachOptions } from './agentcard.js';
3
- export { CardEncryptedError, UnsupportedModeError, PreparationRequiredError, ApprovalTimeoutError, ApprovalDeclinedError, AmountMismatchError, PresetRefusedError, PRESET_REFUSAL_REASONS, IntentNotConfirmableError, ProcessorRefusedError, CheckoutApiError, CheckoutAuthError, redactUrl, SUPPORTED_MODES, PaymentOutcomeUnknownError, CheckoutCancelledError, CheckoutPreparationError, AdyenTestPlatformRefusedError, MerchantTotalError, } from './client.js';
4
- export type { PausedRequest, ReplayResponse, TokenReplay, CseReplay, MerchantHostedReplay, HostedFormReplay, AmountAuthority, AuthorizeInput, VaultClientOptions, AgentcardOptions, AdyenTestPlatformRefusal, MerchantTotalRefusal, PrepareCheckoutOptions, PrepareCheckoutInput, PreparedCheckout, RazorpayProcessorError, RuntimeCancelReason, ExecutionMetadata, } from './client.js';
3
+ export { CardEncryptedError, UnsupportedModeError, UnsupportedCapabilityError, PreparationRequiredError, AmountRequiredError, ApprovalTimeoutError, ApprovalDeclinedError, AmountMismatchError, PresetRefusedError, PRESET_REFUSAL_REASONS, IntentNotConfirmableError, ProcessorRefusedError, CheckoutApiError, CheckoutAuthError, redactUrl, SUPPORTED_MODES, SUPPORTED_CAPABILITIES, PaymentOutcomeUnknownError, CheckoutCancelledError, CheckoutPreparationError, AdyenTestPlatformRefusedError, MerchantTotalError, PlaceholderCardError, } from './client.js';
4
+ export type { GmoUnsupportedReason } from './gmo.js';
5
+ export type { PausedRequest, ReplayResponse, TokenReplay, CseReplay, MerchantHostedReplay, HostedFormReplay, AmountAuthority, AuthorizeInput, VaultClientOptions, AgentcardOptions, AdyenTestPlatformRefusal, MerchantTotalRefusal, PrepareCheckoutOptions, PrepareCheckoutInput, PreparedCheckout, RazorpayProcessorError, RuntimeCancelReason, ExecutionMetadata, PlaceholderCard, } from './client.js';
6
+ export type { MicroformField, MicroformSidecar } from './cybersource-flex.js';
5
7
  export { attachToCdp, attachToPlaywright, corsHeadersFor, corsDecision, withCorsHeaders } from './cdp.js';
6
8
  export { CheckoutAttachmentError } from './attachment.js';
7
9
  export type { CdpLike, AttachOptions, CorsOutcome } from './cdp.js';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // The one client and its modules. `VaultClient` is the client's older name and
2
2
  // stays importable for one release; it constructs the same object.
3
3
  export { Agentcard, Agentcard as VaultClient, CheckoutModule } from './agentcard.js';
4
- export { CardEncryptedError, UnsupportedModeError, PreparationRequiredError, ApprovalTimeoutError, ApprovalDeclinedError, AmountMismatchError, PresetRefusedError, PRESET_REFUSAL_REASONS, IntentNotConfirmableError, ProcessorRefusedError, CheckoutApiError, CheckoutAuthError, redactUrl, SUPPORTED_MODES, PaymentOutcomeUnknownError, CheckoutCancelledError, CheckoutPreparationError, AdyenTestPlatformRefusedError, MerchantTotalError, } from './client.js';
4
+ export { CardEncryptedError, UnsupportedModeError, UnsupportedCapabilityError, PreparationRequiredError, AmountRequiredError, ApprovalTimeoutError, ApprovalDeclinedError, AmountMismatchError, PresetRefusedError, PRESET_REFUSAL_REASONS, IntentNotConfirmableError, ProcessorRefusedError, CheckoutApiError, CheckoutAuthError, redactUrl, SUPPORTED_MODES, SUPPORTED_CAPABILITIES, PaymentOutcomeUnknownError, CheckoutCancelledError, CheckoutPreparationError, AdyenTestPlatformRefusedError, MerchantTotalError, PlaceholderCardError, } from './client.js';
5
5
  export { attachToCdp, attachToPlaywright, corsHeadersFor, corsDecision, withCorsHeaders } from './cdp.js';
6
6
  export { CheckoutAttachmentError } from './attachment.js';
7
7
  export { substituteEncryptedFields, SubstitutionError } from './substitute.js';
@@ -1,6 +1,7 @@
1
1
  import { CheckoutPreparationError, type PrepareCheckoutOptions, type PreparedCheckout, type ReplayResponse } from './client.js';
2
2
  import type { CheckoutMode } from './registry.js';
3
3
  import type { Substitutions } from './substitute.js';
4
+ import type { GmoUnsupportedReason } from './gmo.js';
4
5
  /** The application's resolver confirmed the payment for this checkout authorization. */
5
6
  export interface MerchantPaymentConfirmation {
6
7
  kind: 'merchant_payment';
@@ -183,10 +184,14 @@ export declare class CheckoutLifecycle implements CheckoutController {
183
184
  /**
184
185
  * A card request this SDK cannot pay was aborted. `reason`: an unrecognized
185
186
  * declared payment endpoint (the default), a merchant profile Agentcard has not
186
- * turned on (merchant_profile_observe), or a card request the profile cannot
187
- * finish (merchant_request_unsupported).
187
+ * turned on (merchant_profile_observe), a card request the profile cannot
188
+ * finish (merchant_request_unsupported), a recognized processor's request in
189
+ * a form its gate refuses (GMO's unsupported_gmo_request and
190
+ * unsupported_gmo_token_v1), or a CyberSource Flex request that is not a
191
+ * reviewed Microform v2 card field's (microform_unsupported,
192
+ * cybersource-flex.ts).
188
193
  */
189
- unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported'): void;
194
+ unsupported(reason?: 'unrecognized_payment_endpoint' | 'merchant_profile_observe' | 'merchant_request_unsupported' | GmoUnsupportedReason | 'microform_unsupported'): void;
190
195
  /**
191
196
  * A card request that only a prepared checkout pays (an Adyen merchant
192
197
  * profile's, or a Fiserv card capture) arrived with no preparation on this
package/dist/lifecycle.js CHANGED
@@ -132,8 +132,12 @@ export class CheckoutLifecycle {
132
132
  /**
133
133
  * A card request this SDK cannot pay was aborted. `reason`: an unrecognized
134
134
  * declared payment endpoint (the default), a merchant profile Agentcard has not
135
- * turned on (merchant_profile_observe), or a card request the profile cannot
136
- * finish (merchant_request_unsupported).
135
+ * turned on (merchant_profile_observe), a card request the profile cannot
136
+ * finish (merchant_request_unsupported), a recognized processor's request in
137
+ * a form its gate refuses (GMO's unsupported_gmo_request and
138
+ * unsupported_gmo_token_v1), or a CyberSource Flex request that is not a
139
+ * reviewed Microform v2 card field's (microform_unsupported,
140
+ * cybersource-flex.ts).
137
141
  */
138
142
  unsupported(reason = 'unrecognized_payment_endpoint') {
139
143
  this.held = true;
@@ -3,6 +3,7 @@ import { MercadoCheckoutGate } from './mercado-checkout.js';
3
3
  import type { CheckoutLifecycle } from './lifecycle.js';
4
4
  import { type MerchantProfile } from './merchant-hosted.js';
5
5
  import type { MerchantHandoffWatch } from './merchant-handoff.js';
6
+ import { type CybersourceFlexDecision, type CybersourceFlexPause, type FlexRequest } from './cybersource-flex.js';
6
7
  /** The native Stripe step the gate claimed, or null when none was: exactly what StripeCheckoutGate.claim returns. */
7
8
  type StripeStep = ReturnType<StripeCheckoutGate['claim']>;
8
9
  /** A synthetic response a gate fulfills locally. The effect applies CORS and encoding. */
@@ -17,7 +18,8 @@ type PreClaimEvent = {
17
18
  detail?: unknown;
18
19
  };
19
20
  /** How the request was resolved. continue/answer/abort are already performed by the
20
- * time the decision is returned; only pause hands work back to the adapter. */
21
+ * time the decision is returned; only pause hands work back to the adapter, with the
22
+ * CyberSource Flex sidecar when the Flex gate paused it. */
21
23
  export type PreClaimDecision = {
22
24
  kind: 'continue';
23
25
  } | {
@@ -27,6 +29,7 @@ export type PreClaimDecision = {
27
29
  } | {
28
30
  kind: 'pause';
29
31
  stripeStep: StripeStep;
32
+ cybersourceFlex?: CybersourceFlexPause;
30
33
  };
31
34
  /** The adapter mechanics a gate performs. Each is awaited where the inline code
32
35
  * awaited it; none swallow their own transport failure (a gate adds a .catch
@@ -94,6 +97,14 @@ export interface PreClaimContext {
94
97
  stripeStep: StripeStep;
95
98
  /** The merchant-hosted hand-off this attachment made, if any (merchant-handoff.ts). */
96
99
  merchantHandoff?: Pick<MerchantHandoffWatch, 'retried'>;
100
+ /**
101
+ * The adapter's reader of the Microform field iframe that sent a request (cybersource-flex.ts):
102
+ * CDP judges from the committed frame URLs it recorded, Playwright from request.frame().url().
103
+ * Absent, no CyberSource Flex request can pause: each one the registry recognizes is refused.
104
+ */
105
+ cybersourceFlex?: {
106
+ judge(request: FlexRequest): CybersourceFlexDecision | Promise<CybersourceFlexDecision>;
107
+ };
97
108
  }
98
109
  /**
99
110
  * The URL an event may name for a request. For an armed merchant profile's
package/dist/pre-claim.js CHANGED
@@ -3,9 +3,12 @@
3
3
  // Before a paused request is claimed for the vault, a small set of processor
4
4
  // gates inspect it: Mercado Pago's native Checkout Pro configuration and
5
5
  // association, Braintree's shared /graphql endpoint, native Stripe Checkout,
6
- // and Fiserv's card capture. The first three used to be written inline in
7
- // Fetch.requestPaused and again in the page.route handler, so a decision could
8
- // drift between the two adapters and a new family had to be added in both places.
6
+ // Fiserv's card capture, GMO's CreateToken.json, which also carries requests
7
+ // the vault can never pay, and CyberSource Flex Microform's field iframes (whose
8
+ // capture context rides into the pause). The first three used to be written
9
+ // inline in Fetch.requestPaused and again in the page.route handler, so a
10
+ // decision could drift between the two adapters and a new family had to be
11
+ // added in both places.
9
12
  //
10
13
  // dispatchPreClaim runs the gates in order and resolves the request to one of:
11
14
  // - continue: let the request through untouched, or with a body the gate
@@ -38,8 +41,10 @@
38
41
  import { PaymentOutcomeUnknownError, redactUrl } from './client.js';
39
42
  import { classifyBraintreeRequest } from './braintree.js';
40
43
  import { StripeCheckoutClaimConflictError, stripeCheckoutReadinessError } from './stripe-checkout.js';
44
+ import { gmoRequestRefusal, isGmoTokenHost, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
41
45
  import { classifyProfileRequest, merchantBodyKeyPathSha256, profileTemplatedUrl } from './merchant-hosted.js';
42
46
  import { isFiservCaptureHost, isFiservPausableBody } from './fiserv.js';
47
+ import { CYBERSOURCE_FLEX_PSP, isCybersourceFlexHost } from './cybersource-flex.js';
43
48
  /**
44
49
  * The URL an event may name for a request. For an armed merchant profile's
45
50
  * endpoint or a sibling of it: the endpoint origin, the path TEMPLATE and the
@@ -104,10 +109,15 @@ async function mercadoContinuation(ctx) {
104
109
  // a Braintree POST is classified the same as an empty one, so both adapters
105
110
  // reach the same verdict. The configuration continue swallows a transport
106
111
  // failure, as the inline CDP path did (the Playwright path never reaches a
107
- // failing route here).
112
+ // failing route here). The body is read only for a Braintree host:
113
+ // classifyBraintreeRequest answers undefined for any other host before it looks
114
+ // at a body, so another processor's request is never read here, and a body read
115
+ // that throws (Playwright's postData() can) stays with the gate that owns it.
108
116
  const braintreePreClaim = (ctx) => {
109
- const { url, method, body } = ctx.request;
110
- const braintree = method.toUpperCase() === 'POST' ? classifyBraintreeRequest(url, method, body) : undefined;
117
+ const { url, method } = ctx.request;
118
+ if (method.toUpperCase() !== 'POST' || classifyBraintreeRequest(url, method, null) === undefined)
119
+ return undefined;
120
+ const braintree = classifyBraintreeRequest(url, method, ctx.request.body);
111
121
  if (braintree === 'configuration')
112
122
  return braintreeContinue(ctx);
113
123
  if (braintree === 'invalid')
@@ -311,10 +321,13 @@ async function merchantHostedUnsupported(ctx, profile, url, category, reason) {
311
321
  // Synchronous until it must continue or abort, like every gate. Exported for the
312
322
  // gate registry's tests.
313
323
  export const fiservPreClaim = (ctx) => {
314
- const { url, method, body } = ctx.request;
324
+ const { url, method } = ctx.request;
315
325
  if (String(method).toUpperCase() !== 'POST' || !isFiservCaptureHost(url) || !ctx.vault.isCardRequest(url, method))
316
326
  return undefined;
317
- if (!isFiservPausableBody(body))
327
+ // The body is read only here, on a capture host, since reading it may throw: another processor's
328
+ // request whose body the adapter cannot read then reaches the gate that owns it (GMO's aborts
329
+ // that request alone) or the arbiter, as it did before this gate joined the list.
330
+ if (!isFiservPausableBody(ctx.request.body))
318
331
  return fiservContinue(ctx);
319
332
  const idle = ctx.attachmentReady() && !ctx.stripeAttachmentBusy() && !ctx.interceptionAborted() && !ctx.terminal()
320
333
  && !ctx.lifecycle.isBlocked() && !ctx.awaitingApproval();
@@ -333,6 +346,119 @@ async function fiservPreparationRequired(ctx) {
333
346
  await ctx.effects.abort();
334
347
  return { kind: 'abort' };
335
348
  }
349
+ // GMO token v2 and v1 (gmo.ts). GMO's CreateToken.json also carries requests the vault can never
350
+ // pay, so a POST the registry recognizes on a GMO token host is judged here, before the terminal
351
+ // arbiter would pause it, by the whole-request guard the API runs at create:
352
+ // - mp-token.js's SHARED_PK getToken for one token defers, and the arbiter pauses it;
353
+ // - anything else (UNIQUE_PK, GMO Elements, several tokens, a shop id of the other
354
+ // environment's shape, another serialization) aborts as an unsupported checkout,
355
+ // `unsupported_checkout` with reason `unsupported_gmo_request`, with no claim, create or
356
+ // prompt. The attachment then reads `unsupported` and holds: the merchant's configuration
357
+ // decides that shape, so the page's next request would be the same.
358
+ // GMO token v1's JSONP GET to /ext/api/getToken aborts the same way (`unsupported_gmo_token_v1`),
359
+ // so the agent's placeholder is never tokenized silently. Nothing is decided without the registry:
360
+ // an SDK the API did not serve GMO (a build without the capability, or an org Agentcard has not
361
+ // enabled) recognizes neither, so the gate defers and every GMO request continues as before. The
362
+ // preflight OPTIONS and every other method on those URLs continue through the arbiter.
363
+ // A body the adapter could not read says nothing about the page's configuration, so that request
364
+ // alone is aborted with a `blocked` event, `unreadable_gmo_request`, and the attachment is not held.
365
+ // A body or headers whose read throws (Playwright's postData() can) is that same unread request:
366
+ // it is aborted here rather than rejecting the route handler with the request left undecided.
367
+ // Synchronous until the abort, so a card request pauses with no await. A guard that throws
368
+ // refuses the request rather than letting it reach a prompt.
369
+ const gmoPreClaim = (ctx) => {
370
+ const { url, method } = ctx.request;
371
+ if (method.toUpperCase() === 'POST' && isGmoTokenHost(url) && ctx.vault.isCardRequest(url, method)) {
372
+ let body;
373
+ let headers;
374
+ try {
375
+ body = ctx.request.body;
376
+ headers = ctx.request.headers;
377
+ }
378
+ catch {
379
+ return gmoUnreadable(ctx);
380
+ }
381
+ if (body === null)
382
+ return gmoUnreadable(ctx);
383
+ const reason = gmoRequestRefusal({ url, method, headers, body });
384
+ return reason === null ? undefined : gmoUnsupported(ctx, reason);
385
+ }
386
+ if (isGmoTokenV1Request(url, method) && recognizesGmo(ctx.vault))
387
+ return gmoUnsupported(ctx, 'unsupported_gmo_token_v1');
388
+ return undefined;
389
+ };
390
+ async function gmoUnreadable(ctx) {
391
+ ctx.onEvent?.({ type: 'blocked', detail: 'unreadable_gmo_request' });
392
+ await ctx.effects.abort();
393
+ return { kind: 'abort' };
394
+ }
395
+ async function gmoUnsupported(ctx, reason) {
396
+ const { url, method } = ctx.request;
397
+ ctx.preparationGate.invalidate('unsupported_checkout');
398
+ ctx.lifecycle.unsupported(reason);
399
+ ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: redactUrl(url), method, reason } });
400
+ await ctx.effects.abort();
401
+ return { kind: 'abort' };
402
+ }
403
+ // CyberSource Flex Microform (cybersource-flex.ts). A POST to a Flex host that the registry
404
+ // recognizes is this gate's to judge before the terminal arbiter, which would pause every one of
405
+ // them: the same tokens URL carries Microform v0.x and Flex API direct traffic, and only the
406
+ // field iframe's URL fragment says which card field sent it and under which capture context.
407
+ // - a check (eCheck) Microform: continue untouched, it carries no card;
408
+ // - not a field iframe's sealed card, a frame read and found to be no Microform field, an
409
+ // unreviewed version, or fields the device cannot rebuild: abort as unsupported, before any
410
+ // authorization, and hold the attachment (the page's next request would be judged the same way);
411
+ // - a capture context that does not verify, has expired, names another URL, or did not seal
412
+ // this body, a body the adapter could not read, or a frame it could not read: abort this
413
+ // request only, since the page's next request can pass (a freshly minted context, a frame
414
+ // read on the next try);
415
+ // - otherwise pause, carrying the sidecar the create sends and the request's retry shape.
416
+ // Nothing is decided without the registry: an SDK that did not sync with the capability, or whose
417
+ // org Agentcard has not enabled for CyberSource Flex (the API withholds the recognizer), never
418
+ // recognizes a Flex host, so the gate defers and the request continues as before. Synchronous
419
+ // once the context's verification settled (the adapters start it when the field iframe loads).
420
+ const FLEX_UNREADABLE = { kind: 'refuse', reason: 'microform_frame_unread' };
421
+ const cybersourceFlexPreClaim = (ctx) => {
422
+ const { url, method } = ctx.request;
423
+ if (method.toUpperCase() !== 'POST' || !isCybersourceFlexHost(url) || !ctx.vault.isCardRequest(url, method))
424
+ return undefined;
425
+ let decision;
426
+ try {
427
+ decision = ctx.cybersourceFlex ? ctx.cybersourceFlex.judge(ctx.request) : FLEX_UNREADABLE;
428
+ }
429
+ catch {
430
+ decision = FLEX_UNREADABLE;
431
+ }
432
+ return decision instanceof Promise
433
+ ? decision.then((settled) => cybersourceFlexResolve(ctx, settled), () => cybersourceFlexResolve(ctx, FLEX_UNREADABLE))
434
+ : cybersourceFlexResolve(ctx, decision);
435
+ };
436
+ function cybersourceFlexResolve(ctx, decision) {
437
+ if (decision.kind === 'pause')
438
+ return { kind: 'pause', stripeStep: ctx.stripeStep, cybersourceFlex: decision.flex };
439
+ if (decision.kind === 'continue')
440
+ return cybersourceFlexContinue(ctx);
441
+ if (decision.kind === 'unsupported')
442
+ return cybersourceFlexUnsupported(ctx, decision.reason);
443
+ return cybersourceFlexRefuse(ctx, decision.reason);
444
+ }
445
+ async function cybersourceFlexContinue(ctx) {
446
+ await ctx.effects.continueRequest().catch(() => { });
447
+ return { kind: 'continue' };
448
+ }
449
+ async function cybersourceFlexUnsupported(ctx, reason) {
450
+ const { url, method } = ctx.request;
451
+ ctx.preparationGate.invalidate('unsupported_checkout');
452
+ ctx.lifecycle.unsupported('microform_unsupported');
453
+ ctx.onEvent?.({ type: 'unsupported_checkout', detail: { url: redactUrl(url), method, processor: CYBERSOURCE_FLEX_PSP, reason } });
454
+ await ctx.effects.abort();
455
+ return { kind: 'abort' };
456
+ }
457
+ async function cybersourceFlexRefuse(ctx, reason) {
458
+ ctx.onEvent?.({ type: 'blocked', detail: reason });
459
+ await ctx.effects.abort();
460
+ return { kind: 'abort' };
461
+ }
336
462
  // The terminal arbiter, always last. A request that no gate claimed and that is
337
463
  // neither a native Stripe step nor a recognized card request continues, unless
338
464
  // it matches a declared payment endpoint we do not support (then it aborts as an
@@ -366,7 +492,7 @@ async function unsupportedContinue(ctx) {
366
492
  * arbiter runs after all of them and is not part of this list. A gate appended
367
493
  * after Stripe is never offered a step Stripe claimed: Stripe answers, aborts or
368
494
  * pauses each one itself. */
369
- export const PSP_PRE_CLAIM_GATES = [mercadoPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, fiservPreClaim];
495
+ export const PSP_PRE_CLAIM_GATES = [mercadoPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim];
370
496
  /** Run each gate in order; the first to return a decision wins. If none does,
371
497
  * the terminal arbiter decides between continue, abort (unsupported) and pause.
372
498
  * Returns synchronously while every gate defers synchronously (an ordinary card
@@ -2293,6 +2293,64 @@
2293
2293
  "hostname_pattern": "^p01\\.mul-pay\\.jp$",
2294
2294
  "pathname_pattern": "^\\/link\\/"
2295
2295
  },
2296
+ {
2297
+ "id": "cybersource-flex-microform-script",
2298
+ "psp": "cybersource_flex",
2299
+ "kinds": [
2300
+ "script"
2301
+ ],
2302
+ "origin": "https://flex.cybersource.com",
2303
+ "path_pattern": "/microform/bundle/v2{,.:minor.:patch}/flex-microform.min.js",
2304
+ "sources": [
2305
+ {
2306
+ "kind": "primary_source",
2307
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/flex-microform.min.js",
2308
+ "reviewed_on": "2026-09-23"
2309
+ },
2310
+ {
2311
+ "kind": "primary_source",
2312
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/iframe.min.js",
2313
+ "reviewed_on": "2026-09-23"
2314
+ },
2315
+ {
2316
+ "kind": "official_documentation",
2317
+ "reference": "https://developer.cybersource.com/docs/cybs/en-us/digital-accept-flex-api/developer/ctv/rest/flex-api/microform-integ-v2/microform-integ-getting-started-v2/creating-server-side-context-v2.html",
2318
+ "reviewed_on": "2026-09-23"
2319
+ }
2320
+ ],
2321
+ "hostname_pattern": "^flex\\.cybersource\\.com$",
2322
+ "pathname_pattern": "^\\/microform\\/bundle\\/v2(?:\\.[0-9]+\\.[0-9]+)?\\/flex-microform\\.min\\.js$"
2323
+ },
2324
+ {
2325
+ "id": "cybersource-flex-microform-field",
2326
+ "psp": "cybersource_flex",
2327
+ "kinds": [
2328
+ "frame",
2329
+ "iframe",
2330
+ "document"
2331
+ ],
2332
+ "origin": "https://flex.cybersource.com",
2333
+ "path_pattern": "/microform/bundle/v:version/iframe.html",
2334
+ "sources": [
2335
+ {
2336
+ "kind": "primary_source",
2337
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/flex-microform.min.js",
2338
+ "reviewed_on": "2026-09-23"
2339
+ },
2340
+ {
2341
+ "kind": "primary_source",
2342
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/iframe.min.js",
2343
+ "reviewed_on": "2026-09-23"
2344
+ },
2345
+ {
2346
+ "kind": "official_documentation",
2347
+ "reference": "https://developer.cybersource.com/docs/cybs/en-us/digital-accept-flex-api/developer/ctv/rest/flex-api/microform-integ-v2/microform-integ-getting-started-v2/creating-server-side-context-v2.html",
2348
+ "reviewed_on": "2026-09-23"
2349
+ }
2350
+ ],
2351
+ "hostname_pattern": "^flex\\.cybersource\\.com$",
2352
+ "pathname_pattern": "^\\/microform\\/bundle\\/v2\\.[0-9]+\\.[0-9]+\\/iframe\\.html$"
2353
+ },
2296
2354
  {
2297
2355
  "id": "gmo-mp-token-script-test",
2298
2356
  "psp": "gmo_pg",
@@ -2383,6 +2441,64 @@
2383
2441
  "hostname_pattern": "^pt01\\.mul-pay\\.jp$",
2384
2442
  "pathname_pattern": "^\\/link\\/"
2385
2443
  },
2444
+ {
2445
+ "id": "cybersource-flex-microform-script-test",
2446
+ "psp": "cybersource_flex",
2447
+ "kinds": [
2448
+ "script"
2449
+ ],
2450
+ "origin": "https://testflex.cybersource.com",
2451
+ "path_pattern": "/microform/bundle/v2{,.:minor.:patch}/flex-microform.min.js",
2452
+ "sources": [
2453
+ {
2454
+ "kind": "primary_source",
2455
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/flex-microform.min.js",
2456
+ "reviewed_on": "2026-09-23"
2457
+ },
2458
+ {
2459
+ "kind": "primary_source",
2460
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/iframe.min.js",
2461
+ "reviewed_on": "2026-09-23"
2462
+ },
2463
+ {
2464
+ "kind": "official_documentation",
2465
+ "reference": "https://developer.cybersource.com/docs/cybs/en-us/digital-accept-flex-api/developer/ctv/rest/flex-api/microform-integ-v2/microform-integ-getting-started-v2/creating-server-side-context-v2.html",
2466
+ "reviewed_on": "2026-09-23"
2467
+ }
2468
+ ],
2469
+ "hostname_pattern": "^testflex\\.cybersource\\.com$",
2470
+ "pathname_pattern": "^\\/microform\\/bundle\\/v2(?:\\.[0-9]+\\.[0-9]+)?\\/flex-microform\\.min\\.js$"
2471
+ },
2472
+ {
2473
+ "id": "cybersource-flex-microform-field-test",
2474
+ "psp": "cybersource_flex",
2475
+ "kinds": [
2476
+ "frame",
2477
+ "iframe",
2478
+ "document"
2479
+ ],
2480
+ "origin": "https://testflex.cybersource.com",
2481
+ "path_pattern": "/microform/bundle/v:version/iframe.html",
2482
+ "sources": [
2483
+ {
2484
+ "kind": "primary_source",
2485
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/flex-microform.min.js",
2486
+ "reviewed_on": "2026-09-23"
2487
+ },
2488
+ {
2489
+ "kind": "primary_source",
2490
+ "reference": "https://flex.cybersource.com/microform/bundle/v2.12.1/iframe.min.js",
2491
+ "reviewed_on": "2026-09-23"
2492
+ },
2493
+ {
2494
+ "kind": "official_documentation",
2495
+ "reference": "https://developer.cybersource.com/docs/cybs/en-us/digital-accept-flex-api/developer/ctv/rest/flex-api/microform-integ-v2/microform-integ-getting-started-v2/creating-server-side-context-v2.html",
2496
+ "reviewed_on": "2026-09-23"
2497
+ }
2498
+ ],
2499
+ "hostname_pattern": "^testflex\\.cybersource\\.com$",
2500
+ "pathname_pattern": "^\\/microform\\/bundle\\/v2\\.[0-9]+\\.[0-9]+\\/iframe\\.html$"
2501
+ },
2386
2502
  {
2387
2503
  "id": "square-web-sdk-sandbox",
2388
2504
  "psp": "square",
@@ -473,10 +473,14 @@
473
473
  "gmo-token-v1-script",
474
474
  "gmo-elements-card-frame",
475
475
  "gmo-link-hosted-page",
476
+ "cybersource-flex-microform-script",
477
+ "cybersource-flex-microform-field",
476
478
  "gmo-mp-token-script-test",
477
479
  "gmo-token-v1-script-test",
478
480
  "gmo-elements-card-frame-test",
479
481
  "gmo-link-hosted-page-test",
482
+ "cybersource-flex-microform-script-test",
483
+ "cybersource-flex-microform-field-test",
480
484
  "square-web-sdk-sandbox",
481
485
  "paysafe-js-test",
482
486
  "worldpay-checkout-sdk-try",
@@ -1010,10 +1014,14 @@
1010
1014
  "gmo-token-v1-script",
1011
1015
  "gmo-elements-card-frame",
1012
1016
  "gmo-link-hosted-page",
1017
+ "cybersource-flex-microform-script",
1018
+ "cybersource-flex-microform-field",
1013
1019
  "gmo-mp-token-script-test",
1014
1020
  "gmo-token-v1-script-test",
1015
1021
  "gmo-elements-card-frame-test",
1016
1022
  "gmo-link-hosted-page-test",
1023
+ "cybersource-flex-microform-script-test",
1024
+ "cybersource-flex-microform-field-test",
1017
1025
  "square-web-sdk-sandbox",
1018
1026
  "paysafe-js-test",
1019
1027
  "worldpay-checkout-sdk-try",
@@ -1,5 +1,5 @@
1
1
  // Generated from @agent-cards/payment-core. Do not edit.
2
- // source-sha256: 667b42af10e827f2c772927b1ff5491e5775a7d6b397ca72c8ddd093cdce2005
2
+ // source-sha256: 3a36f98936b5a93687dd4a052d1d4513935a093736b19475f023b5a0847c6b4a
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';
@@ -86,12 +86,26 @@ RULES.push({ id: 'gmo-mp-token-script', psp: 'gmo_pg', kinds: ['script'], hostna
86
86
  variant_unverified: 'GMO Elements takes the card in GMO\'s own iframes and encrypts it under a merchant-registered key; it is not admitted.' }, { id: 'gmo-link-hosted-page', psp: 'gmo_pg', kinds: ['document', 'frame', 'iframe', 'form'], hostname: /^p01\.mul-pay\.jp$/, pathname: /^\/link\//, origin: 'https://p01.mul-pay.jp', path_pattern: '/link/*',
87
87
  sources: [{ kind: 'official_documentation', reference: 'https://docs.gmo-pg.com/mulpay/docs/developer-support/test-environment/overview', reviewed_on: '2026-09-25' }],
88
88
  variant_unverified: 'A GMO Link-type payment page takes the card on GMO\'s own hosted page; it is not admitted.' });
89
+ // CyberSource Flex Microform v2 (dark: its processor declares a capability, so the bundled
90
+ // catalog does not list it and these signals stay unknown until a client opts in). The loader
91
+ // is served as /microform/bundle/v2/ and v2.x.y/; each card field is its own iframe document.
92
+ // Sources: the reviewed v2.12.1 bundles (loader sha256 32151372...32f6, field iframe 8429ebae...8327).
93
+ const cybersourceSources = [
94
+ { kind: 'primary_source', reference: 'https://flex.cybersource.com/microform/bundle/v2.12.1/flex-microform.min.js', reviewed_on: '2026-09-23' },
95
+ { kind: 'primary_source', reference: 'https://flex.cybersource.com/microform/bundle/v2.12.1/iframe.min.js', reviewed_on: '2026-09-23' },
96
+ { kind: 'official_documentation', reference: 'https://developer.cybersource.com/docs/cybs/en-us/digital-accept-flex-api/developer/ctv/rest/flex-api/microform-integ-v2/microform-integ-getting-started-v2/creating-server-side-context-v2.html', reviewed_on: '2026-09-23' },
97
+ ];
98
+ RULES.push({ id: 'cybersource-flex-microform-script', psp: 'cybersource_flex', kinds: ['script'], hostname: /^flex\.cybersource\.com$/, pathname: /^\/microform\/bundle\/v2(?:\.[0-9]+\.[0-9]+)?\/flex-microform\.min\.js$/, origin: 'https://flex.cybersource.com', path_pattern: '/microform/bundle/v2{,.:minor.:patch}/flex-microform.min.js',
99
+ sources: cybersourceSources }, { id: 'cybersource-flex-microform-field', psp: 'cybersource_flex', kinds: ['frame', 'iframe', 'document'], hostname: /^flex\.cybersource\.com$/, pathname: /^\/microform\/bundle\/v2\.[0-9]+\.[0-9]+\/iframe\.html$/, origin: 'https://flex.cybersource.com', path_pattern: '/microform/bundle/v:version/iframe.html',
100
+ sources: cybersourceSources });
89
101
  // Keep sandbox evidence truthful instead of labelling it with a production origin.
90
102
  for (const [baseId, suffix, hostname, origin] of [
91
103
  ['gmo-mp-token-script', 'test', /^stg\.static\.mul-pay\.jp$/, 'https://stg.static.mul-pay.jp'],
92
104
  ['gmo-token-v1-script', 'test', /^stg\.static\.mul-pay\.jp$/, 'https://stg.static.mul-pay.jp'],
93
105
  ['gmo-elements-card-frame', 'test', /^stg\.static\.mul-pay\.jp$/, 'https://stg.static.mul-pay.jp'],
94
106
  ['gmo-link-hosted-page', 'test', /^pt01\.mul-pay\.jp$/, 'https://pt01.mul-pay.jp'],
107
+ ['cybersource-flex-microform-script', 'test', /^testflex\.cybersource\.com$/, 'https://testflex.cybersource.com'],
108
+ ['cybersource-flex-microform-field', 'test', /^testflex\.cybersource\.com$/, 'https://testflex.cybersource.com'],
95
109
  ['square-web-sdk', 'sandbox', /^sandbox\.web\.squarecdn\.com$/, 'https://sandbox.web.squarecdn.com'],
96
110
  ['paysafe-js', 'test', /^hosted\.test\.paysafe\.com$/, 'https://hosted.test.paysafe.com'],
97
111
  ['worldpay-checkout-sdk', 'try', /^try\.access\.worldpay\.com$/, 'https://try.access.worldpay.com'],
@@ -1,4 +1,4 @@
1
- export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv';
1
+ export type PreparationProcessor = 'square' | 'braintree' | 'worldpay' | 'bambora' | 'mercado_pago' | 'recurly' | 'spreedly' | 'adyen' | 'checkout_com' | 'paysafe' | 'fiserv' | 'cybersource_flex';
2
2
  export type PreparationEnvironment = 'production' | 'sandbox' | 'shared';
3
3
  /**
4
4
  * How the approved card reaches the processor: the device's own request (token), or
@@ -10,7 +10,9 @@ export declare function preparationMode(psp: string): PreparationMode;
10
10
  /**
11
11
  * Shared endpoint processors cannot attest test/live mode from their URL or key prefix.
12
12
  * Adyen can: test and live are separate hosts and key prefixes. Fiserv's are separate
13
- * hosts too: connect (production) and connect-cert (sandbox).
13
+ * hosts too: connect (production) and connect-cert (sandbox). So are CyberSource
14
+ * Flex's: flex.cybersource.com is live, testflex.cybersource.com is test, and each has
15
+ * its own signing keys.
14
16
  */
15
17
  export declare function validPreparationEnvironment(psp: string, environment: string): boolean;
16
18
  export declare function preparationEndpoint(psp: PreparationProcessor, environment: PreparationEnvironment): string;