@agent-cards/sdk 0.24.1 → 0.24.2

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/cdp.js CHANGED
@@ -15,6 +15,7 @@ import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
15
15
  import { isToastPaymentMethodsUrl, readToastBody } from './toast.generated.js';
16
16
  import { PADDLE_VGS_PAY_PATTERNS, PaddleVgsPayGate, isPaddleCheckoutService, recognizesPaddleVgs } from './paddle-vgs-pay.js';
17
17
  import { DatatransRepeats } from './datatrans.js';
18
+ import { TokenRepeats } from './token-repeats.js';
18
19
  import { TokenexIframeRepeats, isArmedTokenexIframeUrl } from './tokenex-iframe.js';
19
20
  import { isArmedPaytrailUrl } from './paytrail.js';
20
21
  import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
@@ -2084,6 +2085,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2084
2085
  // The approved TokenEx iframe answers, per frameId (the card-number frame is an out-of-process iframe whose own
2085
2086
  // session pauses its post; its frameId names it): the repeat rule (tokenex-iframe.ts).
2086
2087
  const tokenexIframeRepeats = new TokenexIframeRepeats();
2088
+ // The last approved plain token answer, for the page's identical re-send (token-repeats.ts).
2089
+ const tokenRepeats = new TokenRepeats();
2087
2090
  // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
2088
2091
  const worldlineProducts = new WorldlineMyCheckoutProducts();
2089
2092
  // The Shiji STS getCvvId companion of the getTokenId this attachment last continued (shiji-sts.ts).
@@ -2905,6 +2908,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2905
2908
  juspay: juspayCheckout,
2906
2909
  datatrans: datatransRepeats,
2907
2910
  tokenexIframe: tokenexIframeRepeats,
2911
+ tokenRepeats,
2908
2912
  ebanx: ebanxFollowUps,
2909
2913
  payrexx: payrexxFollowUps,
2910
2914
  nikeOrders,
@@ -3446,6 +3450,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
3446
3450
  // The frame that received it: the paused request's, or its retry's once rebound.
3447
3451
  datatransRepeats.remember(activeRequest?.frameId || undefined, deliverRequest.url, deliverBody, replay);
3448
3452
  tokenexIframeRepeats.remember(activeRequest?.frameId || undefined, deliverRequest.url, deliverBody, replay);
3453
+ tokenRepeats.remember(activeRequest?.frameId || undefined, deliverRequest.url, deliverBody, replay);
3449
3454
  attempt.assertLive();
3450
3455
  // An ACI card-number request answered: its checkout's held CVC and expiry requests get the
3451
3456
  // same answer, and its /payment submission may continue (see AciGate).
@@ -3982,6 +3987,8 @@ export async function attachToPlaywright(page, opts) {
3982
3987
  const datatransRepeats = new DatatransRepeats();
3983
3988
  // The approved TokenEx iframe answers, per Frame: the repeat rule (tokenex-iframe.ts).
3984
3989
  const tokenexIframeRepeats = new TokenexIframeRepeats();
3990
+ // The last approved plain token answer, for the page's identical re-send (token-repeats.ts).
3991
+ const tokenRepeats = new TokenRepeats();
3985
3992
  // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
3986
3993
  const worldlineProducts = new WorldlineMyCheckoutProducts();
3987
3994
  const frameOf = (request) => { try {
@@ -4180,6 +4187,7 @@ export async function attachToPlaywright(page, opts) {
4180
4187
  juspay: juspayCheckout,
4181
4188
  datatrans: datatransRepeats,
4182
4189
  tokenexIframe: tokenexIframeRepeats,
4190
+ tokenRepeats,
4183
4191
  ebanx: ebanxFollowUps,
4184
4192
  payrexx: payrexxFollowUps,
4185
4193
  nikeOrders,
@@ -4586,6 +4594,7 @@ export async function attachToPlaywright(page, opts) {
4586
4594
  await deliverTo.fulfill({ status: replay.status, headers: withCorsHeaders(replay.headers, cors.headers), body: replay.body });
4587
4595
  datatransRepeats.remember(frameOf(deliverRequest), deliverRequest.url(), deliverBody, replay);
4588
4596
  tokenexIframeRepeats.remember(frameOf(deliverRequest), deliverRequest.url(), deliverBody, replay);
4597
+ tokenRepeats.remember(frameOf(deliverRequest), deliverRequest.url(), deliverBody, replay);
4589
4598
  assertRequestLive();
4590
4599
  // See the same step in attachToCdp.
4591
4600
  aci.answered(deliverRequest.url(), { status: replay.status, headers: replay.headers, body: replay.body }, replay.authorizationId);
package/dist/client.d.ts CHANGED
@@ -608,6 +608,15 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
608
608
  * judge. Served on the same terms: only to a build that lists it, for a company Agentcard turned Frisbii
609
609
  * on for.
610
610
  *
611
+ * `bigcommerce_card_form`: the payment BigCommerce's own card form (the card fields on
612
+ * payments.bigcommerce.com/pay/hosted_forms/<id>/field, or the store page's own inputs) sends as JSON to
613
+ * https://payments.bigcommerce.com/api/public/v1/orders/payments under the order's payment token, the card
614
+ * in payment.credit_card. This build pauses only a body that carries a new card there (payment-core
615
+ * processors/bigcommerce.js); a card saved at the store, another processor's token and a wallet continue
616
+ * untouched, and a card the store would keep fails in the browser before anything is created. The body
617
+ * names the store's total, which the cardholder approves. Served on the same terms: only to a build that
618
+ * lists it, for a company Agentcard turned BigCommerce on for.
619
+ *
611
620
  * `aps_custom_merchant_page`: Amazon Payment Services' custom merchant page, whose own card form
612
621
  * posts top-level to /FortAPI/paymentPage on checkout.payfort.com (sbcheckout.payfort.com on APS's
613
622
  * sandbox) with service_command=TOKENIZATION. This build pauses that navigation; the cardholder's
package/dist/client.js CHANGED
@@ -602,6 +602,15 @@ export const SUPPORTED_REGISTRY_FEATURES = ['card_fields', 'checkout_sessions'];
602
602
  * judge. Served on the same terms: only to a build that lists it, for a company Agentcard turned Frisbii
603
603
  * on for.
604
604
  *
605
+ * `bigcommerce_card_form`: the payment BigCommerce's own card form (the card fields on
606
+ * payments.bigcommerce.com/pay/hosted_forms/<id>/field, or the store page's own inputs) sends as JSON to
607
+ * https://payments.bigcommerce.com/api/public/v1/orders/payments under the order's payment token, the card
608
+ * in payment.credit_card. This build pauses only a body that carries a new card there (payment-core
609
+ * processors/bigcommerce.js); a card saved at the store, another processor's token and a wallet continue
610
+ * untouched, and a card the store would keep fails in the browser before anything is created. The body
611
+ * names the store's total, which the cardholder approves. Served on the same terms: only to a build that
612
+ * lists it, for a company Agentcard turned BigCommerce on for.
613
+ *
605
614
  * `aps_custom_merchant_page`: Amazon Payment Services' custom merchant page, whose own card form
606
615
  * posts top-level to /FortAPI/paymentPage on checkout.payfort.com (sbcheckout.payfort.com on APS's
607
616
  * sandbox) with service_command=TOKENIZATION. This build pauses that navigation; the cardholder's
@@ -109,6 +109,7 @@ export declare class CheckoutLifecycle implements CheckoutController {
109
109
  private orderRefusal;
110
110
  private readonly handoffs;
111
111
  private reconciliation;
112
+ private pendingHold;
112
113
  /**
113
114
  * EBANX sends this attachment claimed that are still out (pre-claim.ts ebanxFollowUp): the cardholder's device may be
114
115
  * sending a confirm that pays, outside `active`, so reconcile() and retryAfterMerchantFailure() wait for them too.
@@ -150,6 +151,15 @@ export declare class CheckoutLifecycle implements CheckoutController {
150
151
  isCancelled(): boolean;
151
152
  /** Whether the application gave a merchant resolver, so reconcile() reads the merchant's own record. */
152
153
  hasMerchantResolver(): boolean;
154
+ /**
155
+ * Whether the page's byte-identical re-send of the card request this approval answered may get the same answer
156
+ * (token-repeats.ts, pre-claim.ts tokenRepeatPreClaim): the checkout awaits the merchant on a plain token handoff
157
+ * that held nothing, and the only hold since is the merchant resolver reading the order as pending. A hold from
158
+ * anything else stands: an amount the processor charged that differs from the approval's, an unbound Stripe token,
159
+ * a hosted form or card endpoint, an integrator's requireMerchantResult, a follow-up send still out, an unsupported
160
+ * request, a cancel or an aborted request. The gate still asks the resolver before it answers.
161
+ */
162
+ repeatAnswerable(): boolean;
153
163
  /**
154
164
  * For a Datatrans re-submit once the approved answer reached the page (pre-claim.ts): whether the
155
165
  * application's merchant resolver still reports this approval's order pending. A read only: no
package/dist/lifecycle.js CHANGED
@@ -70,6 +70,8 @@ export class CheckoutLifecycle {
70
70
  // Every request this attachment handed the card to: see cardHandoffs().
71
71
  handoffs = [];
72
72
  reconciliation = null;
73
+ // The hold is only the merchant resolver's `pending` on a handoff nothing else held (resolve): see repeatAnswerable.
74
+ pendingHold = false;
73
75
  /**
74
76
  * EBANX sends this attachment claimed that are still out (pre-claim.ts ebanxFollowUp): the cardholder's device may be
75
77
  * sending a confirm that pays, outside `active`, so reconcile() and retryAfterMerchantFailure() wait for them too.
@@ -140,6 +142,18 @@ export class CheckoutLifecycle {
140
142
  isCancelled() { return this.cancelled; }
141
143
  /** Whether the application gave a merchant resolver, so reconcile() reads the merchant's own record. */
142
144
  hasMerchantResolver() { return typeof this.options.resolveMerchantResult === 'function'; }
145
+ /**
146
+ * Whether the page's byte-identical re-send of the card request this approval answered may get the same answer
147
+ * (token-repeats.ts, pre-claim.ts tokenRepeatPreClaim): the checkout awaits the merchant on a plain token handoff
148
+ * that held nothing, and the only hold since is the merchant resolver reading the order as pending. A hold from
149
+ * anything else stands: an amount the processor charged that differs from the approval's, an unbound Stripe token,
150
+ * a hosted form or card endpoint, an integrator's requireMerchantResult, a follow-up send still out, an unsupported
151
+ * request, a cancel or an aborted request. The gate still asks the resolver before it answers.
152
+ */
153
+ repeatAnswerable() {
154
+ return this.state.status === 'awaiting_merchant' && !!this.state.authorizationId && !this.active && !this.cancelled
155
+ && !this.merchantAborted && this.followUpsOut === 0 && (!this.held || this.pendingHold);
156
+ }
143
157
  /**
144
158
  * For a Datatrans re-submit once the approved answer reached the page (pre-claim.ts): whether the
145
159
  * application's merchant resolver still reports this approval's order pending. A read only: no
@@ -169,6 +183,10 @@ export class CheckoutLifecycle {
169
183
  }
170
184
  end() { this.active = false; }
171
185
  set(state) {
186
+ // Any state but awaiting the merchant (a challenge, a failure, a completion) ends the pending read's exception for
187
+ // good: a later pending read that brings awaiting_merchant back holds every card request again (repeatAnswerable).
188
+ if (state.status !== 'awaiting_merchant')
189
+ this.pendingHold = false;
172
190
  this.state = state;
173
191
  // Application telemetry must never interrupt a paused payment after it reaches the processor.
174
192
  try {
@@ -287,6 +305,7 @@ export class CheckoutLifecycle {
287
305
  */
288
306
  unsupported(reason = 'unrecognized_payment_endpoint') {
289
307
  this.held = true;
308
+ this.pendingHold = false;
290
309
  if (this.state.authorizationId)
291
310
  return; // do not hide an outstanding/completed payment behind a later unsupported request
292
311
  this.set({ ...this.state, status: 'unsupported', reason });
@@ -416,6 +435,7 @@ export class CheckoutLifecycle {
416
435
  if (this.merchantAborted)
417
436
  return;
418
437
  this.settled = true;
438
+ this.pendingHold = false;
419
439
  if (this.orderRefusal) {
420
440
  // Nike's order was refused while this approval was on its way: the answer lands, and the checkout stays failed.
421
441
  this.held = true;
@@ -591,6 +611,10 @@ export class CheckoutLifecycle {
591
611
  await this.requestUserAction(result.reason);
592
612
  }
593
613
  else {
614
+ // A pending order on a handoff nothing held still lets the page's identical re-send of the same card request have
615
+ // its answer again (repeatAnswerable); every other card request stays refused.
616
+ if (!this.held && result.status === 'pending' && this.state.status === 'awaiting_merchant')
617
+ this.pendingHold = true;
594
618
  this.held = true;
595
619
  this.set({ ...this.state, status: result.status === 'pending' ? 'awaiting_merchant' : 'outcome_unknown', reason: 'merchant_not_confirmed' });
596
620
  }
@@ -612,6 +636,7 @@ export class CheckoutLifecycle {
612
636
  if (this.unboundStripeToken)
613
637
  throw new Error('This attachment delivered an unbound Stripe token; reconcile the merchant order and use a separately validated checkout flow.');
614
638
  this.held = false;
639
+ this.pendingHold = false;
615
640
  this.merchantAborted = false;
616
641
  this.orderRefusal = null;
617
642
  this.set({ status: 'idle', authorizationId: null });
@@ -15,6 +15,7 @@ import { type CardEndpointProfile } from './card-endpoint.js';
15
15
  import { type NikeOrders } from './nike-order.js';
16
16
  import { type PayrailsSessionCapture, type PayrailsSidecar } from './payrails.js';
17
17
  import { type VerifoneProfile } from './verifone.js';
18
+ import type { TokenRepeats } from './token-repeats.js';
18
19
  import { type WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
19
20
  import { type PaddleVgsPayGate } from './paddle-vgs-pay.js';
20
21
  import { type EbanxFollowUps } from './ebanx.js';
@@ -210,6 +211,8 @@ export interface PreClaimContext {
210
211
  datatrans?: DatatransRepeats;
211
212
  /** The attachment's approved TokenEx iframe answers (the repeat rule, tokenex-iframe.ts). Absent: none are kept. */
212
213
  tokenexIframe?: TokenexIframeRepeats;
214
+ /** The attachment's last approved plain token answer, for the page's identical re-send (token-repeats.ts). Absent: none is kept. */
215
+ tokenRepeats?: TokenRepeats;
213
216
  /** The adapter's identity for the request's frame (CDP frameId, Playwright Frame); undefined when unknown. */
214
217
  frame?: unknown;
215
218
  /**
package/dist/pre-claim.js CHANGED
@@ -1002,6 +1002,50 @@ const tokenexIframePreClaim = (ctx) => {
1002
1002
  return ctx.lifecycle.getState().status === 'awaiting_merchant'
1003
1003
  ? repeatAfterHandoff(ctx, repeat, TOKENEX_IFRAME_PSP) : answerRepeat(ctx, repeat, TOKENEX_IFRAME_PSP);
1004
1004
  };
1005
+ // Any processor's plain token request, sent again by the page byte for byte after its approved answer reached it
1006
+ // (token-repeats.ts). Last among the gates, so a processor with its own repeat rule (Datatrans, the TokenEx iframe) or
1007
+ // its own hold (Stripe, a card endpoint) decides first. While the lifecycle reads the re-send as answerable
1008
+ // (repeatAnswerable: the same approval awaits the merchant, and nothing but a pending read holds it), the gate asks the
1009
+ // merchant resolver and answers with the stored token only while the order still reads pending, as repeatAfterHandoff
1010
+ // does. Every other card request, another amount or approval among them, defers to the terminal arbiter and the
1011
+ // adapters' hold, as before.
1012
+ const tokenRepeatPreClaim = (ctx) => {
1013
+ const { url, method } = ctx.request;
1014
+ if (!ctx.tokenRepeats || method.toUpperCase() !== 'POST' || !ctx.lifecycle.repeatAnswerable?.() || !ctx.lifecycle.hasMerchantResolver?.())
1015
+ return undefined;
1016
+ // A processor with its own repeat rule keeps its own limits: a re-send its rule declined is a new question.
1017
+ if (isDatatransHost(url) || isTokenexIframePath(url))
1018
+ return undefined;
1019
+ if (ctx.terminal() || ctx.interceptionAborted() || ctx.lifecycleAborted())
1020
+ return undefined;
1021
+ // Read only here, past every cheaper check: a body the adapter cannot read is no re-send of an answered one.
1022
+ let body;
1023
+ try {
1024
+ body = ctx.request.body;
1025
+ }
1026
+ catch {
1027
+ return undefined;
1028
+ }
1029
+ const repeats = ctx.tokenRepeats;
1030
+ const repeat = repeats.answerFor(ctx.frame, url, body, ctx.lifecycle.getState().authorizationId);
1031
+ if (!repeat)
1032
+ return undefined;
1033
+ // A re-send the page never got an answer for gives its place in the count back.
1034
+ return tokenRepeatWhilePending(ctx, repeat).then((decision) => {
1035
+ if (decision?.kind !== 'answer')
1036
+ repeats.released(repeat.authorizationId);
1037
+ return decision;
1038
+ }, (error) => { repeats.released(repeat.authorizationId); throw error; });
1039
+ };
1040
+ async function tokenRepeatWhilePending(ctx, repeat) {
1041
+ if (!await ctx.lifecycle.merchantOrderPending())
1042
+ return undefined;
1043
+ // The resolver answered asynchronously: the same approval must still be the one awaiting the merchant.
1044
+ if (!ctx.lifecycle.repeatAnswerable() || ctx.lifecycle.getState().authorizationId !== repeat.authorizationId
1045
+ || ctx.terminal() || ctx.interceptionAborted() || ctx.lifecycleAborted())
1046
+ return undefined;
1047
+ return answerRepeat(ctx, repeat, 'token');
1048
+ }
1005
1049
  async function tokenexIframeBlock(ctx, reason) {
1006
1050
  ctx.onEvent?.({ type: 'blocked', detail: reason });
1007
1051
  await ctx.effects.abort();
@@ -2623,7 +2667,7 @@ async function unsupportedContinue(ctx) {
2623
2667
  * pauses each one itself. */
2624
2668
  export const PSP_PRE_CLAIM_GATES = [cardEndpointResendPreClaim, cardEndpointPreClaim, nikeOrderPreClaim, mercadoPreClaim, juspayPreClaim, braintreePreClaim, stripePreClaim, merchantHostedPreClaim, verifonePreClaim, fiservPreClaim, gmoPreClaim, cybersourceFlexPreClaim, datatransPreClaim, tokenexIframePreClaim, pelecardPreClaim, aciPreClaim, creditguardPreClaim, primerPreClaim, payrailsPreClaim, convergePreClaim, eprotectPreClaim, sbpsPreClaim, wspayPreClaim, shijiStsPreClaim, ekashuPreClaim, redsysPreClaim, quickpayPreClaim, comgatePreClaim, przelewy24PreClaim, transbankPreClaim, eximbayPreClaim, barionPreClaim, payplugPreClaim, cardlinkVposPreClaim, eghlPreClaim, paypalHostedFieldsPreClaim,
2625
2669
  paypalCardFormsPreClaim, cybersourceSecureAcceptancePreClaim, worldlineMyCheckoutPreClaim, paddleVgsPayPreClaim, ebanxPreClaim, payrexxPreClaim, hipayPreClaim, paytrailPreClaim,
2626
- oceanpaymentPreClaim, aurusPreClaim];
2670
+ oceanpaymentPreClaim, aurusPreClaim, tokenRepeatPreClaim];
2627
2671
  /** Run each gate in order; the first to return a decision wins. If none does,
2628
2672
  * the terminal arbiter decides between continue, abort (unsupported) and pause.
2629
2673
  * Returns synchronously while every gate defers synchronously (an ordinary card
@@ -3522,6 +3522,31 @@
3522
3522
  "hostname_pattern": "^payment\\.pay1\\.checkout\\.worldline-solutions\\.com$",
3523
3523
  "pathname_pattern": "^\\/checkout$"
3524
3524
  },
3525
+ {
3526
+ "id": "bigcommerce-hosted-card-field",
3527
+ "psp": "bigcommerce",
3528
+ "kinds": [
3529
+ "document",
3530
+ "frame",
3531
+ "iframe"
3532
+ ],
3533
+ "origin": "https://payments.bigcommerce.com",
3534
+ "path_pattern": "/pay/hosted_forms/:form/field",
3535
+ "sources": [
3536
+ {
3537
+ "kind": "saved_observation",
3538
+ "reference": "drsturm-2026-11-06:bigcommerce-hosted-form-field-1.997.1",
3539
+ "reviewed_on": "2026-11-06"
3540
+ },
3541
+ {
3542
+ "kind": "primary_source",
3543
+ "reference": "https://checkout-sdk.bigcommerce.com/v1/loader-v1.997.1.js",
3544
+ "reviewed_on": "2026-11-06"
3545
+ }
3546
+ ],
3547
+ "hostname_pattern": "^payments\\.bigcommerce\\.com$",
3548
+ "pathname_pattern": "^\\/pay\\/hosted_forms\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\/field$"
3549
+ },
3525
3550
  {
3526
3551
  "id": "gmo-mp-token-script-test",
3527
3552
  "psp": "gmo_pg",
@@ -525,6 +525,7 @@
525
525
  "amadeus-data-collection-page",
526
526
  "worldline-mycheckout-page",
527
527
  "worldline-mycheckout-card-form",
528
+ "bigcommerce-hosted-card-field",
528
529
  "gmo-mp-token-script-test",
529
530
  "gmo-token-v1-script-test",
530
531
  "gmo-elements-card-frame-test",
@@ -1170,6 +1171,7 @@
1170
1171
  "amadeus-data-collection-page",
1171
1172
  "worldline-mycheckout-page",
1172
1173
  "worldline-mycheckout-card-form",
1174
+ "bigcommerce-hosted-card-field",
1173
1175
  "gmo-mp-token-script-test",
1174
1176
  "gmo-token-v1-script-test",
1175
1177
  "gmo-elements-card-frame-test",
@@ -1,5 +1,5 @@
1
1
  // Generated from @agent-cards/payment-core. Do not edit.
2
- // source-sha256: 1ac6ffa819d0bb72040d0a95cac79f780aeae4f5dea22599fbc4c5ec6c6c8687
2
+ // source-sha256: 84246b0b75dadb1798e0c9bd4ab7a8ab8567df86ea490fb20c9f0f890309e7df
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';
@@ -371,6 +371,15 @@ RULES.push({ id: 'worldline-mycheckout-page', psp: 'worldline_mycheckout', kinds
371
371
  pathname: /^\/checkout\/(?:payment-product-details\/)?[1-9][0-9]{0,9}-[0-9a-f]{32}:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}:[0-9a-f]{32}$/,
372
372
  origin: 'https://payment.pay1.checkout.worldline-solutions.com', path_pattern: '/checkout/{payment-product-details/}:hosted_checkout', sources: worldlineSources }, { id: 'worldline-mycheckout-card-form', psp: 'worldline_mycheckout', kinds: ['form'], hostname: /^payment\.pay1\.checkout\.worldline-solutions\.com$/, pathname: /^\/checkout$/,
373
373
  origin: 'https://payment.pay1.checkout.worldline-solutions.com', path_pattern: '/checkout', sources: worldlineSources });
374
+ // BigCommerce's own card form, read 2026-11-06 at drsturm.com: the store's /checkout/payment/hosted-field iframes
375
+ // redirect to one field document per card input on payments.bigcommerce.com, which loads checkout-sdk's hosted-form
376
+ // module and sends the card to BigCommerce's payments API (processors/bigcommerce.js), whichever gateway charges it.
377
+ // BigCommerce ships dark, so the field names bigcommerce and the result stays unknown, as GMO's does.
378
+ RULES.push({ id: 'bigcommerce-hosted-card-field', psp: 'bigcommerce', kinds: ['document', 'frame', 'iframe'], hostname: /^payments\.bigcommerce\.com$/,
379
+ pathname: /^\/pay\/hosted_forms\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\/field$/, origin: 'https://payments.bigcommerce.com',
380
+ path_pattern: '/pay/hosted_forms/:form/field',
381
+ sources: [{ kind: 'saved_observation', reference: 'drsturm-2026-11-06:bigcommerce-hosted-form-field-1.997.1', reviewed_on: '2026-11-06' },
382
+ { kind: 'primary_source', reference: 'https://checkout-sdk.bigcommerce.com/v1/loader-v1.997.1.js', reviewed_on: '2026-11-06' }] });
374
383
  // A processor that ships dark (its definition declares a capability) is left out of the bundled
375
384
  // catalog. Datatrans's and the TokenEx iframe's discovery rules wait with it: they join RULES only once KNOWN_PSPS, the
376
385
  // bundled catalog's processors, lists its psp. Until then its assets are not evidence, so a page