@agent-cards/sdk 0.23.2 → 0.23.4

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,12 +2,28 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Stop PayPal's legacy Hosted Fields from sending the card your agent typed to PayPal's payment confirm. A checkout that loads PayPal's JS SDK with `components=hosted-fields`, such as Planet Golf UK's PayPal Checkout, takes the card in frames on `assets.braintreegateway.com` and sends it to `https://cors.api.paypal.com/v2/checkout/orders/{id}/confirm-payment-source` (`cors.api.sandbox.paypal.com` in PayPal's sandbox). No Agentcard payment takes that request over, and earlier releases let it through, so PayPal received the dummy card and declined it. Both adapters now fail that request in the browser for every company, whatever `syncRegistry()` served, and the controller reads `unsupported` with reason `unsupported_paypal_hosted_fields` and holds, like every `unsupported` reason: to pay another way on the same page, attach again. A confirm with no card number, such as a PayPal wallet payment, continues untouched. A confirm whose body the SDK cannot read is failed with a `blocked` event, `unreadable_paypal_request`, and the checkout is not held. A merchant that turns on PayPal's card installments also has the card number field send each typed number to PayPal's installment lookup, `/v1/credit/calculated-financing-options`; the SDK leaves that lookup alone, and it pays nothing. TypeScript: the controller's `unsupported()` reasons gain `unsupported_paypal_hosted_fields`; code that switches exhaustively over them needs the new case.
6
+
7
+ - Name PayPal's legacy Hosted Fields as PayPal in a preflight inspection, not as Braintree. Their card fields are braintree-web builds whose version carries `-payments-sdk` (`3.32.0-payments-sdk-dev` on Planet Golf UK), and inspection read the page as `braintree` with status `supported`. The frames now name `paypal`, and the result stays `unknown` with `processor_variant_unverified`. A braintree-web script from a `-payments-sdk` build is no longer Braintree evidence either, and a variant seen in several frames lists its limitation once. The catalog's `catalog_sha256` changes; `catalog_version` stays `2026-09-15.1`.
8
+
9
+ - Stop the card request AT&T's checkout sends today. AT&T's page posts the card to `/msapi/checkoutorch/v1/mfe/paymentInfo` with no query, and the adapters armed only the exporch form, `/msapi/checkoutexporch/v1/paymentInfo?cartId=`, which none of the checkouts Agentcard read live used, so the agent's dummy card reached AT&T. The adapters now pause the request AT&T sends and refuse it as `unsupported_checkout` while AT&T's profile is in `observe`. The exporch form is a profile of its own, `att_exporch_payment_info`, refused the same way, so a cart AT&T moves there is stopped too. AT&T's card-on-file step, which posts the typed card to `cardOnFile` on either base, is aborted in every status as `card_endpoint_refused` with reason `card_resend`, and so is the exporch form with no query, with its card-on-file step: AT&T's page sends both when its checkout names no cart id, and no payment can cover them. AT&T's profile reads the body its page builds: no `X-CSRF-TOKEN` on a guest checkout, `type` `creditcard` or `debitcard`, `statusCode` and `transactionId` as null, `cardSubType` one of `VISA`, `MASTERCARD`, `AMEX` or `DISCOVER`, a two-digit month and a four-digit year.
10
+
11
+ - Recognize Target Optical's card request as its checkout sends it today: a JSON `PUT` to `/wcs/resources/store/12001/cart/@self/addPayment` with the page's `nocache` query, the total in `piAmount` with five decimals (`"49.42000"`). The profile described the form post Target Optical's older checkout sent, which no request matches now, so the adapters let the dummy card reach Target Optical. The profile stays in observe: both adapters now pause that request and refuse it as `unsupported_checkout` before any authorization, let the saved-card body to the same URL through untouched, and abort the card save a signed-in shopper's page sends to `/wcs/resources/store/12001/mypayments` in every status. They also abort, in every status, a post to `/AjaxOrderChangeServicePIAddTO`, the form endpoint the older checkout used, which they refused before and which the storefront still declares in a script it serves, so moving the profile to the `PUT` leaves no path where the dummy card reaches Target Optical. `attachToPlaywright` routes a reviewed card endpoint's request by every method the profiles name; it asked only for `POST`, so a `PUT` card request or resend never reached its gates. A card endpoint create names the profile's own method in `request.method`; an API that reads every card endpoint request as a `POST` refuses a `PUT` create with `invalid_request`, so nothing is charged. TypeScript: `CardEndpointProfile.method` can be `PUT`.
12
+
13
+ - Pay Cinemark and Dick's Sporting Goods' own storefront, two reviewed Adyen merchants that take the card on their own server. Both profiles, `cinemark_initiate` and `dsg_paygate_v2`, ship `enabled`, and the API serves them `enabled` only to a client of a company Agentcard turned them on for. For every other client the adapters keep pausing and aborting their card requests as `merchant_profile_observed`, as before. A payment there needs a preparation for the profile and the cardholder's approval in the Vault, and Autopilot never pays one. The create carries the merchant's own checkout total as your agent's browser read it, and the API refuses a payment whose amount is not that total. Dick's paygate at Golf Galaxy, Public Lands and Going Going Gone moves to its own profile, `dsg_other_stores_paygate_v2`, which ships in `observe`, so the adapters abort those card requests as before. Matching API and Vault releases are required.
14
+
5
15
  - Stop the card fields adyen-web 5 encrypts at a reviewed Adyen merchant profile. adyen-web 5 seals each field as an RSA-OAEP / A256CBC-HS512 JWE, where adyen-web 6 writes RSA-OAEP-256 / A256GCM, and the SDK read only adyen-web 6's form. So the card requests of Cathay Pacific's payment page and Dunelm's legacy checkout, and Cathay's charge request after its signing step, reached the merchant with the agent's dummy card. The adapters now read both forms as a card. Both profiles ship in `observe`, so the adapters abort each of these requests before it leaves the browser and report it as `merchant_profile_observed`. The Vault writes only adyen-web 6's form, so the API refuses a create for a card request in adyen-web 5's form with `cse_template_unsupported`, and nothing is charged.
6
16
 
7
17
  - Abort the requests in which IHG's pay page sends the typed card outside its reviewed card request, in every status, as `card_endpoint_refused` with reason `card_resend`. The page posts the same booking to IHG's enrollment service while its pre-ticked "Enroll for free" box stays ticked, posts it on other paths for more than one room, a travel agent or Business Rewards, and at a hotel that asks for 3-D Secure first posts the number to two 3-D Secure calls. The SDK let each of these reach IHG with the agent's dummy card. A booking with the box unticked reaches IHG's reviewed card request, which the adapters refuse as before while IHG is in `observe`.
8
18
 
9
19
  - Read IHG's booking at a hotel that asks for no security code as IHG's reviewed card request. At the two US hotels a live check visited, the pay page shows no security code field and sends `cvvNumber: {token: ""}`. The adapters read that as a card in a body IHG's profile did not review and refused it with reason `unrecognized_payment_endpoint`, so the booking could never pause for the cardholder. They now refuse it with reason `merchant_card_endpoint_not_enabled`, naming IHG's profile, while IHG is in `observe`, and once Agentcard enables IHG it pauses and asks the cardholder like any other IHG booking. A security code the hotel asks for is still read as three or four digits, and any other value there is refused as before.
10
20
 
21
+ - Read a hotel booking at IHG, Hilton or The Verb Hotel as the hotel's reviewed card request only at a rate the hotel charges in full at booking: IHG's Book Early & Save (`ratePlanCode` `IDAPF`) in the hotel's own currency, a Hilton rate guaranteed `FP` with a deposit, and The Verb Hotel's Non-Refundable rate (`14DAYAP`). The adapters now refuse a rate that holds the card and charges it at the stay, an IHG group rate, an IHG booking that names `hotels[0].bookingCurrency` (the shopper's currency, which IHG's page sends only when the hotel prices in another one) and a Hilton booking with its pre-ticked "Join Hilton Honors for free" left on (`variables.enrollment`) with reason `unrecognized_payment_endpoint` and an event that names only the endpoint, where they refused each as `merchant_card_endpoint_not_enabled` naming the profile. All three hotels stay in `observe`, so the adapters refuse every booking there before any authorization, as before. IHG's and Hilton's profiles now carry the card network codes their pages write (VS, MC, AX and DS at IHG; VI, MC, AX and DS at Hilton): a body that names another code is unrecognized, and once a hotel is enabled the cardholder's device writes the code of the real card's network and refuses a card on any other. Hilton's profile reads `charge_timing: 'at_request'`, since Hilton charges its prepaid rate at booking. Italo's profile no longer waits on how its page sends `expire`, which the live checks of Sep 30 and Oct 6 read as left out.
22
+
23
+ - Stop Universal Orlando's card request at the checkout it runs today. Universal moved its store from `www.universalorlando.com/web-store` to `store.universalorlando.com`, whose guest checkout posts the card to `secure-api.universalparks.com/instances/{guest}/carts/{cart}/precheckout`. The SDK's profile still described the web-store request, so it let this one through and the agent's dummy card reached Universal. The adapters now refuse it before it leaves the browser with reason `merchant_card_endpoint_not_enabled`, naming Universal's profile, while Universal is in `observe`. They also abort the store's two other posts of the typed card, a signed-in customer's precheckout and the Cardinal 3-D Secure token call Universal keeps off today, in every status, as `card_endpoint_refused` with reason `card_resend`. The retired web-store checkout still loads from a deep link such as `www.universalorlando.com/web-store/en/us/checkout`, and the adapters abort its two posts of the typed card the same way: a guest's `/instances/{guest}/commerce-checkout`, the request they refused before this release, and a signed-in customer's `/guests/{guest}/commerce-checkout`. Once Agentcard enables Universal, the API takes one payment per cart and refuses another create for it with `409 merchant_checkout_in_use`.
24
+
25
+ - Read the card number on the D-EDGE card frame the way the frame groups each card network: 4-6-5 for an American Express number, 4-6-4 for a Diners Club number starting 36 or 300 to 305, and groups of four for any other. The SDK's copy of D-EDGE's rules read a grouped number only in groups of four, so an American Express or Diners Club placeholder as the frame groups it was not D-EDGE's reviewed card request, and the same number in groups of four, which the frame never posts, was. D-EDGE stays in `observe`, so its card post still gets the page that says Agentcard has not turned it on.
26
+
11
27
  ## 0.23.0
12
28
 
13
29
  ### Upgrading from 0.22.0
package/PREFLIGHT.md CHANGED
@@ -207,6 +207,8 @@ The bundled direct SDK profile declares processor support for `one_time` purchas
207
207
 
208
208
  The catalog also carries reviewed hosted, regional, and versioned variants observed 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. A variant the implemented adapter does not admit, such as the Tranzila terminal-root page, the Authorize.Net hosted payment form, or a Worldpay hosted payment page, identifies its processor and returns `unknown` with `processor_variant_unverified`. A new fingerprint never declares payment support by itself.
209
209
 
210
+ PayPal's legacy Hosted Fields (`paypal`) are identified from the card fields PayPal's JS SDK renders with `components=hosted-fields`: braintree-web builds whose version carries `-payments-sdk`, such as `3.32.0-payments-sdk-dev`, served from `assets.braintreegateway.com` as `hosted-fields-input-frame.min.html` and `hosted-fields-payments-sdk-tokenization-frame.min.html`. PayPal's frames send the card to PayPal, not to Braintree, so a page that shows them names `paypal` and returns `unknown` with `processor_variant_unverified`, and no braintree-web file from a `-payments-sdk` build counts as Braintree evidence. The SDK fails the card request those frames send; see the PayPal Hosted Fields row in the [README's processor table](./README.md#supported-processors).
211
+
210
212
  Adyen's `supported` covers the Sessions payment request on Adyen's own hosts. Some Adyen merchants post the four encrypted fields to their own server instead, and their pages load the same secured fields, so an inspection identifies Adyen on both and cannot tell the two flows apart. The SDK pays a merchant's own endpoint only through a merchant profile Agentcard reviewed for it; the reviewed profiles (Dunelm and Cinemark) are in `observe`, so the SDK aborts their card requests and nothing is charged. The `actual_payment_request_validation` requirement is where that difference shows: check the paused request's host before treating an Adyen result as payable.
211
213
 
212
214
  Authorize.Net Direct Post Method and SIM ship dark. The SDK pauses their `transact.dll` card form only for an org Agentcard enabled for them (capability `authorize_net_dpm_form`), and the bundled catalog leaves them out, so the table above does not list them and inspection answers for their forms exactly as it did before: the SIM hosted form on `secure.authorize.net` identifies Authorize.Net and returns `unknown` with `processor_variant_unverified`.
package/README.md CHANGED
@@ -254,7 +254,8 @@ Coverage is specific to the processor request format, merchant setup, browser tr
254
254
  | Checkout.com | supported |
255
255
  | 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. |
256
256
  | 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 |
257
- | Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it. Sessions flow: 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 pauses only when Agentcard reviewed it as a merchant profile (Dunelm and Cinemark so far); both profiles are in `observe`, so their card requests are reported and aborted and nothing pays there yet (see "Adyen merchants that take the card on their own server"). 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 |
257
+ | PayPal Hosted Fields (the legacy card fields of PayPal's JS SDK; Planet Golf UK) | not supported: no Agentcard payment takes over the card these fields send to `cors.api.paypal.com`. The SDK fails that request for every company, so PayPal's payment confirm never receives the dummy card, and the controller reads `unsupported` with reason `unsupported_paypal_hosted_fields` and holds the attachment until you attach again; a PayPal wallet payment continues untouched |
258
+ | Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it. Sessions flow: 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 pauses only when Agentcard reviewed it as a merchant profile. Cinemark and Dick's Sporting Goods' own storefront take payments for a company Agentcard turned them on for; every other reviewed merchant is in `observe`, so its card requests are reported and aborted and nothing pays there yet (see "Adyen merchants that take the card on their own server"). 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 |
258
259
  | ACI Worldwide COPYandPAY (Halfords) | dark launch: served only after `syncRegistry()` and only for an org Agentcard enabled; prepared checkout only. The card-number request is replayed from the cardholder's device, the page's own CVC and expiry requests are answered locally, and `/payment` continues once. See [Pay an ACI COPYandPAY checkout](#pay-an-aci-copyandpay-checkout) |
259
260
  | 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 |
260
261
  | Fiserv Commerce Hub | implemented (mode `cse`, prepared checkout only) and off until Agentcard turns it on for your company: the card frame's card capture on `connect.fiservapis.com` (or `connect-cert.fiservapis.com` in the sandbox), with the approved card encrypted on the cardholder's device under a key Agentcard reviewed for the merchant. A merchant whose frame posts to Fiserv's `payment-link` endpoint is not recognized. No live merchant purchase is verified yet |
@@ -424,21 +425,25 @@ mode; the difference matters if you drive `authorize()` yourself.
424
425
  page's origin, the exact origin, path and query of the card request, the
425
426
  session headers and cookies it needs, and the answer headers that may come
426
427
  back. The adapters arm every profile's endpoint (`cardEndpointPatterns()`
427
- lists the `Fetch.enable` patterns) and pause a POST to exactly a profile's
428
- card request URL when it carries a card: anything at the profile's card
429
- number field, or a Luhn-valid number of 12 to 19 digits anywhere in the
430
- body, however it is written. A body the adapter cannot read counts as
431
- carrying one. A GET, a CORS preflight, a look-alike URL, or a POST to the
432
- same URL with no card in it (an IHG stay paid with points, a LookFantastic
433
- order whose card a processor's own fields tokenized, a saved card, a room
434
- another SynXis hotel guarantees with a token) continues untouched. While this
428
+ lists the `Fetch.enable` patterns) and pause a request to exactly a
429
+ profile's card request URL, sent with the profile's own method (a POST, or
430
+ the PUT Target Optical's checkout sends), when it carries a card: anything
431
+ at the profile's card number field, or a Luhn-valid number of 12 to 19
432
+ digits anywhere in the body, however it is written. A body the adapter
433
+ cannot read counts as carrying one. A GET, a CORS preflight, another
434
+ method, a look-alike URL, or a request to the same URL with no card in it
435
+ (an IHG stay paid with points, a saved card, a room another SynXis hotel
436
+ guarantees with a token) continues untouched. While this
435
437
  build's copy of a profile does not take payments (every profile is in
436
438
  observe today) a card request is refused as `unsupported_checkout` before
437
439
  any authorization exists. When the body is the request the profile
438
440
  reviewed, the controller reads `unsupported` with reason
439
441
  `merchant_card_endpoint_not_enabled` and the event names the profile; a
440
442
  card in any other body reads reason `unrecognized_payment_endpoint`, and
441
- the event names only the endpoint origin. For a
443
+ the event names only the endpoint origin. LookFantastic's checkout takes
444
+ the card in BR-DGE's card fields and then sends the masked card number
445
+ BR-DGE returns in its order, so the adapters refuse that order with reason
446
+ `unrecognized_payment_endpoint`. For a
442
447
  profile that takes payments, the adapter captures the request's session:
443
448
  the profile's own header names from the paused request, the endpoint's
444
449
  cookie jar for a cookie profile (`Network.getCookies` or
@@ -1247,7 +1252,7 @@ Some Adyen merchants have adyen-web encrypt the card into four fields and post t
1247
1252
 
1248
1253
  - A request that carries no encrypted card (a basket query, a preflight) continues as before.
1249
1254
  - A body the browser did not hand over (a blob or a file, which neither adapter can read) is not proven card-free, so any request but a `GET`, `HEAD` or `OPTIONS` that carries one is treated as a card request the profile cannot finish. Playwright reports a missing body the same way, so an empty body is treated alike in both adapters.
1250
- - A profile in `observe` never pays. Its card request is aborted before it reaches the merchant, and the adapters report `merchant_profile_observed` with the profile, the templated URL, the verdict and `keyPathSha256`, the SHA-256 of the body's sorted key paths (never a value; `null` for a body that was not read), then `unsupported_checkout`. The controller reads `unsupported` with reason `merchant_profile_observe`, and no authorization exists. Both reviewed profiles, `dunelm_was_graphql` and `cinemark_initiate`, ship in `observe`.
1255
+ - A profile in `observe` never pays. Its card request is aborted before it reaches the merchant, and the adapters report `merchant_profile_observed` with the profile, the templated URL, the verdict and `keyPathSha256`, the SHA-256 of the body's sorted key paths (never a value; `null` for a body that was not read), then `unsupported_checkout`. The controller reads `unsupported` with reason `merchant_profile_observe`, and no authorization exists. `cinemark_initiate` and `dsg_paygate_v2` (Dick's Sporting Goods' own storefront) ship `enabled`, and the API serves them `enabled` only to a client of a company Agentcard turned them on for. Every other reviewed profile ships in `observe`, `dunelm_was_graphql` and `dsg_other_stores_paygate_v2` (Dick's paygate at Golf Galaxy, Public Lands and Going Going Gone) among them.
1251
1256
  - A card request the profile cannot finish (storing the card, a stored card, another operation, three fields instead of four, a sibling endpoint, an unreadable body) is aborted with `unsupported_checkout` and the reason, and reads `unsupported` with reason `merchant_request_unsupported`.
1252
1257
  - The profile's own card request, once Agentcard enables the profile for your client and this build, pays only through a preparation for that profile. Without one it is aborted before any pause, prompt or authorization: the adapters report `blocked` with `preparation_required`, the controller reads `failed` with that reason, and `prepare()` is refused on that attachment, as after any first card request. Create a new attachment and prepare before Pay.
1253
1258
 
@@ -1261,7 +1266,7 @@ After hand-off, a `5xx` final answer (after any redirect), a network failure, or
1261
1266
 
1262
1267
  #### Testing against your own Adyen TEST account
1263
1268
 
1264
- A test-mode client can run the same checkout against its own server and its own Adyen TEST account, before any reviewed profile is enabled. Declare your endpoint on the client, before any attach, as taking the card request of a profile this build reviewed:
1269
+ A test-mode client can run the same checkout against its own server and its own Adyen TEST account, whether or not Agentcard has enabled the reviewed profile. Declare your endpoint on the client, before any attach, as taking the card request of a profile this build reviewed:
1265
1270
 
1266
1271
  ```ts
1267
1272
  const vault = new VaultClient({ clientId, clientSecret, sandboxMerchants: [
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: c979802cf1a5aa95eedd5dd00949cd56c5c478ca3c637b3232b69597cc62d4a4
3
+ // artifact-sha256: 4790fdfc656acbda50ddf8471507933ddd4938f4c5b351ec096a9549a7c8c257
4
4
  // src/deep-freeze.js
5
5
  function deepFreeze(value) {
6
6
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -1086,6 +1086,118 @@ function selectMerchantExchanges(profile, stageName, input) {
1086
1086
  return { ok: true, responses: newestMerchantExchanges(kept) };
1087
1087
  }
1088
1088
  // src/merchant-profiles.js
1089
+ var DSG_PAYGATE_V2 = /* @__PURE__ */ deepFreeze({
1090
+ method: "POST",
1091
+ path: "/prod/api/paygate/v2/authorization",
1092
+ query: {},
1093
+ // The page sends true for every checkout its translator read, the only checkouts
1094
+ // the amount source below maps; a legacy checkout is not this card request.
1095
+ requires: [{ path: ["isModernOrder"], equals: true }],
1096
+ holder: ["tenders", "encryptedCardTenders", 0],
1097
+ fields: {
1098
+ encryptedCardNumber: "cardNumber",
1099
+ encryptedExpiryMonth: "expirationMonth",
1100
+ encryptedExpiryYear: "expirationYear",
1101
+ encryptedSecurityCode: "cvv"
1102
+ },
1103
+ // cardType and vinBalue describe the dummy the agent typed: the facts below
1104
+ // write the approved card's instead, so nothing is removed.
1105
+ remove: [],
1106
+ refuse: [
1107
+ { path: ["tenders", "encryptedCardTenders", 0, "saveForLater"], when: "true" },
1108
+ // A saved card, or a second card tender the Vault never encrypted.
1109
+ { path: ["tenders", "savedCardTender"], when: "present" },
1110
+ { path: ["tenders", "encryptedCardTenders", 1], when: "present" },
1111
+ // A gift card pays part of the order, so the card pays less than the total.
1112
+ { path: ["tenders", "giftCardTenders"], when: "present" }
1113
+ ],
1114
+ amount: null,
1115
+ // Read Sep 25, 2026 (research amount_source/dickssportinggoods). Every checkout call
1116
+ // on the checkout page answers the whole checkout, camelCase on the wire: the page
1117
+ // load's PATCH (or the create), then every address, shipping, promotion, gift card,
1118
+ // tip and ScoreCard change. pricing.totalRemaining is what the card pays, a string
1119
+ // with exactly two decimals ("123.45"; the page itself drops the dot for minor
1120
+ // units). transactionReference is the checkout key, in the body and the URL. After
1121
+ // payment the order submit and the confirmation read return the placed checkout.
1122
+ amountSource: {
1123
+ id: "dsg_checkout_pricing",
1124
+ origin: "checkout",
1125
+ format: "json",
1126
+ responses: [
1127
+ { method: "PATCH", path: "/api/v1/checkouts/:checkoutKey", query: {} },
1128
+ { method: "POST", path: "/api/v1/checkouts", query: {} },
1129
+ { method: "POST", path: "/api/v1/checkouts/:checkoutKey/addresses", queries: [{}, { type: "billing" }, { type: "shipping" }] },
1130
+ { method: "PUT", path: "/api/v1/checkouts/:checkoutKey/shipping/selected/groups/:groupNumber/mode", query: {} },
1131
+ { method: "PUT", path: "/api/v2/checkouts/:checkoutKey/promotion/cart", query: {} },
1132
+ { method: "DELETE", path: "/api/v2/checkouts/:checkoutKey/promotion/cart", query: {} },
1133
+ { method: "POST", path: "/api/v1/checkouts/:checkoutKey/gift-cards", query: {} },
1134
+ { method: "DELETE", path: "/api/v1/checkouts/:checkoutKey/gift-cards/:giftCardIndex", query: {} },
1135
+ { method: "PUT", path: "/api/v1/checkouts/:checkoutKey/tip", query: {} },
1136
+ { method: "PUT", path: "/api/v1/checkouts/:checkoutKey/scorecard", query: {} },
1137
+ { method: "DELETE", path: "/api/v1/checkouts/:checkoutKey/scorecard", query: {} },
1138
+ { method: "DELETE", path: "/api/v1/checkouts/:checkoutKey/v2/gifts", query: {} }
1139
+ ],
1140
+ total: { path: ["pricing", "totalRemaining"], format: "decimal2" },
1141
+ unit: "major",
1142
+ currency: "USD",
1143
+ currencyPath: ["pricing", "currency"],
1144
+ bind: [
1145
+ { source: ["checkoutKey"], card: ["transactionReference"] },
1146
+ { placeholder: "checkoutKey", card: ["transactionReference"] }
1147
+ ],
1148
+ // The order total and the card's share are the same text. Anything that lowers the
1149
+ // share below the total (a gift card, ScoreCard rewards) or lands in only one of them
1150
+ // (a tip, a donation) is refused until a HAR shows which one the charge follows, so
1151
+ // the approved amount and the placed checkout's total read after payment agree.
1152
+ requires: [{ path: ["pricing", "total"], equalsPath: ["pricing", "totalRemaining"] }],
1153
+ refuse: [
1154
+ // A placed checkout is not the one being paid.
1155
+ { path: ["orderPlacedTime"], when: "true" },
1156
+ // Gift cards and a checkout with no address yet (tax not estimated) wait for a HAR.
1157
+ { path: ["payment", "giftCards"], when: "nonEmpty" },
1158
+ { path: ["addresses"], when: "empty" }
1159
+ ],
1160
+ maxAgeMs: 9e5,
1161
+ afterPayment: {
1162
+ origin: "checkout",
1163
+ format: "json",
1164
+ responses: [
1165
+ { method: "POST", path: "/api/v1/checkouts/:checkoutKey", query: {} },
1166
+ { method: "GET", path: "/api/v1/checkouts/submitted/:checkoutKey", query: {} }
1167
+ ],
1168
+ // The order total, which the requires rule above held equal to the card's share
1169
+ // before payment. totalRemaining is not read here: after placement it can drop to
1170
+ // what is still unpaid, which would hide a higher charge.
1171
+ total: { path: ["pricing", "total"], format: "decimal2" },
1172
+ unit: "major",
1173
+ currency: "USD",
1174
+ currencyPath: ["pricing", "currency"],
1175
+ bind: [
1176
+ { source: ["checkoutKey"], card: ["transactionReference"] },
1177
+ { placeholder: "checkoutKey", card: ["transactionReference"] }
1178
+ ],
1179
+ refuse: [{ path: ["orderPlacedTime"], when: "unset" }]
1180
+ }
1181
+ },
1182
+ facts: [
1183
+ // adyen-web's brand codes, the ones the page's supported list names; Dick's own
1184
+ // private-label (plcc, comc) cards have no Agentcard brand and are refused.
1185
+ {
1186
+ path: ["tenders", "encryptedCardTenders", 0, "cardType"],
1187
+ fact: "brand",
1188
+ map: { visa: "visa", mastercard: "mc", amex: "amex", discover: "discover", diners: "diners", jcb: "jcb" }
1189
+ },
1190
+ { path: ["tenders", "encryptedCardTenders", 0, "vinBalue"], fact: "bin6" }
1191
+ ],
1192
+ adyen: {
1193
+ environment: "live",
1194
+ clientKey: "live_YJLFKT47JJG2RI7QIYX6HTKKDEDOEGOH",
1195
+ publicKeySha256: "da33611149dff3ab715be8671c0550bb8aec6112983b2473db9ee7afb829c110"
1196
+ }
1197
+ });
1198
+ function dsgPaygateProfile(own) {
1199
+ return { ...own, ...DSG_PAYGATE_V2 };
1200
+ }
1089
1201
  var MERCHANT_HOSTED_PROFILES = /* @__PURE__ */ deepFreeze([
1090
1202
  {
1091
1203
  // Read Sep 23, 2026. assets.dunelm.com checkout-page bundle: submitPayment
@@ -1181,10 +1293,13 @@ var MERCHANT_HOSTED_PROFILES = /* @__PURE__ */ deepFreeze([
1181
1293
  // client key from the three securedFields iframes on
1182
1294
  // checkoutshopper-live-us.adyen.com (adyen-web 6.x). The request body itself
1183
1295
  // was not captured: the guest email gate stopped the run before Pay. Public
1184
- // key read from checkoutshopper-live-us.adyen.com on 2026-09-25 (UTC).
1296
+ // key read from checkoutshopper-live-us.adyen.com on 2026-09-25 (UTC). Enabled
1297
+ // Oct 7, 2026 under D1 (amountSource, above): live checks on Sep 30 and Oct 6
1298
+ // matched its key pin, its card request as the page's script builds it, and its
1299
+ // checkout total.
1185
1300
  id: "cinemark_initiate",
1186
1301
  merchant: "Cinemark",
1187
- status: "observe",
1302
+ status: "enabled",
1188
1303
  origins: [{ checkout: "https://www.cinemark.com", endpoint: "https://www.cinemark.com" }],
1189
1304
  method: "POST",
1190
1305
  path: "/api/adyen/initiatePayment",
@@ -1257,137 +1372,29 @@ var MERCHANT_HOSTED_PROFILES = /* @__PURE__ */ deepFreeze([
1257
1372
  publicKeySha256: "9dd5ef549c7454d60ca970ff76c8f4cac36d79f2961e59eca9116791e86cbcc5"
1258
1373
  }
1259
1374
  },
1260
- {
1261
- // Read Sep 23, 2026. payments.dickssportinggoods.com/library/dppjs/index-v2.js
1262
- // (adyen-web 6.39.0): Dd() builds tenders.encryptedCardTenders[0] from the Card
1263
- // component's four JWEs as cardNumber, expirationMonth, expirationYear and cvv,
1264
- // beside cardType (adyen-web's first detected brand), saveForLater (the save-card
1265
- // box) and vinBalue (the six digits onBinValue reports). The page posts it with
1266
- // transactionReference, customer.device and any giftCardTenders to
1267
- // <payments host>/prod/api/paygate/v2/authorization, the host picked from the
1268
- // page's own (Dick's, Golf Galaxy, Public Lands, Going Going Gone). The same slot
1269
- // carries Afterpay, CashApp and Affirm tenders with literal strings where the JWEs
1270
- // go, and a saved card goes to tenders.savedCardTender instead. The body names no
1271
- // amount: the paygate charges the cart it holds for transactionReference.
1272
- // mountCCForm names the client key and environment 'live'. Public key read from
1273
- // checkoutshopper-live.adyen.com on 2026-09-25 (UTC).
1375
+ /* @__PURE__ */ dsgPaygateProfile({
1376
+ // Dick's own storefront, the one live checks read on Sep 30 and Oct 6, 2026: its key
1377
+ // pin, its card request as the page's code builds it, its card facts and its checkout
1378
+ // pricing matched DSG_PAYGATE_V2. Enabled Oct 7, 2026 under D1 (amountSource, above).
1274
1379
  id: "dsg_paygate_v2",
1275
1380
  merchant: "Dick's Sporting Goods",
1381
+ status: "enabled",
1382
+ origins: [
1383
+ { checkout: "https://www.dickssportinggoods.com", endpoint: "https://payments.dickssportinggoods.com" }
1384
+ ]
1385
+ }),
1386
+ /* @__PURE__ */ dsgPaygateProfile({
1387
+ // The same paygate on the three other storefronts its page names. No live check has
1388
+ // read them, so their card requests stay paused and aborted in observe.
1389
+ id: "dsg_other_stores_paygate_v2",
1390
+ merchant: "Golf Galaxy, Public Lands and Going Going Gone",
1276
1391
  status: "observe",
1277
1392
  origins: [
1278
- { checkout: "https://www.dickssportinggoods.com", endpoint: "https://payments.dickssportinggoods.com" },
1279
1393
  { checkout: "https://www.golfgalaxy.com", endpoint: "https://payments.golfgalaxy.com" },
1280
1394
  { checkout: "https://www.publiclands.com", endpoint: "https://payments.publiclands.com" },
1281
1395
  { checkout: "https://www.goinggoinggone.com", endpoint: "https://payments.goinggoinggone.com" }
1282
- ],
1283
- method: "POST",
1284
- path: "/prod/api/paygate/v2/authorization",
1285
- query: {},
1286
- // The page sends true for every checkout its translator read, the only checkouts
1287
- // the amount source below maps; a legacy checkout is not this card request.
1288
- requires: [{ path: ["isModernOrder"], equals: true }],
1289
- holder: ["tenders", "encryptedCardTenders", 0],
1290
- fields: {
1291
- encryptedCardNumber: "cardNumber",
1292
- encryptedExpiryMonth: "expirationMonth",
1293
- encryptedExpiryYear: "expirationYear",
1294
- encryptedSecurityCode: "cvv"
1295
- },
1296
- // cardType and vinBalue describe the dummy the agent typed: the facts below
1297
- // write the approved card's instead, so nothing is removed.
1298
- remove: [],
1299
- refuse: [
1300
- { path: ["tenders", "encryptedCardTenders", 0, "saveForLater"], when: "true" },
1301
- // A saved card, or a second card tender the Vault never encrypted.
1302
- { path: ["tenders", "savedCardTender"], when: "present" },
1303
- { path: ["tenders", "encryptedCardTenders", 1], when: "present" },
1304
- // A gift card pays part of the order, so the card pays less than the total.
1305
- { path: ["tenders", "giftCardTenders"], when: "present" }
1306
- ],
1307
- amount: null,
1308
- // Read Sep 25, 2026 (research amount_source/dickssportinggoods). Every checkout call
1309
- // on the checkout page answers the whole checkout, camelCase on the wire: the page
1310
- // load's PATCH (or the create), then every address, shipping, promotion, gift card,
1311
- // tip and ScoreCard change. pricing.totalRemaining is what the card pays, a string
1312
- // with exactly two decimals ("123.45"; the page itself drops the dot for minor
1313
- // units). transactionReference is the checkout key, in the body and the URL. After
1314
- // payment the order submit and the confirmation read return the placed checkout.
1315
- amountSource: {
1316
- id: "dsg_checkout_pricing",
1317
- origin: "checkout",
1318
- format: "json",
1319
- responses: [
1320
- { method: "PATCH", path: "/api/v1/checkouts/:checkoutKey", query: {} },
1321
- { method: "POST", path: "/api/v1/checkouts", query: {} },
1322
- { method: "POST", path: "/api/v1/checkouts/:checkoutKey/addresses", queries: [{}, { type: "billing" }, { type: "shipping" }] },
1323
- { method: "PUT", path: "/api/v1/checkouts/:checkoutKey/shipping/selected/groups/:groupNumber/mode", query: {} },
1324
- { method: "PUT", path: "/api/v2/checkouts/:checkoutKey/promotion/cart", query: {} },
1325
- { method: "DELETE", path: "/api/v2/checkouts/:checkoutKey/promotion/cart", query: {} },
1326
- { method: "POST", path: "/api/v1/checkouts/:checkoutKey/gift-cards", query: {} },
1327
- { method: "DELETE", path: "/api/v1/checkouts/:checkoutKey/gift-cards/:giftCardIndex", query: {} },
1328
- { method: "PUT", path: "/api/v1/checkouts/:checkoutKey/tip", query: {} },
1329
- { method: "PUT", path: "/api/v1/checkouts/:checkoutKey/scorecard", query: {} },
1330
- { method: "DELETE", path: "/api/v1/checkouts/:checkoutKey/scorecard", query: {} },
1331
- { method: "DELETE", path: "/api/v1/checkouts/:checkoutKey/v2/gifts", query: {} }
1332
- ],
1333
- total: { path: ["pricing", "totalRemaining"], format: "decimal2" },
1334
- unit: "major",
1335
- currency: "USD",
1336
- currencyPath: ["pricing", "currency"],
1337
- bind: [
1338
- { source: ["checkoutKey"], card: ["transactionReference"] },
1339
- { placeholder: "checkoutKey", card: ["transactionReference"] }
1340
- ],
1341
- // The order total and the card's share are the same text. Anything that lowers the
1342
- // share below the total (a gift card, ScoreCard rewards) or lands in only one of them
1343
- // (a tip, a donation) is refused until a HAR shows which one the charge follows, so
1344
- // the approved amount and the placed checkout's total read after payment agree.
1345
- requires: [{ path: ["pricing", "total"], equalsPath: ["pricing", "totalRemaining"] }],
1346
- refuse: [
1347
- // A placed checkout is not the one being paid.
1348
- { path: ["orderPlacedTime"], when: "true" },
1349
- // Gift cards and a checkout with no address yet (tax not estimated) wait for a HAR.
1350
- { path: ["payment", "giftCards"], when: "nonEmpty" },
1351
- { path: ["addresses"], when: "empty" }
1352
- ],
1353
- maxAgeMs: 9e5,
1354
- afterPayment: {
1355
- origin: "checkout",
1356
- format: "json",
1357
- responses: [
1358
- { method: "POST", path: "/api/v1/checkouts/:checkoutKey", query: {} },
1359
- { method: "GET", path: "/api/v1/checkouts/submitted/:checkoutKey", query: {} }
1360
- ],
1361
- // The order total, which the requires rule above held equal to the card's share
1362
- // before payment. totalRemaining is not read here: after placement it can drop to
1363
- // what is still unpaid, which would hide a higher charge.
1364
- total: { path: ["pricing", "total"], format: "decimal2" },
1365
- unit: "major",
1366
- currency: "USD",
1367
- currencyPath: ["pricing", "currency"],
1368
- bind: [
1369
- { source: ["checkoutKey"], card: ["transactionReference"] },
1370
- { placeholder: "checkoutKey", card: ["transactionReference"] }
1371
- ],
1372
- refuse: [{ path: ["orderPlacedTime"], when: "unset" }]
1373
- }
1374
- },
1375
- facts: [
1376
- // adyen-web's brand codes, the ones the page's supported list names; Dick's own
1377
- // private-label (plcc, comc) cards have no Agentcard brand and are refused.
1378
- {
1379
- path: ["tenders", "encryptedCardTenders", 0, "cardType"],
1380
- fact: "brand",
1381
- map: { visa: "visa", mastercard: "mc", amex: "amex", discover: "discover", diners: "diners", jcb: "jcb" }
1382
- },
1383
- { path: ["tenders", "encryptedCardTenders", 0, "vinBalue"], fact: "bin6" }
1384
- ],
1385
- adyen: {
1386
- environment: "live",
1387
- clientKey: "live_YJLFKT47JJG2RI7QIYX6HTKKDEDOEGOH",
1388
- publicKeySha256: "da33611149dff3ab715be8671c0550bb8aec6112983b2473db9ee7afb829c110"
1389
- }
1390
- },
1396
+ ]
1397
+ }),
1391
1398
  {
1392
1399
  // Read Sep 25, 2026, from public code only: the WooCommerce plugin super-payments 1.40.1 and
1393
1400
  // cdn.superpayments.com/js/v2/payment.webcomponents.script.js as it stood on Sep 19.
@@ -12,7 +12,8 @@ export interface CardEndpointProfile {
12
12
  readonly frame: string | null;
13
13
  /** The exact https origin the card request goes to. */
14
14
  readonly endpoint: string;
15
- readonly method: 'POST';
15
+ /** A POST, or a PUT for a page that sends its card request as one (Target Optical's addPayment). */
16
+ readonly method: 'POST' | 'PUT';
16
17
  readonly path: string;
17
18
  readonly query: Readonly<Record<string, string>>;
18
19
  /** json_api is a JSON object sent as application/vnd.api+json. */
@@ -130,6 +131,13 @@ export declare function cardEndpointById(id: string): CardEndpointProfile | null
130
131
  export declare function cardEndpointPasses(profile: CardEndpointProfile, body: string | null, contentType: string | undefined): boolean;
131
132
  /** The reviewed profile (any status) one of whose resend requests `url` and `method` are, or null: the adapters abort it. */
132
133
  export declare function cardEndpointResendFor(url: string, method: string): CardEndpointProfile | null;
134
+ /**
135
+ * Whether a URL is any profile's card request or one of its resends, by any method the
136
+ * table names: the Playwright adapter's route filter, which sees the URL alone. The
137
+ * pre-claim gates then judge each routed request by its own method and body (a GET or a
138
+ * CORS preflight to the same URL defers), so routing a URL is never a decision to pause it.
139
+ */
140
+ export declare function cardEndpointRouted(url: string): boolean;
133
141
  /** The origins of the profiles whose merchant keeps its own record of the order: the only responses card-endpoint-facts.ts records. */
134
142
  export declare function cardEndpointFactsOrigins(): readonly string[];
135
143
  /**
@@ -11,8 +11,9 @@
11
11
  // typed into, the exact origin, path and query of the card request, which session
12
12
  // headers and cookies it needs, and which answer headers may travel back.
13
13
  //
14
- // The adapters arm every profile's endpoint. A POST that is exactly a profile's card
15
- // request URL pauses when it carries a card (cardEndpointCarriesCard); one that carries
14
+ // The adapters arm every profile's endpoint. A request that is exactly a profile's card
15
+ // request, its URL and its method (a POST, or Target Optical's PUT), pauses when it
16
+ // carries a card (cardEndpointCarriesCard); one that carries
16
17
  // none, such as a stay paid with points or a card a processor's own fields tokenized,
17
18
  // is the merchant's own business and continues untouched. While this build's copy of
18
19
  // the profile does not take payments (observe, a fact only a partner HAR can pin, no
@@ -108,6 +109,18 @@ export function cardEndpointResendFor(url, method) {
108
109
  return null;
109
110
  }
110
111
  }
112
+ // Every method a profile's card request or one of its resends is sent with (a POST, Target
113
+ // Optical's PUT addPayment and PUT card save), from this build's own table.
114
+ const ROUTED_METHODS = [...new Set(PROFILES.flatMap((profile) => [profile.method, ...(profile.resends ?? []).map((resend) => resend.method)]))];
115
+ /**
116
+ * Whether a URL is any profile's card request or one of its resends, by any method the
117
+ * table names: the Playwright adapter's route filter, which sees the URL alone. The
118
+ * pre-claim gates then judge each routed request by its own method and body (a GET or a
119
+ * CORS preflight to the same URL defers), so routing a URL is never a decision to pause it.
120
+ */
121
+ export function cardEndpointRouted(url) {
122
+ return ROUTED_METHODS.some((method) => cardEndpointFor(url, method) !== null || cardEndpointResendFor(url, method) !== null);
123
+ }
111
124
  /** The origins of the profiles whose merchant keeps its own record of the order: the only responses card-endpoint-facts.ts records. */
112
125
  export function cardEndpointFactsOrigins() {
113
126
  return [...new Set(PROFILES.filter((profile) => profile.facts).map((profile) => profile.endpoint))];
package/dist/cdp.js CHANGED
@@ -12,6 +12,7 @@ import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesG
12
12
  import { DatatransRepeats } from './datatrans.js';
13
13
  import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
14
14
  import { isArmedEkashuUrl } from './ekashu.js';
15
+ import { PAYPAL_HOSTED_FIELDS_PATTERNS, isPaypalHostedFieldsConfirm } from './paypal-hosted-fields.js';
15
16
  import { CheckoutAttachmentError, attachmentDeadline, attachmentFailure, withinAttachmentDeadline } from './attachment.js';
16
17
  import { cseBody } from './cse-body.js';
17
18
  import { STRIPE_SETUP_INTENT_CONFIRM, setupConfirmPayingWith } from './stripe-setup.js';
@@ -23,7 +24,7 @@ import { hostedFormSubmittedPage } from './hosted-form.js';
23
24
  import { amadeusSendWindowMs, armSendWindow, failsAsNetworkError } from './amadeus.js';
24
25
  import { CheckoutLifecycle, paymentEndpointGuards } from './lifecycle.js';
25
26
  import { CaptureContexts, MicroformFrames, judgeMicroformFrameUrls, loadedCardMicroformFromUrls, refusedRequestFailure, } from './cybersource-flex.js';
26
- import { CARD_ENDPOINT_REFUSED_STEP_ANSWER, CARD_ENDPOINT_WITHHELD_ANSWER, CardEndpointRefusedError, captureCardEndpointSession, cardEndpointAnswerHeaders, cardEndpointCookies, cardEndpointDetail, cardEndpointFor, cardEndpointNavigationAnswer, cardEndpointPatterns, cardEndpointResendFor, } from './card-endpoint.js';
27
+ import { CARD_ENDPOINT_REFUSED_STEP_ANSWER, CARD_ENDPOINT_WITHHELD_ANSWER, CardEndpointRefusedError, captureCardEndpointSession, cardEndpointAnswerHeaders, cardEndpointCookies, cardEndpointDetail, cardEndpointNavigationAnswer, cardEndpointPatterns, cardEndpointRouted, } from './card-endpoint.js';
27
28
  import { CardEndpointFactsWatch } from './card-endpoint-facts.js';
28
29
  import { PAYRAILS_GUARD_PATTERNS, PAYRAILS_INIT_BODY_LIMIT, PayrailsSessionCapture, isPayrailsHost, payrailsInitCandidate, recognizesPayrails, } from './payrails.js';
29
30
  import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoint-page.js';
@@ -621,13 +622,13 @@ export function withCorsHeaders(headers, cors) {
621
622
  return out;
622
623
  }
623
624
  /**
624
- * The request a card endpoint approval is asked for: `request` names the URL and
625
- * nothing else, and the profile travels with the captured session in
626
- * merchant_card_endpoint, the one member the API seals, with the merchant's own cut
627
- * record of the order for a profile that reads one.
625
+ * The request a card endpoint approval is asked for: `request` names the URL and the
626
+ * profile's own method and nothing else, and the profile travels with the captured
627
+ * session in merchant_card_endpoint, the one member the API seals, with the merchant's
628
+ * own cut record of the order for a profile that reads one.
628
629
  */
629
630
  function cardEndpointRequest(profile, session, facts = null) {
630
- return { url: session.url, method: 'POST', headers: {}, body: '', merchant_card_endpoint: { profile: profile.id, session, ...(facts ? { facts } : {}) } };
631
+ return { url: session.url, method: profile.method, headers: {}, body: '', merchant_card_endpoint: { profile: profile.id, session, ...(facts ? { facts } : {}) } };
631
632
  }
632
633
  /**
633
634
  * The merchant's own record of the order for a paused card request of a profile that
@@ -1909,10 +1910,13 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1909
1910
  // merchant, and a request with no card in it continues untouched.
1910
1911
  // Every Payrails host is intercepted only for an org the API served Payrails
1911
1912
  // to, so the gate can abort a card-form payment (see payrails.ts).
1913
+ // PayPal's legacy Hosted Fields confirm is armed for every org, whatever the
1914
+ // registry serves: one that carries the typed card is paused only to be
1915
+ // refused, and one with no card continues (see paypal-hosted-fields.ts).
1912
1916
  const urlPatterns = [...new Set([...(derived.length > 0 ? derived : FALLBACK_CARD_PATTERNS), ...guards.patterns,
1913
1917
  ...(stripeCheckout.isEnabled() ? ['https://api.stripe.com/*'] : []), MERCADO_CHECKOUT_PATTERN, ...merchantPatterns,
1914
1918
  ...(recognizesGmo(opts.vault) ? GMO_TOKEN_V1_PATTERNS : []), ...juspayCheckout.urlPatterns(), ...cardEndpointPatterns(),
1915
- ...(payrails ? PAYRAILS_GUARD_PATTERNS : [])])];
1919
+ ...(payrails ? PAYRAILS_GUARD_PATTERNS : []), ...PAYPAL_HOSTED_FIELDS_PATTERNS])];
1916
1920
  const arm = async (sessionId, resume = false, worker = false) => {
1917
1921
  const key = sessionId ?? '__root__';
1918
1922
  if (armed.has(key))
@@ -3173,17 +3177,19 @@ export async function attachToPlaywright(page, opts) {
3173
3177
  // GMO token v1's JSONP GET, routed only for an org the API served GMO to (see attachToCdp).
3174
3178
  || (isGmoTokenV1Request(url.toString()) && recognizesGmo(opts.vault))
3175
3179
  || juspayCheckout.matches(url.toString())
3176
- // Every reviewed card endpoint, in any status (the pre-claim gate holds only a request that carries a card,
3177
- // and refuses one this build does not pay).
3178
- || cardEndpointFor(url.toString(), 'POST') !== null
3179
- // A request that sends the typed card to such a merchant again, refused in every status.
3180
- || cardEndpointResendFor(url.toString(), 'POST') !== null
3180
+ // Every reviewed card endpoint, in any status, and every request that sends the typed card to such a merchant
3181
+ // again, by any method the table names (Target Optical's addPayment and card save are PUTs). The pre-claim
3182
+ // gates hold only a card request that carries a card, refuse one this build does not pay, and refuse a resend
3183
+ // in every status.
3184
+ || cardEndpointRouted(url.toString())
3181
3185
  || aci.matches(url.toString())
3182
3186
  // Every Payrails host, routed only for an org the API served Payrails to (see attachToCdp).
3183
3187
  || (payrails !== null && isPayrailsHost(url.toString()))
3184
3188
  // Every request to an NMI host whose entry is served (ekashu.ts), as attachToCdp arms the
3185
3189
  // host: the card page's post and its 3-D Secure calls reach the NMI gate and fail.
3186
- || isArmedEkashuUrl(opts.vault, url.toString()), async (route) => {
3190
+ || isArmedEkashuUrl(opts.vault, url.toString())
3191
+ // PayPal's legacy Hosted Fields confirm, for every org, as attachToCdp arms it (paypal-hosted-fields.ts).
3192
+ || isPaypalHostedFieldsConfirm(url.toString()), async (route) => {
3187
3193
  const request = route.request();
3188
3194
  // When this request paused, and the page it came from: the merchant's total is read as of now.
3189
3195
  const pausedAt = Date.now();