@agent-cards/sdk 0.22.0 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,40 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - 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
+
7
+ - 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
+
9
+ ## 0.23.0
10
+
11
+ ### Upgrading from 0.22.0
12
+
13
+ - An SDK attached at Delta, Hilton, IHG, American Airlines, EconomyBookings, The Verb Hotel, RunSignUp, Mindbody, LookFantastic, Hyatt, LifeMiles, Southwest Airlines, United Airlines, Athens-Clarke County's MyRec page, Los Angeles or San Francisco on Conduent eTIMS, the D-EDGE card frame, NIC Common Checkout or Lexicon's FastSpring popup now stops the request that carries the card before it reaches the merchant. 0.22.0 let that request through with whatever card the agent typed. Every one of these profiles is in observe, so the request is refused as `unsupported_checkout` and the controller reads `unsupported`. A card form the page submits as a page load gets the SDK's stopped page instead of an aborted load.
14
+
15
+ - On every reviewed card endpoint, the five from 0.22.0 included, a POST to the card request URL that carries no card now continues untouched (0.22.0 refused every POST there). A card in a body other than the one the profile reviewed now reads reason `unrecognized_payment_endpoint`, and its event names only the endpoint origin; only the reviewed body reads `merchant_card_endpoint_not_enabled`.
16
+
17
+ - TypeScript: `CardEndpointRefusal` gains `facts_unavailable` and `facts_refused`, `CardEndpointReplay`'s `withheld` gains `refused_step_in_response`, and the controller's `unsupported()` reasons gain `processor_card_page` and `merchant_card_endpoint_resend_refused`. Code that switches exhaustively over one of them needs the new cases. `PaymentOutcomeUnknownError` gains `processorRequestStarted`, `PrepareCheckoutOptions` gains `tokenizeBeforePay`, `PreparedCheckout` gains `cybersourceFlex`, and `PausedRequest`'s `merchant_card_endpoint` gains `facts`.
18
+
19
+ - NMI and CyberSource Flex tokenize-before-pay are served only to a company Agentcard enabled for them, so nothing changes there for any other company.
20
+
21
+ ### New
22
+
23
+ - Recognize nine more reviewed merchants that post the card from their own checkout page to their own server: Delta, Hilton, IHG, American Airlines, EconomyBookings, The Verb Hotel, RunSignUp, Mindbody and LookFantastic (mode `card_endpoint`). Every profile ships in observe, so the adapters pause each merchant's exact card request and refuse it as `unsupported_checkout` before any authorization. On every reviewed card endpoint, the first five included, only a request that carries a card pauses (anything at the profile's card number field, or a Luhn-valid number of 12 to 19 digits anywhere in the body; a body the adapter cannot read counts as one). A request to the same URL with no card in it, such as an IHG stay paid with points, a LookFantastic order whose card BR-DGE or Adyen tokenized, a saved card, or a room another SynXis hotel guarantees with a token, continues untouched. The refusal names the profile, with reason `merchant_card_endpoint_not_enabled`, only when the body is the request the profile reviewed; a card in any other body reads reason `unrecognized_payment_endpoint`, and its event names only the endpoint origin. EconomyBookings sends its card request from its own card vault frame inside the checkout page: the adapters keep a `Referer` on that frame and drop one on the page around it, and the page the request was paused on stays the checkout page. Delta and IHG call an API host of the checkout page's own site, so the endpoint's cookie jar travels as it does for a same-origin profile. `CardEndpointProfile` gains `frame`, and its `encoding` may be `json_api`.
24
+
25
+ - Recognize four more reviewed merchants whose own checkout page encrypts the card under a key the merchant publishes before it posts the card to the merchant's server: Hyatt (its card token and security code token requests), LifeMiles, Southwest Airlines and United Airlines (mode `card_endpoint`). Their paused requests carry only the dummy card as the page encrypted it, and the adapters handle them exactly as the other fourteen: every profile ships in observe, so each merchant's exact card request is refused as `unsupported_checkout` before any authorization and the dummy card never reaches the merchant. When one of them takes payments, the cardholder's device writes the real card encrypted under the merchant's key Agentcard pinned, so only that merchant can read it.
26
+
27
+ - Recognize reviewed card forms the merchant's page submits as a page load (a card endpoint profile with `navigation` `top` or `frame`): Athens-Clarke County's MyRec page, Los Angeles and San Francisco on Conduent eTIMS, the D-EDGE card frame and NIC Common Checkout (NJMCdirect and other state agencies), every one in observe. A post with no card on the same URL continues untouched; a card post a script sends is aborted; the reviewed page load pauses, carries only the profile's listed cookies, and is answered with the merchant's page or a 301, 302 or 303 the browser follows in its own session. A 307 or 308 is refused. Every stopped page load gets a small HTML page (`agentcard-checkout` `stopped_before_send`, `sent_answer_not_shown` or `payment_in_progress`) instead of an aborted load; it never says nothing was sent while a payment for the checkout is waiting or may have reached the merchant. `PaymentOutcomeUnknownError` gains `processorRequestStarted`, false once the API confirmed it retired the authorization before any processor request. `CardEndpointSession` gains `navigation` (`top` or `frame`). Once Agentcard enables such a profile, the API takes one payment per merchant checkout and refuses another create for it with `409 merchant_checkout_in_use`, and the page then reads `payment_in_progress`. Matching API and Vault releases are required.
28
+
29
+ - Recognize Lexicon's FastSpring popup checkout as a reviewed card endpoint, in observe like every other reviewed card endpoint. The adapters read the page a card request pauses on from the popup frame the request came from, let a PayPal or wallet payment on the same URL that carries no card through untouched (`card_endpoint_passed`), and abort FastSpring's EBANX resend of the typed card in every status (`card_endpoint_refused`, reason `card_resend`; the controller reads `unsupported` with reason `merchant_card_endpoint_resend_refused`). They record FastSpring's own record of the order, read-only, and send it with the create cut to its total, currency, test mode and renewal marks; a total or currency other than your integer `amount` throws `AmountMismatchError` with `amountSource: 'merchant_total'`, and a renewing order or a test checkout throws `CardEndpointRefusedError` with the new reason `facts_refused` (`facts_unavailable` when no record was read, when the popup sent a request the SDK does not read the order from after the newest record, or when the checkout sent more requests than the SDK keeps and one it let go could still change the order). A card endpoint answer the device withheld as a step the review refuses (`withheld: 'refused_step_in_response'`) reaches the page as a `502` with code `agentcard_step_refused`. Matching API and Vault releases are required.
30
+
31
+ - Pay a CyberSource Flex page that gives createToken only seconds (Alaska Airlines' flight checkout gives it three). `prepare({ psp: 'cybersource_flex', environment, tokenizeBeforePay: true })`, called once the card fields show, sends the capture context and fields of the page's one card Microform with the preparation (`PreparedCheckout.cybersourceFlex`), so the cardholder's device tokenizes the card when it approves. At Pay only a field request under that same context and those fields binds (`checkout_changed` otherwise), and the SDK reads the bound authorization every 100 ms for 10 seconds. Refusals before any preparation exists: `microform_not_loaded`, `microform_ambiguous`, `microform_unsupported`, `capture_context_invalid`, `cybersource_environment_mismatch`, and the API's `processor_unavailable`, `sandbox_host_on_live_row` and `live_host_on_sandbox_row`. The matching API and Vault releases are required.
32
+
33
+ - Pause NMI's hosted payment page (formerly eKashu) for the cardholder's device. The sync now also asks for `capabilities=ekashu_hosted_payment_page`, and the API serves NMI's recognizer only for a company Agentcard turned NMI on for. The SDK pauses the merchant's entry form to `live.ekashu.com` (or `test.ekashu.com`), which carries no card, as a `hosted_form` authorization, and answers the paused navigation with the `submitted_on_device` page once the cardholder's device has paid it. Every other post your browser makes to NMI, its card page's own post and its 3-D Secure calls, carries the card your agent typed: the SDK fails it and reads `unsupported` with reason `processor_card_page`. Matching API and Vault releases are required.
34
+
35
+ - The preflight catalog identifies NMI's hosted payment page (`ekashu`): a form that posts to `live.ekashu.com` or `test.ekashu.com`, or NMI's card page itself. Agentcard keeps NMI off until it turns it on for a company, so the bundled profile declares no NMI support and the result stays `unknown`. The catalog's `catalog_sha256` changes; `catalog_version` stays `2026-09-15.1`.
36
+
37
+ ## 0.22.0
38
+
5
39
  - A refused amount names what you passed and each amount it can mean, in the forms the API accepts, so an agent can correct its next call. `authorize()` given the string `"2667"` with `usd` throws `amount is an integer in the currency's smallest unit (2306 for $23.06), or a decimal string in normal units ("23.06"). You passed the string "2667". For 26.67 USD, send the number 2667 or the string "26.67". For 2667.00 USD, send the number 266700 or the string "2667.00".` `prepare()` and `prepareCheckout()` add the same sentences to their `amount_required` message. The first sentence is unchanged, so code that matches on it keeps working. The offers use the decimal places the API reads for each currency: none for JPY and MGA, three for KWD, whose amounts end in 0. A value below zero is never offered back as a positive amount, and a value with more digits than any amount is described and never repeated, so a card number typed into the wrong field stays out of your logs.
6
40
 
7
41
  - **The phone's attestation rides on the key itself** (WP199, 1.3.4). `voice.appKeys.register(key, { user_id })` reads the block from `key.hardware`, where `@agent-cards/voice` 1.9.8's `VoiceHost` puts it, and sends it on as `hardware`; a server that forwards the key description as it came from the phone needs no change. `opts.hardware` still takes a block and wins when both are given. `HardwareAttestation` gains `app_id` (optional), the `<team id>.<bundle id>` an iOS phone names so Agentcard checks the attestation's `rpIdHash` against it; an Android block carries none.
@@ -34,14 +68,6 @@
34
68
 
35
69
  - Pay a Juspay Express Checkout card payment (Swiggy's website) behind a dark launch. `syncRegistry()` now also declares `juspay_txns.v1`, so the API serves the Juspay recognizer to an org Agentcard has enabled. Both adapters answer the page's `/card/tokenize` locally with a stand-in token and refuse its placeholder `/v2/card/fingerprint` lookup, so the placeholder never reaches Juspay and the page never holds a real card token. The `/txns` that carries the stand-in token pauses, the cardholder's device sends both real requests, and the page receives only Juspay's order id, transaction id, status and bank redirect. Saving the card, EMI, a card number in `/txns` and card requests to `api.juspay.io` or `api-ns1.juspay.in` are aborted as `unsupported_checkout` before an authorization exists, and the controller reads `unsupported` with reason `juspay_request_unsupported`. An approval never answers a `/txns` for another order. Until Agentcard enables Juspay for your org, the SDK does not take over Juspay's requests, and a create for one answers `processor_unavailable`. The matching API and Vault releases are required.
36
70
 
37
- - Pay a CyberSource Flex Microform checkout (Amtrak, Sonos, Learning Without Tears) behind a dark launch. `syncRegistry()` now declares the `capture_context` capability (`SUPPORTED_CAPABILITIES`), and the API serves the Flex recognizer only to an org Agentcard has enabled for it, so every other org's Microform requests go through untouched, as before. Both adapters read the capture context from the card field iframe's URL, verify it offline against CyberSource's pinned signing keys, and send it with the Microform fields on the create; the cardholder's device seals the real card for CyberSource and the field's paused request is answered with the transient token. Only Microform 2.12.1 card fields pause, and a field counts only once its document has loaded: a check Microform continues, any other request on the tokens URL is aborted as `unsupported_checkout` before an authorization exists, and a field the SDK could not read is refused for that request alone. A retry from either field after the field's 30-second timeout is answered from the same approval, and `prepare({ psp: 'cybersource_flex', environment: 'production' | 'sandbox' })` binds one field request. `vault.placeholderCard({ user, cardId })` returns the test number, security code and real expiry to type so the page's own card facts match the stored card; pin its `cardId` on the attachment so the approval page preselects that card. New exports: `SUPPORTED_CAPABILITIES`, `UnsupportedCapabilityError`, `PlaceholderCardError`, and the types `PlaceholderCard`, `MicroformSidecar` and `MicroformField`. The matching API and Vault releases are required.
38
-
39
- - Recognize Gravy (Gr4vy) Secure Fields. The registry and the coverage catalog now name the new-card request that updates a checkout session at `/checkout/sessions/<session id>/fields` on the merchant's own `api.<merchant>.gr4vy.app` host, in sandbox and production, with `payment_method.number`, `payment_method.expiration_date` as one `MM/YY` value and `payment_method.security_code`. Gravy publishes this request as an HTTP PUT and the SDK pauses and replays POST requests only, so the recognizer identifies the destination and fills the body but intercepts nothing yet; a PUT on that host continues to Gravy untouched, and the preflight reports Gravy `unsupported` with `request_method_not_supported` in every card scenario until the SDK can intercept that method. The shape follows Gravy's published contract and is not confirmed by a live capture. Updating the session stores the card with Gravy; the merchant creates the transaction on its own server, so a completed request is not a payment. Gravy Embed on `embed.<merchant>.gr4vy.app` is not covered.
40
-
41
- - Attach in a browser whose default context runs extensions. A browser extension's service worker or background page, at a `chrome-extension://` URL, no longer counts as a site's worker: `attachToPlaywright` and `attachToCdp` attach beside it, and one that starts later leaves the checkout armed, because an extension's worker never serves the merchant's requests. Cloud browsers such as Anchor run their agent, ad blocker and popup blocker as extensions in the default context, and stock Chrome runs its component extensions there, so that context used to be refused with `Service workers are active` or `checkout_attachment_failed`. A site's own service worker, a shared worker, or a worker with no URL still refuses the context.
42
-
43
- - Pay a GMO Payment Gateway checkout (teamLab's ticket sites) behind a dark launch. `syncRegistry()` now declares the `gmo_shared_pk` capability (`SUPPORTED_CAPABILITIES`, now exported), and the API serves the GMO recognizer only to an org Agentcard has enabled for GMO, so no other org's GMO requests are paused or refused. For an enabled org, both adapters pause `mp-token.js`'s `getToken` for one token on `token.mul-pay.jp` or `stg.token.mul-pay.jp`: the cardholder's device sends the card to GMO, and the page receives only GMO's single-use token, reduced to the fields `mp-token.js` reads; any other answer throws `PaymentOutcomeUnknownError` with reason `gmo_token_unconfirmed`. Any other request to that URL (a merchant-registered key, GMO Elements, several tokens, a shop id that does not match the host) and GMO token v1's script request are aborted before anything is created: the attachment reads `unsupported` with reason `unsupported_gmo_request` or `unsupported_gmo_token_v1`, which the `unsupported_checkout` event also carries. A body the SDK cannot read is aborted alone (`blocked`, `unreadable_gmo_request`). The request names no amount, so pass `amount` and `currency`; without them it throws the new `AmountRequiredError` before anything is created. A retry the page sends after losing its request takes the pending approval only when it names the same shop. The matching API release is required; the Vault's GMO path ships separately.
44
-
45
71
  - Pause Elavon Converge's Hosted Payment Page for an org Agentcard enabled for it. NY DMV's payment apps redirect to Converge's own page on `api.convergepay.com` (`api.demo.convergepay.com` for Converge's demo environment), which posts the card as one same-origin JSON request to `/hosted-payments/service/payment/hpp/process`. `syncRegistry()` now declares the `converge_hpp_process` capability, and the API serves the dark recognizer only to an org Agentcard enabled for Converge; every other org's Converge requests continue untouched, as before. A first credit card sale pauses in `token` mode for the cardholder's approval of its own `ssl_amount`, so no `amount` is needed; the cardholder's device sends the real card and the paused request is answered with Converge's approval, rebuilt without the card's expiry, the cardholder's names or any card token. Electronic check and financing payments on the same URL continue untouched. Every other shape is refused in the browser before any create or prompt, with payment-core's own code on the `blocked` event (`ConvergeRefusalReason`): a wallet, the debit, gift card and push-to-card tabs, a follow-up after a 3-D Secure, Pay Defense or surcharge step, a CAPTCHA, a public-sector service fee, recurring or installment fields, card saving (`ssl_get_token`, `ssl_add_token`), a tip or cash back, another currency, and anything that is not the page's own request. A refusal does not hold the attachment, so the page's next payment is judged afresh. Autopilot never pays a Converge payment: it always waits for the cardholder's own approval.
46
72
 
47
73
  - Worldpay eProtect card registration, behind a dark launch, through a prepared checkout only. A registration is not a payment. The matching API, Vault and relay releases are required.
@@ -57,6 +83,22 @@
57
83
 
58
84
  - Pay an Amadeus Checkout card step through a prepared checkout, for an org Agentcard enabled for it. Air France and KLM take cards through Amadeus's Checkout Web SDK, whose tunnel iframe posts every action to `paypages.payment.amadeus.com/1ASIATP/ARIAPP/pay`: `load` as the payment step opens, the BIN lookup, and the card `add`. `syncRegistry()` now declares the `amadeus_checkout_actions.v1` capability, and the API serves the dark recognizer only to an enabled org; every other org's Amadeus requests continue untouched, as before. With `prepare({ psp: 'amadeus_checkout', environment: 'production' | 'sandbox' })`, only the add pauses, bound to the prepared page by its `Amadeus-Checkout-Sdk-Host` header. A request the catalog marks as routine without a card now continues before the preparation claim, in both adapters and for every processor, so the page's own `load` no longer spends or blocks a preparation, and Air France's Nuvei payment with a `ccTempToken` after a prepared add continues instead of failing. The tunnel gives up on the add after 30 seconds, so 20 seconds after the pause (`AMADEUS_SEND_WINDOW_MS`) the SDK asks the API to retire the authorization, or, while its create has not answered, the preparation and any request already bound to it (`VaultClient.retirePreparation`). Once the API confirms the card never left the device, the SDK fails the page's request with the controller at `failed` and reason `amadeus_send_window_closed`; when it cannot confirm, the SDK waits for the device's answer. A refused Amadeus request fails as a network error, which the tunnel reports at once, rather than as a cancellation, which it ignores. Air France's and KLM's add is refused before any create or prompt as `amadeus_merchant_held_expiry`, because their own form keeps the expiry, a Data Collection Page store as `amadeus_dcp_field_config_unreviewed`, and an add naming another page as `amadeus_header_binding`, each on the controller's `failed` state. Autopilot never pays an Amadeus payment.
59
85
 
86
+ - Pause three more Adyen merchants that take adyen-web's encrypted card on their own server, through merchant profiles in observe: Super Payments (WePrescribe's checkout), CorePower Yoga on Zenoti, and Cathay Pacific. The adapters pause each one's card request, report its shape (a hash of its key paths, never a value) as `merchant_profile_observed` and abort it, so the agent's placeholder card never reaches the merchant. A card sent to Super's save-card request, or to the charge request Cathay posts after signing the card body, is aborted too.
87
+
88
+ ## 0.21.0
89
+
90
+ - Pay a CyberSource Flex Microform checkout (Amtrak, Sonos, Learning Without Tears) behind a dark launch. `syncRegistry()` now declares the `capture_context` capability (`SUPPORTED_CAPABILITIES`), and the API serves the Flex recognizer only to an org Agentcard has enabled for it, so every other org's Microform requests go through untouched, as before. Both adapters read the capture context from the card field iframe's URL, verify it offline against CyberSource's pinned signing keys, and send it with the Microform fields on the create; the cardholder's device seals the real card for CyberSource and the field's paused request is answered with the transient token. Only Microform 2.12.1 card fields pause, and a field counts only once its document has loaded: a check Microform continues, any other request on the tokens URL is aborted as `unsupported_checkout` before an authorization exists, and a field the SDK could not read is refused for that request alone. A retry from either field after the field's 30-second timeout is answered from the same approval, and `prepare({ psp: 'cybersource_flex', environment: 'production' | 'sandbox' })` binds one field request. `vault.placeholderCard({ user, cardId })` returns the test number, security code and real expiry to type so the page's own card facts match the stored card; pin its `cardId` on the attachment so the approval page preselects that card. New exports: `SUPPORTED_CAPABILITIES`, `UnsupportedCapabilityError`, `PlaceholderCardError`, and the types `PlaceholderCard`, `MicroformSidecar` and `MicroformField`. The matching API and Vault releases are required.
91
+
92
+ - Pay a GMO Payment Gateway checkout (teamLab's ticket sites) behind a dark launch. `syncRegistry()` now declares the `gmo_shared_pk` capability (`SUPPORTED_CAPABILITIES`, now exported), and the API serves the GMO recognizer only to an org Agentcard has enabled for GMO, so no other org's GMO requests are paused or refused. For an enabled org, both adapters pause `mp-token.js`'s `getToken` for one token on `token.mul-pay.jp` or `stg.token.mul-pay.jp`: the cardholder's device sends the card to GMO, and the page receives only GMO's single-use token, reduced to the fields `mp-token.js` reads; any other answer throws `PaymentOutcomeUnknownError` with reason `gmo_token_unconfirmed`. Any other request to that URL (a merchant-registered key, GMO Elements, several tokens, a shop id that does not match the host) and GMO token v1's script request are aborted before anything is created: the attachment reads `unsupported` with reason `unsupported_gmo_request` or `unsupported_gmo_token_v1`, which the `unsupported_checkout` event also carries. A body the SDK cannot read is aborted alone (`blocked`, `unreadable_gmo_request`). The request names no amount, so pass `amount` and `currency`; without them it throws the new `AmountRequiredError` before anything is created. A retry the page sends after losing its request takes the pending approval only when it names the same shop. The matching API release is required; the Vault's GMO path ships separately.
93
+
94
+ ## 0.20.0
95
+
96
+ The first release named `@agent-cards/sdk`. `@agent-cards/checkout` has no 0.20.0; from 0.21.0 it re-exports this package. 0.20.0 also carried the 1.0.0 to 1.3.2 sections below, internal build numbers that never reached npm.
97
+
98
+ - Recognize Gravy (Gr4vy) Secure Fields. The registry and the coverage catalog now name the new-card request that updates a checkout session at `/checkout/sessions/<session id>/fields` on the merchant's own `api.<merchant>.gr4vy.app` host, in sandbox and production, with `payment_method.number`, `payment_method.expiration_date` as one `MM/YY` value and `payment_method.security_code`. Gravy publishes this request as an HTTP PUT and the SDK pauses and replays POST requests only, so the recognizer identifies the destination and fills the body but intercepts nothing yet; a PUT on that host continues to Gravy untouched, and the preflight reports Gravy `unsupported` with `request_method_not_supported` in every card scenario until the SDK can intercept that method. The shape follows Gravy's published contract and is not confirmed by a live capture. Updating the session stores the card with Gravy; the merchant creates the transaction on its own server, so a completed request is not a payment. Gravy Embed on `embed.<merchant>.gr4vy.app` is not covered.
99
+
100
+ - Attach in a browser whose default context runs extensions. A browser extension's service worker or background page, at a `chrome-extension://` URL, no longer counts as a site's worker: `attachToPlaywright` and `attachToCdp` attach beside it, and one that starts later leaves the checkout armed, because an extension's worker never serves the merchant's requests. Cloud browsers such as Anchor run their agent, ad blocker and popup blocker as extensions in the default context, and stock Chrome runs its component extensions there, so that context used to be refused with `Service workers are active` or `checkout_attachment_failed`. A site's own service worker, a shared worker, or a worker with no URL still refuses the context.
101
+
60
102
  ## 1.3.2
61
103
 
62
104
  - **`AppVoiceKeyDescription` names no `agreement_public_key`** (WP115, the audit's fourth lane). The key the app copy is sealed for is never a business's to name: the frame mints it and the person's vault proves it at the set-up inside the app. `voice.appKeys.register` sends the signing half alone; the API answers `agreement_key_not_accepted` to a body that still carries one, and `GET /vault/voice/app_copy/:lang` answers `agreement_key_required` until the vault has vouched for the key.
package/PREFLIGHT.md CHANGED
@@ -179,6 +179,8 @@ The catalog includes reviewed discovery rules and direct SDK implementation capa
179
179
 
180
180
  Fiserv Commerce Hub is not in the table. Agentcard keeps Fiserv off until it turns it on for a company, so the bundled profile declares no Fiserv support. Fiserv's assets still identify `fiserv`: `payment-fields.js` and `checkout.js` on `commercehub-checkout.fiservapps.com` and the card frame on `commercehub-secure-data-capture.fiservapps.com`, with their `-cert` hosts. The result stays `unknown`, and a page that loads a Fiserv asset beside another processor's reads as `ambiguous`. A company that Agentcard turned Fiserv on for pays it through `prepare()`, as the [README](./README.md) describes.
181
181
 
182
+ NMI's hosted payment page (formerly eKashu) is not in the table either. Agentcard keeps NMI off until it turns it on for a company, so the bundled profile declares no NMI support. A merchant form that posts to `live.ekashu.com` or `test.ekashu.com`, or NMI's card page itself, still identifies `ekashu`, and the result stays `unknown`.
183
+
182
184
  Verifone eCommerce is not in the table either, for the same reason. Its `verifone.js` on `us.jsclient.verifone.cloud`, `emea.jsclient.verifone.cloud` and the two sandbox hosts `uscst.jsclient.vficloud.net` and `cst.jsclient.vficloud.net` identifies `verifone`, and the result stays `unknown`. A Verifone page encrypts the card itself and sends it to the merchant's own server, so Agentcard pays it only at a merchant it reviewed.
183
185
 
184
186
  GMO Payment Gateway (`gmo_pg`) is identified from its token v2 script `mp-token.js` on `static.mul-pay.jp` and `stg.static.mul-pay.jp`, but it is not in this table. It launches dark: the direct SDK pays it only after `syncRegistry()` and only for an org Agentcard has enabled, so the bundled catalog leaves it out and a GMO checkout stays `unknown`, even when your SDK build can pay it. GMO token v1 (`ext/js/token.js`), GMO Elements card frames and Link-type payment pages name their processor and return `unknown` with `processor_variant_unverified`. Read the [GMO section of the README](./README.md#pay-a-gmo-payment-gateway-checkout) for what the SDK covers.
@@ -227,7 +229,7 @@ Amadeus Checkout (`amadeus_checkout`), which Air France and KLM run, ships dark
227
229
  | A single processor script during an incomplete scan | `unknown`; `psp` stays `null` and the script remains in `candidates` as `undetermined` until a complete scan confirms it. |
228
230
  | Visible card controls in a nested frame on a site no evidenced processor owns | `unknown` with `card_entry_unrecognized`; a co-loaded SDK script is listed as a candidate, not as the processor. |
229
231
 
230
- Reviewed merchant card endpoints are not processors. AT&T, Italo, Universal Orlando, Target Optical and Opus Virtual Offices post the card from their own checkout page to their own server, and the catalog carries no discovery rule for them, so an inspection of their checkout pages preserves `unknown`. The payment adapters recognize each merchant's exact card request (mode `card_endpoint`) and, while its profile is in observe, refuse it as `unsupported_checkout` before any authorization. All five are in observe; an `unknown` result there is not a path to payment.
232
+ Reviewed merchant card endpoints are not processors. AT&T, Italo, Universal Orlando, Target Optical, Opus Virtual Offices, Delta, Hilton, IHG, American Airlines, EconomyBookings, The Verb Hotel, RunSignUp, Mindbody and LookFantastic post the card from their own checkout page to their own server, Hyatt, LifeMiles, Southwest Airlines and United Airlines do the same after their page encrypts it under the merchant's own key, and Lexicon's FastSpring popup posts it to FastSpring's own session URL; the catalog carries no discovery rule for them, so an inspection of their checkout pages preserves `unknown`. The payment adapters recognize each merchant's exact card request (mode `card_endpoint`) and, while its profile is in observe, refuse it as `unsupported_checkout` before any authorization. So are the card forms Athens-Clarke County's MyRec page, Conduent eTIMS, the D-EDGE card frame and NIC Common Checkout submit as a page load. All of them are in observe; an `unknown` result there is not a path to payment.
231
233
 
232
234
  Processor support does not establish which payment method the merchant selected. Many SDKs also load wallets or fraud checks before a card form appears. Read `checkout_flow_status` and the returned limitations alongside `status`; never present `supported` alone as a promise that a merchant purchase will succeed.
233
235
 
package/README.md CHANGED
@@ -264,7 +264,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
264
264
  | GMO Payment Gateway | dark launch (mode `token`): only for an org Agentcard has enabled; see [Pay a GMO Payment Gateway checkout](#pay-a-gmo-payment-gateway-checkout) |
265
265
  | CyberSource Flex Microform | dark launch: Microform 2.12.1 card fields only, after `vault.syncRegistry()`, and only for an org Agentcard has enabled. For any other org the API does not serve the recognizer, so nothing pauses there. The cardholder's device seals the card for CyberSource and the card field receives the transient token. Validated in local Chromium; no live merchant purchase yet. See [Pay a CyberSource Flex Microform checkout](#pay-a-cybersource-flex-microform-checkout) |
266
266
  | Juspay Express Checkout | dark launch: the new-card flow Swiggy's website sends from its own page, for an org Agentcard has enabled, after `vault.syncRegistry()`. Validated in local Chromium with no live merchant purchase yet; see [Pay a Juspay checkout](#pay-a-juspay-checkout) |
267
- | Reviewed merchant card endpoints: AT&T, Italo, Universal Orlando, Target Optical, Opus Virtual Offices | recognized in mode `card_endpoint`, every one in observe: the adapters pause each merchant's own card request and refuse it as `unsupported_checkout` before any authorization, so the dummy card never reaches the merchant. A profile takes payments only after a reviewed partner HAR, a reviewed release of this SDK and the API, and per-org enablement |
267
+ | Reviewed merchant card endpoints: AT&T, Italo, Universal Orlando, Target Optical, Opus Virtual Offices, Delta, Hilton, IHG, American Airlines, EconomyBookings, The Verb Hotel, RunSignUp, Mindbody, LookFantastic, Lexicon (FastSpring's popup checkout), and Hyatt, LifeMiles, Southwest Airlines and United Airlines, whose page encrypts the card under the merchant's own key, and the card forms Athens-Clarke County's MyRec page, Los Angeles and San Francisco on Conduent eTIMS, the D-EDGE card frame and NIC Common Checkout (NJMCdirect and other state agencies) submit as a page load | recognized in mode `card_endpoint`, every one in observe: the adapters pause each merchant's own card request and refuse it as `unsupported_checkout` before any authorization, so the dummy card never reaches the merchant, and a request to the same URL with no card in it continues untouched. A profile takes payments only after a reviewed partner HAR, a reviewed release of this SDK and the API, and per-org enablement |
268
268
  | Datatrans Secure Fields (PCI Proxy; British Airways) | dark launch: the card-number iframe's TOKENIZE card post only, after `vault.syncRegistry()`, and only for an org Agentcard has enabled (see [Pay a Datatrans Secure Fields checkout](#pay-a-datatrans-secure-fields-checkout)). The cardholder's device sends the card to Datatrans and the iframe receives the token and its `cardInfo`. A post that asks PCI Proxy to keep the card on file, which British Airways's Nexus checkout sends for every new card, is refused before any prompt while Agentcard's card-on-file policy refuses it, so a Nexus checkout does not complete yet |
269
269
  | Authorize.Net Direct Post Method and SIM | dark launch (mode `hosted_form`, capability `authorize_net_dpm_form`): served only to an org Agentcard enabled for it. The card form posts one form navigation to `transact.dll` on `secure2`, `secure` or `test.authorize.net`; the cardholder's device submits it, and you poll the merchant's order state. Not eligible for auto-approval |
270
270
  | Pelecard (Iframe/Redirect 2.0) | dark: paused only for an org Agentcard enables for Pelecard, in test mode only. One-time card payments on `gateway21` and `gateway20.pelecard.biz`; pass `amount`, `currency` and the page's total as `pageAmount`. See [Pay a Pelecard checkout](#pay-a-pelecard-checkout) |
@@ -282,7 +282,7 @@ so new processors work without you shipping a release. `attachToCdp` derives the
282
282
  `Fetch.enable` url patterns from that same list rather than a constant, which is
283
283
  why the sync call belongs before the attach. `agentcard.checkout.cardUrlPatterns()` returns
284
284
  those patterns if you arm a CDP connection yourself. Call
285
- `GET /v2/checkout/recognizers?modes=token,cse,hosted_form,device_encrypt&capabilities=fiserv_card_capture,gmo_shared_pk,capture_context,juspay_txns.v1,datatrans_secure_fields_tokenize,authorize_net_dpm_form,pelecard_transaction,aci_split_card.v1,creditguard_pps_process,primer_payment_instruments,payrails_device_encrypt,converge_hpp_process,worldpay_eprotect_jsonp,wspay_confirm,shiji_sts_pinned_key,amadeus_checkout_actions.v1,worldline_mycheckout_form,verifone_card_capture`
285
+ `GET /v2/checkout/recognizers?modes=token,cse,hosted_form,device_encrypt&capabilities=fiserv_card_capture,gmo_shared_pk,capture_context,juspay_txns.v1,datatrans_secure_fields_tokenize,authorize_net_dpm_form,pelecard_transaction,aci_split_card.v1,creditguard_pps_process,primer_payment_instruments,payrails_device_encrypt,converge_hpp_process,worldpay_eprotect_jsonp,wspay_confirm,shiji_sts_pinned_key,amadeus_checkout_actions.v1,worldline_mycheckout_form,verifone_card_capture,ekashu_hosted_payment_page`
286
286
  for the list this build syncs. The API serves Fiserv's card capture only to a
287
287
  build that lists `fiserv_card_capture`, and only for a company Agentcard turned
288
288
  Fiserv on for. An older SDK never pauses a Fiserv request it could not finish,
@@ -298,7 +298,11 @@ confirm behind `wspay_confirm`, Shiji STS's card leg behind
298
298
  `amadeus_checkout_actions.v1`, Worldline MyCheckout's card form behind
299
299
  `worldline_mycheckout_form`, and Payrails' card save behind
300
300
  `payrails_device_encrypt` to a build that also asks for `device_encrypt`. The
301
- same holds for the Verifone profiles and `verifone_card_capture`.
301
+ same holds for the Verifone profiles and `verifone_card_capture`. NMI's hosted
302
+ payment page (`ekashu_hosted_payment_page`) is served on the same terms: this
303
+ build pauses the merchant's entry form to `live.ekashu.com` for the
304
+ cardholder's device and fails every other post your browser makes to NMI, so
305
+ NMI never sees the card your agent typed.
302
306
 
303
307
  ## Modes
304
308
 
@@ -420,17 +424,28 @@ mode; the difference matters if you drive `authorize()` yourself.
420
424
  page's origin, the exact origin, path and query of the card request, the
421
425
  session headers and cookies it needs, and the answer headers that may come
422
426
  back. The adapters arm every profile's endpoint (`cardEndpointPatterns()`
423
- lists the `Fetch.enable` patterns) and pause a POST that is exactly a
424
- profile's card request; a GET, a CORS preflight or a look-alike URL
425
- continues untouched. While this build's copy of a profile does not take
426
- payments (all five are in observe today) the request is refused as
427
- `unsupported_checkout` and the controller reads `unsupported` with reason
428
- `merchant_card_endpoint_not_enabled`, before any authorization exists. For a
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
435
+ build's copy of a profile does not take payments (every profile is in
436
+ observe today) a card request is refused as `unsupported_checkout` before
437
+ any authorization exists. When the body is the request the profile
438
+ reviewed, the controller reads `unsupported` with reason
439
+ `merchant_card_endpoint_not_enabled` and the event names the profile; a
440
+ card in any other body reads reason `unrecognized_payment_endpoint`, and
441
+ the event names only the endpoint origin. For a
429
442
  profile that takes payments, the adapter captures the request's session:
430
443
  the profile's own header names from the paused request, the endpoint's
431
444
  cookie jar for a cookie profile (`Network.getCookies` or
432
445
  `context.cookies()`, since the paused headers never carry it), and a
433
- `Referer` only when it is a page on the checkout origin. It sends them with
446
+ `Referer` only when it is a page on the origin the request is sent from: the
447
+ merchant's own card vault frame when the profile names one (EconomyBookings),
448
+ else the checkout page. It sends them with
434
449
  the paused body, which holds only the dummy card, in the create's
435
450
  `merchant_card_endpoint` member, never in `request`, and checks them first
436
451
  with the same rules the API and the device run. The cardholder's device
@@ -456,10 +471,71 @@ mode; the difference matters if you drive `authorize()` yourself.
456
471
  approval never outlives its request: a card request the page gives up on
457
472
  retires the authorization. After the hand-off the attachment holds, so the
458
473
  page's retry of the card request, or a step-up that sends the card again,
459
- is refused until you reconcile the order. Card endpoint events name the
474
+ is refused until you reconcile the order.
475
+
476
+ Lexicon sells through FastSpring's popup checkout, which needs four more
477
+ rules. The card is typed into FastSpring's frame on the store's own host
478
+ inside Lexicon's page, so the page a card request pauses on is that frame's
479
+ document (the frame tree in CDP, `request.frame()` in Playwright). FastSpring
480
+ posts PayPal and wallet payments to the same URL: a body whose `paymentType`
481
+ names another method and that carries no card continues untouched, and the
482
+ adapters emit `card_endpoint_passed`; one that names another method but
483
+ carries a card is held like any card request. FastSpring's EBANX step,
484
+ which reads the typed card back and posts it to `/3ds/return-ebanx`, is
485
+ aborted in every status
486
+ (`card_endpoint_refused` with reason `card_resend`, the controller reads
487
+ `unsupported` with reason `merchant_card_endpoint_resend_refused`); the
488
+ device withholds the answer that would start it, which the paused request
489
+ receives as a fixed `502` with code `agentcard_step_refused`. And the SDK
490
+ records FastSpring's own record of the order, read-only, from the popup
491
+ document and each JSON answer its page reads the order from, a quantity or
492
+ add-on change included. When the card request pauses it sends the newest one
493
+ with the create, cut to the total with tax, the currency, test mode and the
494
+ renewal marks; a total or currency other than your integer `amount` is an
495
+ `AmountMismatchError` against the merchant's total, and a renewing order or
496
+ a checkout in FastSpring's test mode is refused as `facts_refused`, both
497
+ before any authorization. A request the popup sends on the same checkout
498
+ after that record, other than one the SDK reads the order from or
499
+ FastSpring's customer recognition, leaves the order unknown: the payment is
500
+ refused as `facts_unavailable`, and a reload of the popup reads it again. The
501
+ SDK keeps 32 of these requests. A checkout that sends more is refused the
502
+ same way when one the SDK let go could be newer than the record it read, and
503
+ for the rest of that checkout session once one went while still loading. Card
504
+ endpoint events name the
460
505
  `profile` and the `endpoint` origin, never the request's URL, body or
461
506
  session: the URL carries the merchant's cart and booking ids.
462
507
 
508
+ Some of these merchants' pages submit the card form as a page load rather
509
+ than a fetch: the profile's `navigation` is `top`, or `frame` for a form
510
+ inside an iframe, which is then judged by the iframe's own document. The
511
+ same URL takes the page's other posts, so a post whose card fields are
512
+ empty and that holds no card number (a Cancel button, another payment
513
+ method) continues untouched in any status, and a card post a script sends
514
+ is aborted, since no page waits on it. The reviewed page load pauses, its
515
+ session carries only the cookie names the profile lists, and the
516
+ merchant's answer comes back as a page load: its page, or a 301, 302 or
517
+ 303 your browser follows in its own session with the merchant's cookies
518
+ written first. A 307 or 308 would make the browser post the typed form
519
+ again, so it is refused. Every stopped page load (a profile in observe, a
520
+ post that is not the reviewed form, a declined approval, an approval
521
+ already outstanding, a refused redirect, a withheld answer) is answered
522
+ with a small HTML page instead of an aborted load. Its
523
+ `<meta name="agentcard-checkout">`, its `x-agentcard-checkout` header and
524
+ its JSON block (`#agentcard-checkout`) say one of three outcomes.
525
+ `stopped_before_send`: nothing reached the merchant, and no other payment
526
+ for this checkout is waiting. `sent_answer_not_shown`: the card reached the
527
+ merchant, so check the payment with the merchant before paying again.
528
+ `payment_in_progress`: a payment for this checkout is waiting for approval
529
+ or may have reached the merchant, so check that payment and never send the
530
+ form again. The page reads `payment_in_progress` while an approval is
531
+ outstanding (the cardholder's device sends the card before it approves),
532
+ after a payment this attachment handed over or cannot account for, and when
533
+ the API refuses the create with `409 merchant_checkout_in_use`, as it does
534
+ on every later post of that attachment. A local deadline whose
535
+ authorization the API confirms it retired before any processor request
536
+ reads `stopped_before_send`: the `PaymentOutcomeUnknownError` then carries
537
+ `processorRequestStarted: false`.
538
+
463
539
  `syncRegistry()` asks the API for `SUPPORTED_MODES` only
464
540
  (`token,cse,hosted_form,device_encrypt`), so a processor whose flow this build
465
541
  cannot finish is never paused; the API serves `hosted_form` and `device_encrypt`
@@ -764,8 +840,11 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
764
840
  `amount_required`, `approval_required` (the attachment asks for
765
841
  Autopilot), `preparation_unsupported`, or `request_unsupported` (the
766
842
  paused request is not the card request Agentcard reviewed; `detail` names
767
- fields, never values). Every reason but `request_unsupported` holds the
768
- attachment. The adapters also emit `card_endpoint_refused` with the
843
+ fields, never values), `facts_unavailable` (the merchant keeps its own
844
+ record of the order and this runtime read none for this checkout) or
845
+ `facts_refused` (that record says the order renews, or that the checkout is
846
+ in test mode). Every reason but `request_unsupported`, `facts_unavailable`
847
+ and `facts_refused` holds the attachment. The adapters also emit `card_endpoint_refused` with the
769
848
  profile, the endpoint origin and the reason.
770
849
  - `PageAmountRequiredError` (`code: 'page_amount_required'`, `psp`): the paused
771
850
  request names no amount and no page total reached the SDK. For Pelecard the
@@ -1246,8 +1325,8 @@ event, `unreadable_gmo_request`, and the next one is judged again.
1246
1325
 
1247
1326
  ### Pay a CyberSource Flex Microform checkout
1248
1327
 
1249
- Amtrak, Sonos and Learning Without Tears take cards in CyberSource Flex
1250
- Microform. Each card field is CyberSource's own iframe, and on createToken the
1328
+ Amtrak, Sonos, Learning Without Tears and Alaska Airlines take cards in
1329
+ CyberSource Flex Microform. Each card field is CyberSource's own iframe, and on createToken the
1251
1330
  field seals what the agent typed into a JWE under a capture context the merchant
1252
1331
  minted for this checkout. Nothing in that body can be swapped, so the
1253
1332
  cardholder's device seals the real card under the same context and sends it to
@@ -1318,6 +1397,32 @@ const checkout = await attachToPlaywright(page, {
1318
1397
  agent types the last card field; `sandbox` is `testflex.cybersource.com` with
1319
1398
  CyberSource's test keys, and a test capture context pays only from a test
1320
1399
  client.
1400
+ - **Seconds, not thirty.** Some pages race createToken against a timer of
1401
+ their own: Alaska Airlines' flight checkout gives it three seconds, then
1402
+ shows "Token creation timed out". After Pay, the cardholder's device would
1403
+ still have to seal the card and reach CyberSource, and the SDK's first read
1404
+ comes two seconds after the create. For such a page, prepare once the card
1405
+ fields show, with `tokenizeBeforePay`:
1406
+
1407
+ ```ts
1408
+ // The agent has typed the placeholder card and the expiry; the fields are loaded.
1409
+ const prepared = await checkout.prepare({ psp: 'cybersource_flex', environment: 'production', tokenizeBeforePay: true });
1410
+ await page.getByRole('button', { name: 'Pay', exact: true }).click();
1411
+ ```
1412
+
1413
+ The attachment reads the capture context and the fields of the one card
1414
+ Microform on the page and sends them with the preparation. When the
1415
+ cardholder approves, their device seals the card under that context, gets
1416
+ CyberSource's transient token and keeps it; at Pay the SDK binds the field's
1417
+ request, reads every 100 ms for 10 seconds, and answers the page as soon as
1418
+ the device hands the token over. Only a request from the same fields, under
1419
+ the same capture context, binds: a page that loads its fields again (Alaska
1420
+ refreshes its context every 15 minutes) fails with `checkout_changed`, so
1421
+ prepare again. `prepare()` is refused before any preparation exists with
1422
+ `microform_not_loaded` (no card field yet), `microform_ambiguous` (two card
1423
+ Microforms), `microform_unsupported`, `capture_context_invalid` or
1424
+ `cybersource_environment_mismatch`. The token the device holds authorizes
1425
+ nothing on its own: if the agent never clicks Pay, it expires unused.
1321
1426
  - **One createToken per purchase.** A prepared approval binds one request. On
1322
1427
  Amtrak, leave "add to profile" unticked or check out as a guest: that box
1323
1428
  calls createToken a second time before Pay.
@@ -1,6 +1,7 @@
1
1
  declare var MERCHANT_EXCHANGES_MAX: number;
2
2
  declare var MERCHANT_PROFILE_RECOGNIZERS: any;
3
3
  declare function declaredEndpointRefusal(endpoint: any): string | null;
4
+ declare function isAdyenWebJweCompact(s: any): boolean;
4
5
  declare function sha256Hex(input: any): string;
5
6
  declare function merchantAmountFromResponses(profile: any, input: any): {
6
7
  ok: boolean;
@@ -274,4 +275,4 @@ declare function declaredUrlGlobs(endpoint: any): string[];
274
275
  declare function merchantProfileRulesMatch(profileId: any, served: any): boolean;
275
276
  declare function merchantProfileEnvironment(profileId: any): "production" | "sandbox";
276
277
  declare function merchantProfileEndpointFor(profileId: any, checkoutOrigin: any): string | null;
277
- export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };
278
+ export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, isAdyenWebJweCompact, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 83b01fa41dce013a97e4daf34de13d53978f6ef972e0328c67b3380f057b4e17
3
+ // artifact-sha256: c979802cf1a5aa95eedd5dd00949cd56c5c478ca3c637b3232b69597cc62d4a4
4
4
  // src/deep-freeze.js
5
5
  function deepFreeze(value) {
6
6
  if (value && typeof value === "object" && !Object.isFrozen(value)) {
@@ -79,9 +79,14 @@ var PLACEHOLDER_NAME = /^[A-Za-z][A-Za-z0-9_]{0,31}$/;
79
79
  var LITERAL_SEGMENT = /^[A-Za-z0-9._~@+-]{1,256}$/;
80
80
  var QUERY_KEY = /^[A-Za-z0-9._~-]{1,64}$/;
81
81
  var QUERY_LITERAL = /^[A-Za-z0-9._~,-]{0,256}$/;
82
+ var QUERY_PREFIX = /^(?:[A-Za-z0-9._~,-]|%[0-9A-F]{2}){1,64}$/;
82
83
  var MAX_QUERIES = 8;
83
84
  var MAX_PAIRS = 16;
84
85
  var placeholderValue = (value) => PLACEHOLDER_VALUE.test(value) && value !== "." && !value.includes("..");
86
+ function queryPlaceholder(value) {
87
+ const mark = value.lastIndexOf(":");
88
+ return mark === -1 ? null : { prefix: value.slice(0, mark), name: value.slice(mark + 1) };
89
+ }
85
90
  function pathPlaceholders(template, pathname) {
86
91
  if (typeof template !== "string" || typeof pathname !== "string")
87
92
  return null;
@@ -126,10 +131,14 @@ function queryPlaceholders(query, text) {
126
131
  const written = got[i].slice(mark + 1);
127
132
  if (typeof value !== "string")
128
133
  return null;
129
- if (value.startsWith(":")) {
130
- if (!placeholderValue(written))
134
+ const placeholder = queryPlaceholder(value);
135
+ if (placeholder) {
136
+ if (!written.startsWith(placeholder.prefix))
137
+ return null;
138
+ const rest = written.slice(placeholder.prefix.length);
139
+ if (!placeholderValue(rest))
131
140
  return null;
132
- values[value.slice(1)] = written;
141
+ values[placeholder.name] = rest;
133
142
  }
134
143
  else if (written !== value) {
135
144
  return null;
@@ -178,9 +187,11 @@ function templatePlaceholders(template) {
178
187
  const queries = Array.isArray(template?.queries) ? template.queries : [template?.query];
179
188
  for (const query of queries) {
180
189
  if (query && typeof query === "object") {
181
- for (const value of Object.values(query))
182
- if (typeof value === "string" && value.startsWith(":"))
183
- names.push(value.slice(1));
190
+ for (const value of Object.values(query)) {
191
+ const placeholder = typeof value === "string" ? queryPlaceholder(value) : null;
192
+ if (placeholder)
193
+ names.push(placeholder.name);
194
+ }
184
195
  }
185
196
  }
186
197
  return names;
@@ -215,7 +226,8 @@ function queryTemplateRefusal(query) {
215
226
  return `the query key "${key}" is not unreserved characters`;
216
227
  if (typeof value !== "string")
217
228
  return `the query value of ${key} is not text`;
218
- if (value.startsWith(":") ? !PLACEHOLDER_NAME.test(value.slice(1)) : !QUERY_LITERAL.test(value)) {
229
+ const placeholder = queryPlaceholder(value);
230
+ if (placeholder ? !PLACEHOLDER_NAME.test(placeholder.name) || placeholder.prefix !== "" && !QUERY_PREFIX.test(placeholder.prefix) : !QUERY_LITERAL.test(value)) {
219
231
  return `the query value of ${key} is neither a literal nor a :name placeholder`;
220
232
  }
221
233
  }
@@ -1628,40 +1640,38 @@ function merchantBodyRefusal(profile, body) {
1628
1640
  return null;
1629
1641
  }
1630
1642
  // src/adyen-jwe.js
1631
- var ADYEN_JWE_HEADER = { alg: "RSA-OAEP-256", enc: "A256GCM", version: "1" };
1632
1643
  var BASE64URL = /^[A-Za-z0-9_-]+$/;
1633
- function isAdyenJweCompact(s) {
1634
- if (typeof s !== "string" || s.length > 2048)
1635
- return false;
1636
- const parts = s.split(".");
1637
- if (parts.length !== 5)
1638
- return false;
1639
- const [header, encryptedKey, iv, ciphertext, tag] = parts;
1640
- if (!parts.every((p) => p.length > 0 && BASE64URL.test(p)))
1641
- return false;
1642
- if (encryptedKey.length !== 342 || iv.length !== 16 || tag.length !== 22)
1643
- return false;
1644
- if (ciphertext.length > 1024)
1645
- return false;
1646
- return isAdyenJweHeader(header);
1647
- }
1648
- function isAdyenJweHeader(segment) {
1649
- if (!BASE64URL.test(segment) || segment.length > 128)
1650
- return false;
1644
+ var ADYEN_WEB_JWE_FORMATS = /* @__PURE__ */ Object.freeze([
1645
+ /* @__PURE__ */ Object.freeze({ alg: "RSA-OAEP-256", enc: "A256GCM", iv: 16, tag: 22 }),
1646
+ /* @__PURE__ */ Object.freeze({ alg: "RSA-OAEP", enc: "A256CBC-HS512", iv: 22, tag: 43 })
1647
+ ]);
1648
+ var ADYEN_WEB_CIPHERTEXT_MAX = 8192;
1649
+ var ADYEN_WEB_HEADER_MAX = 128;
1650
+ function adyenWebJweHeader(segment) {
1651
+ if (!BASE64URL.test(segment) || segment.length > ADYEN_WEB_HEADER_MAX)
1652
+ return null;
1651
1653
  let parsed;
1652
1654
  try {
1653
1655
  parsed = JSON.parse(new TextDecoder().decode(Uint8Array.from(atob(segment.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0))));
1654
1656
  }
1655
1657
  catch {
1656
- return false;
1658
+ return null;
1657
1659
  }
1658
1660
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
1661
+ return null;
1662
+ return Object.keys(parsed).sort().join(",") === "alg,enc,version" && parsed.version === "1" ? parsed : null;
1663
+ }
1664
+ function isAdyenWebJweCompact(s) {
1665
+ if (typeof s !== "string" || s.length > ADYEN_WEB_HEADER_MAX + 342 + 22 + ADYEN_WEB_CIPHERTEXT_MAX + 43 + 4)
1659
1666
  return false;
1660
- const obj = parsed;
1661
- const keys = Object.keys(obj).sort();
1662
- if (keys.join(",") !== "alg,enc,version")
1667
+ const parts = s.split(".");
1668
+ if (parts.length !== 5 || !parts.every((p) => p.length > 0 && BASE64URL.test(p)))
1669
+ return false;
1670
+ const [header, encryptedKey, iv, ciphertext, tag] = parts;
1671
+ if (encryptedKey.length !== 342 || ciphertext.length > ADYEN_WEB_CIPHERTEXT_MAX)
1663
1672
  return false;
1664
- return obj.alg === ADYEN_JWE_HEADER.alg && obj.enc === ADYEN_JWE_HEADER.enc && obj.version === ADYEN_JWE_HEADER.version;
1673
+ const parsed = adyenWebJweHeader(header);
1674
+ return parsed !== null && ADYEN_WEB_JWE_FORMATS.some((form) => parsed.alg === form.alg && parsed.enc === form.enc && iv.length === form.iv && tag.length === form.tag);
1665
1675
  }
1666
1676
  // src/sha256.js
1667
1677
  var K = /* @__PURE__ */ new Uint32Array([
@@ -1861,16 +1871,18 @@ function merchantProfileUrlRelation(profileId, rawUrl) {
1861
1871
  return "then";
1862
1872
  return otherCardRequestOf(profile, url) ? "other" : null;
1863
1873
  }
1864
- var JWE_AT = /ey[A-Za-z0-9_-]{1,126}\.[A-Za-z0-9_-]{342}\.[A-Za-z0-9_-]{16}\.[A-Za-z0-9_-]{1,1024}\.[A-Za-z0-9_-]{22}/y;
1874
+ var JWE_AT = /* @__PURE__ */ ADYEN_WEB_JWE_FORMATS.map(({ iv, tag }) => new RegExp(`ey[A-Za-z0-9_-]{1,126}\\.[A-Za-z0-9_-]{342}\\.[A-Za-z0-9_-]{${iv}}\\.[A-Za-z0-9_-]{1,${ADYEN_WEB_CIPHERTEXT_MAX}}\\.[A-Za-z0-9_-]{${tag}}`, "y"));
1865
1875
  var LEGACY_BLOB = /adyenjs_0_1_\d{1,4}(?:\$|%24)/i;
1866
1876
  function textCarriesCiphertext(text) {
1867
1877
  if (LEGACY_BLOB.test(text))
1868
1878
  return true;
1869
1879
  for (let at = text.indexOf("ey"); at !== -1; at = text.indexOf("ey", at + 1)) {
1870
- JWE_AT.lastIndex = at;
1871
- const match = JWE_AT.exec(text);
1872
- if (match && isAdyenJweCompact(match[0]))
1873
- return true;
1880
+ for (const pattern of JWE_AT) {
1881
+ pattern.lastIndex = at;
1882
+ const match = pattern.exec(text);
1883
+ if (match && isAdyenWebJweCompact(match[0]))
1884
+ return true;
1885
+ }
1874
1886
  }
1875
1887
  return false;
1876
1888
  }
@@ -1935,7 +1947,7 @@ function holderRefusal(profile, parsed) {
1935
1947
  return `the paused body carries no card object where ${profile.merchant}'s card request holds it`;
1936
1948
  }
1937
1949
  const values = Object.values(profile.fields).map((name) => Object.prototype.hasOwnProperty.call(holder.value, name) ? holder.value[name] : void 0);
1938
- if (!values.every((value) => typeof value === "string" && isAdyenJweCompact(value))) {
1950
+ if (!values.every((value) => typeof value === "string" && isAdyenWebJweCompact(value))) {
1939
1951
  return `the paused body does not carry the four encrypted card fields ${profile.merchant}'s card request holds a new card in`;
1940
1952
  }
1941
1953
  if (new Set(values).size !== values.length)
@@ -2133,4 +2145,4 @@ function merchantProfileEndpointFor(profileId, checkoutOrigin) {
2133
2145
  const query = Object.entries(profile.query).map(([key, value]) => `${key}=${value}`).join("&");
2134
2146
  return `${pair.endpoint}${profile.path}${query ? `?${query}` : ""}`;
2135
2147
  }
2136
- export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };
2148
+ export { MERCHANT_EXCHANGES_MAX, MERCHANT_PROFILE_RECOGNIZERS, carriesAdyenCardCiphertext, classifyDeclaredMerchantRequest, classifyMerchantBody, classifyMerchantRequest, declaredEndpointRefusal, declaredTemplatedUrl, declaredUrlGlobs, declaredUrlRelation, isAdyenWebJweCompact, merchantAmountExchanges, merchantAmountFromResponses, merchantAmountPending, merchantAmountStagesOf, merchantBodyKeyPathSha256, merchantChargedFromResponses, merchantProfileEndpointFor, merchantProfileEnvironment, merchantProfileRulesMatch, merchantProfileTemplatedUrl, merchantProfileUrlGlobs, merchantProfileUrlRelation, merchantTotalFromResponses, sha256Hex };