@agent-cards/sdk 0.23.19 → 0.23.21

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +61 -2
  2. package/PREFLIGHT.md +4 -0
  3. package/README.md +175 -7
  4. package/dist/amadeus.d.ts +4 -2
  5. package/dist/amadeus.js +16 -3
  6. package/dist/buy.d.ts +84 -2
  7. package/dist/buy.js +16 -1
  8. package/dist/card-endpoint-facts.d.ts +2 -1
  9. package/dist/card-endpoint-facts.js +18 -9
  10. package/dist/card-endpoint.d.ts +20 -4
  11. package/dist/card-endpoint.js +38 -4
  12. package/dist/card-fields.generated.js +1444 -51
  13. package/dist/cdp.d.ts +29 -0
  14. package/dist/cdp.js +444 -24
  15. package/dist/client.d.ts +134 -7
  16. package/dist/client.js +350 -25
  17. package/dist/comgate.d.ts +50 -0
  18. package/dist/comgate.js +80 -0
  19. package/dist/cybersource-secure-acceptance.generated.d.ts +1 -1
  20. package/dist/eprotect.generated.d.ts +1 -1
  21. package/dist/gmo.generated.js +7 -3
  22. package/dist/hipay.generated.d.ts +56 -0
  23. package/dist/hipay.generated.js +392 -0
  24. package/dist/hold.d.ts +180 -0
  25. package/dist/hold.js +270 -0
  26. package/dist/hosted-entry.d.ts +4 -0
  27. package/dist/hosted-entry.js +32 -0
  28. package/dist/index.d.ts +4 -1
  29. package/dist/index.js +1 -0
  30. package/dist/lifecycle.d.ts +10 -0
  31. package/dist/lifecycle.js +21 -0
  32. package/dist/merchant-card-endpoints.generated.js +102 -11
  33. package/dist/paddle-tokenex.generated.js +7 -3
  34. package/dist/paddle-vgs.generated.d.ts +3 -2
  35. package/dist/paddle-vgs.generated.js +9 -5
  36. package/dist/payrails.generated.js +7 -3
  37. package/dist/pre-claim.d.ts +20 -0
  38. package/dist/pre-claim.js +202 -22
  39. package/dist/preflight-catalog.json +58 -0
  40. package/dist/preflight-schemas.json +4 -0
  41. package/dist/preflight.generated.js +26 -2
  42. package/dist/prepared-processor.d.ts +6 -2
  43. package/dist/prepared-processor.js +18 -2
  44. package/dist/primer.generated.js +1487 -94
  45. package/dist/przelewy24.d.ts +47 -0
  46. package/dist/przelewy24.js +88 -0
  47. package/dist/quickpay.d.ts +44 -0
  48. package/dist/quickpay.js +65 -0
  49. package/dist/registry.d.ts +6 -5
  50. package/dist/registry.js +27 -7
  51. package/dist/shiji-sts.generated.js +7 -3
  52. package/dist/stripe-checkout.generated.js +744 -13
  53. package/dist/tokenex-iframe.d.ts +93 -0
  54. package/dist/tokenex-iframe.generated.d.ts +81 -0
  55. package/dist/tokenex-iframe.generated.js +403 -0
  56. package/dist/tokenex-iframe.js +195 -0
  57. package/dist/transbank.d.ts +45 -0
  58. package/dist/transbank.js +73 -0
  59. package/dist/twoc2p.d.ts +54 -0
  60. package/dist/twoc2p.generated.d.ts +33 -0
  61. package/dist/twoc2p.generated.js +1149 -0
  62. package/dist/twoc2p.js +85 -0
  63. package/dist/voice.d.ts +16 -13
  64. package/dist/voice.js +13 -10
  65. package/examples/preflight/kernel-native/inventory.json +1 -1
  66. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.23.21
6
+
7
+ - Pause ComGate's payment gateway, where most Czech and Slovak e-shops send the buyer, for the cardholder's device. The sync now also asks for `capabilities=comgate_hosted_payment_page`, and the API serves ComGate's recognizer only for a company Agentcard turned ComGate on for. The merchant's server creates the payment at ComGate and sends the buyer to `https://payN.comgate.cz/init?id=<transaction id>`. The SDK pauses that link, in the page, in a frame of it or in a window it opened, as a `hosted_form` authorization that carries the link alone, and answers the paused navigation with the `submitted_on_device` page once the cardholder's device has paid it. The link names no amount, so `authorize()` refuses one without `amount` and `currency`, above zero, with `AmountRequiredError`. Every request but a GET that your browser makes to ComGate's API (`payments.comgate.cz`, `payments-gcp.comgate.cz`) or its card frame hosts (`sec.comgate.cz`, `sec-gcp.comgate.cz`) carries the card your agent typed, part of it or its ciphertext, and the SDK fails each one and reads `unsupported` with reason `processor_card_page`. Your events and error messages name the link without its id. Matching API and Vault releases are required. ComGate stays off for every company Agentcard has not turned it on for.
8
+
9
+ - Pause card requests in a window the merchant's page opens. `attachToCdp` now attaches each window the attached page opens (`window.open`, or a form or link aimed at a new window) in the page's own browser context, and each window such a window opens, and arms it as it arms the page, so a request there reaches the same pause and the same gates. A hosted payment page the merchant opens in its own window, on the merchant's page first and then sent on to the processor, pauses there, and the window gets the `submitted_on_device` page. Earlier releases never saw a request made in such a window. The attach starts when Chromium reports the window, so a window opened straight onto a processor's page can still load it before it is armed. A window another page opens, or one in another browser context, is left alone. `attachToPlaywright` routes the page's own requests, as before.
10
+
11
+ - Name the checkout page when the request `attachToCdp` pauses is the page's own navigation away from it. A hosted payment page entry that the merchant's page sends its window to, by script, by a form or through the merchant's redirect, pauses the page's top-level navigation, and Chromium does not answer a read of that page's frame tree while it is paused. Earlier releases waited one second for that read and then created the authorization with no `checkout_origin`, so a processor whose rules hold the payment to a reviewed merchant's page refused it. The adapter now keeps the URL the page last committed, from its target when it attaches and from each document the page commits after, and names its origin at once. A request from a frame, a worker or another document is read as before.
12
+
13
+ - Pay a MakeCommerce card payment through the checkout relay, for a company Agentcard turns MakeCommerce on for (dark launch). MakeCommerce (Maksekeskus, in Estonia, Latvia, Lithuania and Finland) takes the card in its own card frame, which posts it to `https://cc.maksekeskus.ee/card-payment` under the shop's publishable key. This build declares `makecommerce_card_frame` on every sync, and the API serves the MakeCommerce entry only to a company Agentcard turned MakeCommerce on for, so every other company's MakeCommerce payments go through untouched, as for an unsupported processor. For an enabled company the SDK pauses only the frame's own one-time card payment; a post with "Remember my card" or the subscription box ticked is refused before anything is sent. The post names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total the shop's order page shows, in euros, the one currency MakeCommerce charges. The cardholder's device sends the card through the checkout relay, and the frame gets MakeCommerce's answer, the URL of its 3-D Secure page, which the frame opens in the agent's browser, where a challenge needs a person at that browser. A MakeCommerce payment also needs the matching API, Vault and checkout relay releases.
14
+
15
+ - Pay a JetBlue booking's card form, TokenEx's hosted iframe v3, for a company Agentcard turns the TokenEx iframe on for (dark launch). JetBlue takes the card number and CVV in TokenEx's cross-origin card-number frame on `htp.tokenex.com` (`test-htp.tokenex.com` in test); the cardholder name and the expiry stay on JetBlue's page. The frame posts the whole card as JSON to its own URL, query included. This build declares `tokenex_iframe_tokenize` on every sync, and the API serves the TokenEx entry only to a company Agentcard turned it on for, so every other company's TokenEx requests go through untouched. For an enabled company both adapters pause only that frame's own card post (on CDP, on the out-of-process frame's own session) and refuse every other post on the frame's path that carries a card, a stored token's CVV re-entry or a query this build has not read among them, with a `blocked` event, detail `invalid_tokenex_iframe_request`, before anything is created and without holding the page. The post names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total JetBlue's page shows. Once the cardholder approves, the frame gets TokenEx's token as the API recorded it, rebuilt from payment-core's allow-list under a JSON content type, and adds its own `cardType`; an approval whose recorded answer is not a token ends `PaymentOutcomeUnknownError`, reason `tokenex_iframe_token_unconfirmed`, and the frame gets nothing. The frame answers a repeat with nothing changed from its own cache; when it sends the same bytes again (its five-minute timer, a reset, a reload at the same URL), the SDK answers that post with the same token, a `repeat_answered` event and no second prompt, while that approval is the checkout's current one, for at most the iframe key's lifetime, and once the token reached the page only while `resolveMerchantResult` reports the order `pending`. Another placeholder or CVV, a re-created frame or a new key asks the cardholder again. `placeholderCard({ user, psp: 'tokenex_iframe' })` gives a Visa, Mastercard or American Express number the frame reads as the stored card's own brand, and refuses every other network (`brand_unsupported`), since the cardholder's device refuses a placeholder of another brand. Autopilot never pays it. Today the API refuses every TokenEx create as `unsupported_checkout` (`tokenex_iframe_account_unreviewed`) until Agentcard lists JetBlue's account, and a TokenEx payment also needs the matching API, Vault and checkout relay releases.
16
+
17
+ - Pause Przelewy24's pay wall (where most Polish shops send a buyer who pays by card) for the cardholder's device. The sync now also asks for `capabilities=przelewy24_hosted_payment_page`, and the API serves Przelewy24's recognizer only for a company Agentcard turned Przelewy24 on for. The SDK pauses the pay wall link, `https://secure.przelewy24.pl/trnRequest/<token>` (or `sandbox.przelewy24.pl` for a test payment), 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. The link names no amount, so send the amount Przelewy24's pay wall shows, with its currency: the API refuses a create without one (`amount_required`). The merchant's page sends the buyer there by a script, by its server's redirect or in a window it opens. `attachToCdp` pauses all three; `attachToPlaywright` pauses only the script's navigation in the page, because Playwright continues a redirect itself and does not route a window the page opens, so attach to a Przelewy24 checkout with `attachToCdp`. Every post a page the SDK watches makes to the pay wall, such as its card method's post and its card form's post, and the request to its Saferpay resolver, which names the typed card's type, leads to a card: the SDK fails each one and reads `unsupported` with reason `processor_card_page`. The SDK watches the attached page and its frames, and under `attachToCdp` each window the page opens. A window `attachToPlaywright` does not watch reaches the pay wall untouched, with the placeholder card your agent types. Matching API and Vault releases are required. Przelewy24 stays off for every company Agentcard has not turned it on for.
18
+
19
+ - Pay a Weeztix ticket shop's card form, which sends the card to Pay. (pay.nl), for a company Agentcard turned Pay. on for (dark launch): the SDK declares `pay_nl_card_token`, pauses the form's card token request and hands the form Pay.'s token answer. Pay.'s request names no price, so pass `amount` and `currency`, above zero: that is the price the cardholder approves. Without them, `authorize()` throws before anything is created:
20
+
21
+ ```text
22
+ AmountRequiredError: A Pay. payment request names no amount, so pass amount and currency: that is the amount the cardholder approves. Nothing was created and nothing was charged.
23
+ ```
24
+
25
+ - Pay a PayPal Card Fields checkout, such as a WooCommerce PayPal Payments store's "Debit & Credit Cards" option, for a company Agentcard turned PayPal Card Fields on for (dark launch). The store's card fields are PayPal's frames, which post the card to `https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source` (Card Fields v5) or the same path on `api-m.paypal.com` (v6). The sync now also asks for `capabilities=paypal_card_fields`, and the API serves PayPal's recognizer only to an enabled company. There both adapters pause the frames' own confirm, `authorize()` creates the authorization with it, the cardholder's device sends the card (a v5 confirm through the checkout relay, a v6 one directly), and the frame receives the order's id and status, after which the store's server captures the payment. The confirm names no amount, so pass `amount` and `currency` above zero: `authorize()` refuses one without them with `AmountRequiredError`. A confirm that asks PayPal to keep the card or asks for 3-D Secure on every payment fails in the browser with a `blocked` event. The SDK arms only the confirm's path on PayPal's hosts, never the rest of `www.paypal.com`. Every other company keeps the refusal described below, `unsupported_paypal_card_fields`. A payment also needs the matching API, relay and Vault releases.
26
+
27
+ - Name PayPal Card Fields as PayPal in a preflight inspection. PayPal's JS SDK draws each card field as a frame on `www.paypal.com`, at `/smart/card-field` for Card Fields v5 and at `/web-sdk/v6/card-fields-content` for v6, and an inspection that saw only those frames named no processor (`psp_not_detected`). Both frames now name `paypal`, and the result stays `unknown` while PayPal Card Fields ships dark. The catalog's `catalog_sha256` changes; `catalog_version` stays `2026-09-15.1`.
28
+
29
+ - Pay GoPay's card form through the checkout relay, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once a reviewed change binds GoPay's payee and amount to the approval. A Czech or Slovak shop opens GoPay's gateway as a page or as an overlay frame on its own page, and the gateway's card frame posts the card as JSON to its own URL, `https://secure.gopay.com/gp-card-comm/h/<token>` (`secure.sandbox.gopay.com` in test). This build declares `gopay_card_form` on every sync, and the API serves the GoPay entry only to a company Agentcard turned GoPay on for, so every other company's GoPay payments go through untouched, as for an unsupported processor. For an enabled company the SDK pauses only the card frame's own card post. One card frame serves every GoPay shop and its post names none, so the API takes a live post only on the checkout page of a shop Agentcard reviewed (Caramilla.cz today), refuses any other with `unsupported_checkout`, and names the reviewed shop, whatever `merchant` you send. A shop that sends the buyer away to GoPay's gateway leaves the gateway as the checkout page, which names no shop, so it is refused the same way. The post names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total GoPay's gateway shows. The cardholder's device sends the card through the checkout relay, and the frame gets GoPay's answer, an empty JSON object. GoPay's gateway then reads how the payment ended from GoPay and runs 3-D Secure in the agent's browser when the bank asks for it, where a challenge needs a person at that browser. A GoPay shop takes a card issued outside Europe only when it asked GoPay for that, and GoPay refuses such a card at authorization, with nothing charged. Your events and error messages also hide a GoPay card form's token behind any path on GoPay's card frame hosts, not only after `/h/`, and with any of its digits percent-escaped. A GoPay payment also needs the matching API, Vault and checkout relay releases.
30
+
31
+ - Keep a processor's path credential out of hold mode. `onHeld`, `controller.hold.held`, the `card_request_held`, `card_request_refused`, `card_request_expired` and `card_request_lost` events, and the `card_request_unwatched` event a shared worker's card request raises named the request's origin and path as written, so a credential a processor writes into the path, such as a GoPay card form's token or a PayMongo session, showed there. They now name it as `redactUrl` does, as every other event already did. No API or Vault release is needed.
32
+
33
+ - Pay a Revolut Business card field, from Agentcard's own company only for now (dark launch): Agentcard turns it on for another company once the API reads the order a payment names and holds its merchant, amount and currency to what the cardholder approves. Revolut's card field and card popup, on a merchant's own page (Revolut's WooCommerce plugin among them) or on Revolut's hosted checkout, post the card from their frame on `merchant.revolut.com` to `https://merchant.revolut.com/api/public/orders/<order token>/payments` as one JSON body. This build declares `revolut_card_field` on every sync, and the API serves the Revolut entry only to a company Agentcard turned Revolut on for, so every other company's Revolut payments go through untouched, as for an unsupported processor. For an enabled company the SDK pauses only the frame's card payment: a payment by Apple Pay, Google Pay or Revolut Pay on the same URL, and the frame's poll of the payment, continue untouched, and a card payment that asks Revolut to keep the card on file, or any other card body that is not the frame's own, fails in the browser before anything is created. The card payment names the order's amount in minor units but not its currency, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the order's total and its currency. The API refuses an amount that is not the card payment's, or a currency Revolut and Agentcard do not both count in hundredths (`amount_mismatch`), and any request that is not the card field's own (`invalid_revolut_request`). The cardholder's device sends the card to Revolut itself, since Revolut's host answers the Vault's origin, and the frame gets Revolut's own answer: the payment Revolut created, which the frame then follows through any 3-D Secure step in the agent's browser, where a challenge needs a person at that browser. No live payment has gone through this route yet. A payment also needs the matching API and Vault releases.
34
+
35
+ - Pay SumUp's Payment Widget for a company Agentcard turns SumUp on for (dark launch). SumUp's WooCommerce plugin, payment links, Online Store and invoices all mount one widget, whose hidden form frame on `gateway.sumup.com` sends the card in one `PUT` to `https://api.sumup.com/v0.2/checkouts/<checkout id>`. This build declares `sumup_checkout_process` on every sync, and the API serves the SumUp entry only to a company Agentcard turned SumUp on for, so every other company's SumUp payments go through untouched. For an enabled company the SDK pauses that `PUT` and only that method, through `attachToCdp` or `attachToPlaywright`: the form frame is on another origin than the page, so an attachment inside the page never sees it. For that company every other body in that `PUT` fails in the frame before the SDK creates anything: a method other than a card, a card body that saves the card or splits it into installments, and the card body of SumUp's own Hosted Checkout page (`checkout.sumup.com`), which also names the product and the page's session. The request names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total SumUp's payment page shows. The API reads the checkout from SumUp, refuses a total that is not the checkout's exactly (`amount_mismatch`), a test-mode create (`live_host_on_sandbox_row`), a checkout that is paid, failed, expired, sets up recurring payments or belongs to a SumUp test merchant (`sumup_checkout_unpayable`), and a merchant Agentcard has not reviewed (`unsupported_checkout`), and it names the reviewed merchant, whatever `merchant` you send. The cardholder's device sends the card to SumUp itself, and the frame gets SumUp's own answer: the paid checkout, or the 3-D Secure step the widget then runs in the agent's browser, where a challenge needs a person at that browser. A SumUp payment also needs the matching API and Vault releases.
36
+
37
+ - Pay a GoDaddy Payments checkout, a GoDaddy Online Store's card step, for a company Agentcard turned GoDaddy Payments on for (dark launch). The store's card form is Poynt's frame on `cdn.poynt.net`, which posts the card to `https://services.poynt.net/v1/commerce/stores/<store id>/payment-tokens/generate-nonce`. The sync now also asks for `capabilities=poynt_collect_nonce`, and the API serves Poynt's recognizer only to an enabled company. There both adapters pause that post, `authorize()` creates the authorization with it, the cardholder's device sends the card through the checkout relay, and the frame receives the nonce Poynt minted, which the store's page sends to its own server to charge. The request names no amount, so pass `amount` and `currency` above zero: `authorize()` refuses one without them with `AmountRequiredError`. A live payment needs a store Agentcard reviewed at its own checkout page, or the create answers `unsupported_checkout`, and a test key is refused, since Poynt has no test host. A form that asks Poynt to keep the card fails in the browser with a `blocked` event. The store's pay button runs an invisible reCAPTCHA, so a person presses it. Every other company keeps today's path, where the request continues to Poynt untouched. A payment also needs the matching API, relay and Vault releases.
38
+
39
+ - Pause Transbank's Webpay Plus, the card payment form most Chilean shops send the buyer to, for the cardholder's device. The sync now also asks for `capabilities=transbank_webpay_hosted_payment_page`, and the API serves Webpay's recognizer only for a company Agentcard turned Webpay on for. The merchant's server creates the transaction at Transbank and sends the buyer to `https://webpay3g.transbank.cl/webpayserver/initTransaction?token_ws=<token>` with a GET, as the official WooCommerce plugin does. The SDK pauses that entry in the page or a frame of it, and with `attachToCdp` also in a window the page opened, as a `hosted_form` authorization that carries the entry alone, and answers the paused navigation with the `submitted_on_device` page once the cardholder's device has paid it. With `attachToPlaywright`, a popup that opens Webpay needs its own attachment, and the entry pauses only when the page navigates to it directly or by script. A merchant whose server redirects the checkout to Webpay needs `attachToCdp`, because Playwright's `page.route` never sees a redirect hop; with `attachToPlaywright` that checkout reads `unsupported` with reason `processor_card_page` at Webpay's token submit, and nothing is paid. The entry names no amount, so `authorize()` refuses one without `amount` and `currency`, above zero, with `AmountRequiredError`; the API takes only Chilean pesos (`clp`). Every request but a GET that your browser makes to Webpay's hosts (`webpay3g.transbank.cl`, `webpay3gint.transbank.cl`) either spends the token (`init_transaction.cgi`) or carries the card your agent typed (Webpay's card lookup, card check and card post), and the SDK fails each one and reads `unsupported` with reason `processor_card_page`. Your events and error messages name the entry without its token. Matching API, relay and Vault releases are required. Webpay stays off for every company Agentcard has not turned it on for.
40
+
41
+ ## 0.23.1 to 0.23.20
42
+
43
+ - Continue a browser checkout from typed fields. `buy.ask` and `buy.confirm` now return `checkout_handoff`, the step your own agent takes when the purchase agent hands it a matched offer's checkout: the `buy_url` to open in the browser that checks out, the store and product, the listed price, the checkout authorization to create and the time to create it by. It is null on every other turn, and on a kind of step this version does not know. `buy.conversation` now returns `open_offers`, each offer handed to your agent and how far it got, and `browser_checkouts`, each checkout Agentcard ran in its own browser with its status, total, cart hash, approval link and order id. Before, the SDK dropped all three, so a caller read the step from `reply` and could not recover a browser checkout's approval link or order id from the conversation. Both lists are empty when the API sends none. TypeScript: `BuyTurn` gains `checkout_handoff` and `BuyConversation` gains `open_offers` and `browser_checkouts`; the new types `BuyCheckoutHandoff`, `BuyOpenOffer`, `BuyBrowserCheckout` and `BuyApiAmount` are exported. The listed price and the authorization hint arrive as the API writes them (`BuyApiAmount`: their `currency` typed, their amount fields passed through). An API that sends none of them reads as null and empty lists, so this release works before and after the API release that adds the browser path.
44
+
45
+ - Hold a card request with nothing created until the person confirms the total. `attachToCdp(…, { hold: true })` holds the first card request; `controller.release({ amount, currency, pageTotal })` creates the authorization at that total only when the page still shows it (`upTo: true` for a ceiling), and `controller.hold.cancel()` fails it. A held request is cancelled before its processor's own wait, and every other card request fails at once. Hold mode also holds Bolt's tokenizer (cancel only) and, with `placeholderNumbers`, any request carrying the placeholder card. New `serviceWorkers: 'bypass'` sends every armed frame's requests past site service workers instead of refusing the page, and `sharedWorkers: 'accept'` accepts a shared worker unwatched instead of refusing the page (both the default in hold mode; `'observe'` attaches and watches it where the browser allows). Exported: `CardRequestHold`, `HeldCardRequest`, `HoldOptions`, `HoldReleaseInput`, `HoldReleaseOutcome`, `HoldState`, `HoldEndedError`, `HoldAnsweredError`, `BOLT_HOLD_RULE`, `placeholderHoldRule`, `matchHoldOnlyRule`, `HOLD_DEADLINES_MS`.
46
+
47
+ - Hold a repeat confirm for the person's decision with `buy.confirm({ ..., wait: true })`. On a browser cart already waiting for the person's approval, the API holds the confirm up to 25 seconds and answers the moment they decide (`approved_not_charged` in sandbox, `order_placed` live, or `declined`), so your agent no longer polls. The first confirm never holds: it answers with the `approval_url` at once. Without `wait`, a confirm behaves as before. Needs an API release that reads `wait`; an older API ignores it and answers at once.
48
+
49
+ - Read `approved_not_charged` as a turn's status. A sandbox browser checkout the person approved now answers `status: 'approved_not_charged'` instead of `declined`: the card was never sent to the store, so nothing was ordered or charged, and `decline_code` still reads `sandbox_mode`. Earlier releases read the new status as `needs_input`. Every other sandbox answer, such as a sandbox order through a store integration, still answers `declined` with `sandbox_mode`. TypeScript: `BuyStatus` gains `approved_not_charged`; code that switches exhaustively over it needs the new case.
50
+
51
+ - Approve on Agentcard's page when a client names no approval posture. A client built without `voice`, or with `voice` and no `approval`, now runs every `voice.*` verb as `approval: 'sheet'` (the person approves on Agentcard's page) instead of throwing `missing_approval_posture`, so a checkout client that pauses a card request and asks for approval works without extra options. `approval: 'in_app'` is unchanged, and a value that is neither `'sheet'` nor `'in_app'` still throws `missing_approval_posture` at construction. A behaviour change for clients that relied on the error: release it as a minor version.
52
+
53
+ - Continue 2C2P's hosted payment page (`pgw-ui.2c2p.com`, builds 4.1 to 4.5) with the card field the cardholder's device writes, for Agentcard's proof company only (dark launch). 2C2P's page encrypts the card into one field, `payment.data.securePayToken`, and posts it to `pgwv4.2c2p.com/payment/<build>/Payment` (`sandbox-pgwv4.2c2p.com` for test payments). This build declares `twoc2p_secure_pay` on every sync, and the API serves the 2C2P entry only to Agentcard's proof company, whatever its processor lists name, so every other company's 2C2P payments go through untouched, as for an unsupported processor. The Payment names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total 2C2P's page shows. Before it creates anything, the API reads the merchant and the total from 2C2P itself and refuses another total (`amount_mismatch`), a saved card, an instalment plan, a currency conversion or a points redemption (`invalid_twoc2p_request`), and a live merchant Agentcard has not reviewed (`unsupported_checkout`). After the approval the SDK writes the device's field over the one your agent's page wrote, in that Payment alone, moves no other byte, and continues the page's own request; 3-D Secure then runs in the agent's browser, where a challenge needs a person at that browser. A request that is not byte for byte the one the cardholder approved is refused, and the SDK retires that approval as `merchant_never_retried`. The API serves the device's field only in the 10 minutes after the approval and then deletes it. Your events and error messages show nothing after the page's build folder on 2C2P's page hosts: `https://pgw-ui.2c2p.com/payment/4.1/{token}`. A 2C2P payment also needs the matching API and Vault releases.
54
+
5
55
  - Answer a Datatrans form's second card post from a token answer that carries no `cardInfo`, or a `cardInfo` with the `accountType` and `binLength` fields Datatrans documents. Datatrans's card frame reads both answers as a success, and the API records them once it runs this release's checkout rules. An earlier SDK version still hands the first answer to the page, because it passes on whatever the API stored, but it sends the page's next post of the same form to the cardholder as a new approval.
6
56
 
7
57
  - Finish a Worldpay eProtect checkout that your code resumes itself. With `attachToCdp(…, { pauseOnly: true })`, you create the authorization, read it, and call `controller.resume(read)`. The read carries the card registration as Agentcard stored it, bare JSON, and earlier releases handed exactly that to the merchant's paused registration script. Worldpay's script reads its answer only through the callback the request named, so it never received the registration: the page waited until the merchant's timeout, and the checkout read `outcome_unknown` with reason `merchant_request_aborted` after the cardholder had approved. The SDK now answers the resumed registration as it answers one it created itself, with the registration inside the request's own callback under Worldpay's script type. The SDK never hands the page a read that is not this request's registration, such as one already inside a callback or another request's answer: `resume` rejects with `PaymentOutcomeUnknownError`, reason `eprotect_registration_unconfirmed`, and the paused request fails. A checkout whose authorization the SDK creates was already answered this way and does not change. No API or Vault release is needed.
@@ -24,6 +74,7 @@
24
74
 
25
75
  - Pay a Paddle checkout whose card method names TokenEx, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it has confirmed that a card encrypted for one Paddle checkout cannot pay another. On such a checkout, Paddle's frame RSA-encrypts the card number and the CVV in the page itself, each on its own, under the account's TokenEx key, and posts them to `https://checkout-service.paddle.com/<checkout>/pay` (`https://sandbox-checkout-service.paddle.com/<checkout>/pay` in test) with the placeholder's first six and last four digits, expiry and brand. The SDK now pauses that `/pay`, and once the cardholder approves, the cardholder's device encrypts the real card under the key the SDK pins for that checkout's environment and the SDK writes the two ciphertexts and those five facts over the placeholder's, changing nothing else in the request. The full card number never reaches the agent's page. The sync now also asks for `capabilities=paddle_tokenex_pay`. The ciphertext goes only into the `/pay` the approval was created from, byte for byte, because the account's TokenEx key would decrypt it in any later `/pay` of that seller. A `/pay` that names no TokenEx card (PayPal, a wallet, a saved card) carries none of the cardholder's cards and continues untouched, and the SDK refuses a `/pay` that is not the frame's shape with `paddle_tokenex_pay_unsupported` before anything is sent. No live payment has gone through this route yet. A payment also needs the matching API and Vault releases.
26
76
  - Pay a Lyra payment form (PayZen, Systempay or Sogecommerce) for a company Agentcard turned Lyra on for (dark launch): the SDK declares `lyra_krypton_payment`, pauses the form's card payment and hands the form Lyra's answer with the payment id its 3-D Secure step needs.
77
+
27
78
  - Keep a Lyra payment's session id out of your events and error messages. Lyra's Krypton payment form (PayZen, Systempay, Sogecommerce, Lyra Collect) sends its payment to `/api-payment/V4/Charge/Public/ProcessPayment/2;jsessionid=<id>` on the brand's API host, and that id, with the form token the page holds, continues the payment on Lyra's side. `redactUrl`, every event and every error message show it as `{session}`, as in `https://api.systempay.fr/api-payment/V4/Charge/Public/ProcessPayment/2;jsessionid={session}`. Lyra stays dark for every company Agentcard has not turned it on for, and no API or Vault release is needed.
28
79
 
29
80
  - Stop PayPal's Card Fields and PayPal's guest card form from sending the card your agent typed to PayPal. A checkout that loads PayPal's JS SDK with `components=card-fields`, such as a WooCommerce PayPal Payments store's "Debit & Credit Cards" option, takes the card in frames on `www.paypal.com/smart/card-field` and sends it to `https://www.paypal.com/v2/checkout/orders/{id}/confirm-payment-source`; PayPal's v6 SDK sends the same request to `api-m.paypal.com`, and PayPal's sandbox uses `www.sandbox.paypal.com` and `api-m.sandbox.paypal.com`. PayPal's black "Debit or Credit Card" button takes the card on PayPal's own guest form, which sends it to `https://www.paypal.com/graphql?OnboardGuestMutation`, and Card Fields saves a card with no purchase through `graphql?UpdateVaultSetupToken`. No Agentcard payment took any of these over, and earlier releases let them through, so PayPal received the dummy card and declined it. Both adapters now fail each of them in the browser for every company, and the controller reads `unsupported` with reason `unsupported_paypal_card_fields`, or `unsupported_paypal_guest_checkout` for the guest form, and holds: to pay another way on the same page, attach again. A request with no card in it, such as a PayPal wallet confirm or the buttons' other GraphQL calls, 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 GraphQL post it cannot read is failed the same way only when its URL names one of the two card operations. A guest form that PayPal opens in a popup window runs in another page, which the SDK covers only when you attach it to that page as well. Events and error messages now name a PayPal order as `{order}`, as in `https://www.paypal.com/v2/checkout/orders/{order}/confirm-payment-source`, on every PayPal host, legacy Hosted Fields' `cors.api.paypal.com` included: an order id opens PayPal's own checkout page for that order to anyone who holds it. TypeScript: the controller's `unsupported()` reasons gain `unsupported_paypal_card_fields` and `unsupported_paypal_guest_checkout`; code that switches exhaustively over them needs the new cases.
@@ -32,6 +83,8 @@
32
83
 
33
84
  - Pay a store on Paysafe Checkout's current build, 1.16.1. The loader Paysafe tells merchants to include, `checkout/v1/latest`, now runs version 1.16.1, and that build sends the card to `api.paysafe.com/checkout/api/v1/tokenize` (`api.test.paysafe.com` in its test environment), where 1.8.0 sent it to `hosted.paysafe.com`. Earlier releases did not recognize that request, so it was never paused and the agent's placeholder card reached Paysafe. The SDK now pauses it and keeps the three headers the build sends beside its credential: `X-Internal-Correlation-Id`, `X-App-Version` and `X-TransactionSource`. Paysafe.js latest sends the same three, and its replay keeps them too. `prepare({ psp: 'paysafe' })` binds a request only to a Checkout build Agentcard read, by the version the request names: 1.8.0 on `hosted.paysafe.com`, or 1.16.1 on `api.paysafe.com`, each with its own headers. A request from any other build stops with `checkout_changed`, and nothing is sent. An SDK that syncs its registry pauses the 1.16.1 request once the API release deploys; a prepared 1.16.1 checkout needs this release. Both need a Vault release after 0.6.2.
34
85
 
86
+ - Read an Authorize.Net refusal as a refusal. Authorize.Net's Accept.js endpoint answers HTTP 200 whether or not it took the card and names the outcome in the answer's `resultCode`, and the cardholder's device recorded an `Error` answer as approved: `authorize()` resolved and the paused request continued with Authorize.Net's refusal in its body. The device now records it as Authorize.Net's refusal, so `authorize()` throws `ProcessorRefusedError` with Authorize.Net's code, such as `E00007`, and the authorization reads `declined` with reason `processor_refused`. Only an answer that carries Accept.js's nonce under `resultCode` `Ok` is approved; any other answer leaves the outcome unknown. No SDK upgrade is needed: the device changes with the next Vault release, and the API judges the answer again at approve once it deploys.
87
+
35
88
  - Keep a Gravy checkout session out of your events and error messages. Gravy's Secure Fields send the card to `/checkout/sessions/<session id>/fields` on the merchant's Gravy host with no auth header, so the session id is the only credential that request needs: anyone who holds it can write card fields into the session until it expires. Earlier releases named the full URL in the `card_request_paused` event and in the error `authorize()` throws for a request in the wrong method, so the session id reached your `onEvent` handler and every log that recorded the event or the error. `redactUrl`, every event and every error message now show it as `{session}`, as in `https://api.<merchant>.gr4vy.app/checkout/sessions/{session}/fields`. No API or Vault release is needed.
36
89
 
37
90
  - Pay FlixBus's checkout on Payrails web SDK 6.7.0. FlixBus's page now loads web SDK 6.7.0 in place of 5.50.0, and 6.7.0's Pay now button sends the card-form payment without the `enrollInstrumentToNetworkOffers` member that 5.50.0 sent as `false`. 0.23.11 required that member and aborted every such payment as `unsupported_payrails_card_payment` before creating anything, so no FlixBus payment reached an approval. A payment that names no `enrollInstrumentToNetworkOffers` now pauses as 5.50.0's does: Payrails reads a missing flag as `false`, and 6.7.0 sends no other member in its place. A payment that sets the flag `true` or keeps the card (`storeInstrument: true`, or `meta.order.processingType` `CardOnFile` or `UnscheduledCardOnFile`) still aborts as `payrails_card_on_file_consent_required`, and the flag in another case, in another place or as anything but a boolean aborts as `unsupported_payrails_card_payment`. A payment also needs the matching API and Vault releases.
@@ -127,6 +180,8 @@
127
180
 
128
181
  - 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.
129
182
 
183
+ - Read the booking D-EDGE's payment hub holds for a card post from D-EDGE's card frame, where HUMA Kotor Bay and other D-EDGE hotels take the card. The adapters record the hub's own `POST /load/<session>` answer, read-only, and send it with the create, cut to what the card pays now, the room's total and the part of it left for the stay, the rate's deposit and scheduled-payment marks and the booking's status. A deposit other than your integer `amount` throws `AmountMismatchError` with `amountSource: 'merchant_total'`. A rate that only guarantees the booking with the card, a charge on a later date, or part of the room left to pay at the stay throws `CardEndpointRefusedError` with reason `facts_refused`, because the hotel would charge the card after a single-use card may have closed; a stay tax the hotel collects at the desk is not part of the room. A voucher, points or a bank chosen in the hub after the booking loaded throws it with reason `facts_unavailable`. D-EDGE stays in `observe`, so its card post still ends on the stopped page with reason `not_enabled`. The API answers a D-EDGE create that carries no hub record with `409 merchant_facts_required`, and one at a rate the hotel charges later with `409 merchant_facts_refused` and reason `deferred`, before it checks whether D-EDGE takes payments, so an earlier SDK cannot pay D-EDGE once Agentcard enables it. A matching API release is required.
184
+
130
185
  - 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.
131
186
 
132
187
  - 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`.
@@ -151,7 +206,9 @@
151
206
 
152
207
  - Pay a Wix checkout whose card goes to Wix's own tokenizer, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it checks that the site instance Wix's card frame sends belongs to the reviewed store. Every Wix checkout mounts Wix Cashier's card frame, and for Wix Payments and classic Stripe on Wix that frame posts the card to `https://cashier-pci.wixapps.net/_api/payment-gateway-web/tokenize/v2/tokens`. Wix's payment widget fails the payment 30 seconds after it asks the frame for a token, too soon for an approval after Pay, so the SDK pays a Wix checkout only through `prepare({ psp: 'wix_payments', environment: 'production' })`, at a store Agentcard reviewed (ParaHouse Magazine, Psalms & Potions and Alexandra Hagel today), with `amount` and `currency`, the total the cardholder approves: the request names none. Wix has no test environment, so a test client's `prepare()` fails with `live_host_on_sandbox_row`. The sync now also asks for `capabilities=wix_payments_tokenize`. Once the cardholder approves, the frame's request pauses, the cardholder's device sends the card through the checkout relay, and the frame receives Wix's token. The frame sends its card request once more under a fresh site instance after Wix answers 401 or 403: the SDK refuses that second request in the browser and never answers it from the first approval. The SDK fails a paused request the cardholder's device has not sent 20 seconds after the pause, of the 30 seconds Wix waits, with reason `wix_payments_send_window_closed`, as it fails a prepared Secure Acceptance token create, and the device sends nothing past its own earlier boundary.
153
208
 
154
- - Keep a GoPay card form's token out of your events and error messages. GoPay's card frame sends the card to its own URL, `https://secure.gopay.com/gp-card-comm/h/<token>?lang=..&ver=..&SG=<signature>`, and that token, with the signature in the query, opens that payment's card form to anyone who holds it. `redactUrl`, every event and every error message now show it as `{token}` on GoPay's card frame hosts, as in `https://secure.gopay.com/gp-card-comm/h/{token}`, and drop the query as before. The SDK does not pause a GoPay payment yet. No API or Vault release is needed.
209
+ - Pay a HiPay checkout whose card goes to HiPay's own vault, for a company Agentcard turned HiPay on for (dark launch). HiPay's hosted fields, which HiPay's WooCommerce, PrestaShop and Magento modules use by default, send the card from a hidden frame on `libs.hipay.com` to `https://secure2-vault.hipay-tpp.com/rest/v2/token` under the merchant's public credential, and the merchant's page gives that request 20 seconds after Pay, too soon for an approval after Pay. So the SDK pays a HiPay checkout only through `prepare({ psp: 'hipay', environment: 'production' })` (`'sandbox'` for HiPay's stage vault, with a test client), with `amount` and `currency` above zero, the total the cardholder approves: the request names none. On the live vault only a merchant Agentcard reviewed is paid, at its own checkout page and under its own public credential; none is reviewed yet, so a live `prepare()` fails with `unsupported_checkout`. The sync now also asks for `capabilities=hipay_vault_token`. The frame posts the card number alone while it is typed, and posts `/rest/v2/token/update` once it holds a token: the SDK fails both in the browser, and any post to the token URL that carries no security code with them, with a `blocked` event (`hipay_number_only`, `hipay_update_refused`) and spends no ready preparation, so the frame keeps the number and Pay sends one complete request. Once the cardholder approves, that request pauses, the cardholder's device sends the card to HiPay itself, and the frame receives HiPay's token: the SDK reads the approval every 500 ms for the 20 seconds the page waits. The SDK fails a paused request the device has not sent 12 seconds after the pause, of the 20 seconds the page waits, with reason `hipay_send_window_closed`. A payment also needs the matching API and Vault releases, and a company listed for HiPay.
210
+
211
+ - Keep a GoPay card form's token out of your events and error messages. GoPay's card frame sends the card to its own URL, `https://secure.gopay.com/gp-card-comm/h/<token>?lang=..&ver=..&SG=<signature>`, and that token, with the signature in the query, opens that payment's card form to anyone who holds it. `redactUrl`, every event and every error message now show it as `{token}` on GoPay's card frame hosts, as in `https://secure.gopay.com/gp-card-comm/h/{token}`, and drop the query as before. No API or Vault release is needed.
155
212
 
156
213
  - Pay PayMongo's hosted checkout through the checkout relay, for a company Agentcard turns PayMongo on for (dark launch). Every PayMongo payment link, Storefront page and Checkout Session opens one page, `https://checkout.paymongo.com/<session>`, whose Pay button posts the card to that page's own URL as a Next.js Server Action. This build declares `paymongo_checkout_pay` on every sync, and the API serves the PayMongo entry only to a company Agentcard turned PayMongo on for, so every other company's PayMongo payments go through untouched, as for an unsupported processor. For an enabled company the SDK pauses only the checkout page's own card attach. The attach names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total PayMongo's checkout shows, in Philippine pesos. The API reads the checkout session from PayMongo and refuses a total that is not the session's to the centavo (`amount_mismatch`), a test-mode session on live credentials or the reverse (`paymongo_environment_mismatch`), and a session that has expired, is already being paid, takes no cards or adds PayMongo's fee on top (`paymongo_checkout_unpayable`), and it names PayMongo's merchant, whatever `merchant` you send. The cardholder's device sends the card through the checkout relay, and the page gets PayMongo's own answer: the paid payment, or the step toward PayMongo's 3-D Secure page, which the page then runs in the agent's browser, where a challenge needs a person at that browser. A PayMongo payment also needs the matching API, Vault and checkout relay releases.
157
214
 
@@ -159,10 +216,12 @@
159
216
 
160
217
  - Keep a Przelewy24 transaction's token out of your events and error messages. Przelewy24's pay wall names its transaction by one token in every path (`/trnRequest/<token>`, its method links, `/card/p24form/<token>`, `/trnResult/<token>`), with no auth header, so the token is all a browser needs to open that transaction's pay wall, with its merchant, its amount and its card form. `redactUrl`, every event and every error message now show it as `{token}` on Przelewy24's hosts, on every route, as in `https://secure.przelewy24.pl/trnRequest/{token}`. The SDK does not pause a Przelewy24 payment yet. No API or Vault release is needed.
161
218
 
162
- - Keep a Revolut order's token out of your events and error messages. Revolut's card field and card popup send the card to `https://merchant.revolut.com/api/public/orders/<order token>/payments` (`sandbox-merchant.revolut.com` in test) with no auth header that binds it, so the order token is the only credential that request needs: anyone who holds it can read the order, its amount and its merchant, and pay it. The frame then reads the payment at `/api/public/payments/<payment token>`, and Revolut's hosted checkout page names the order token in its own paths. `redactUrl`, every event and every error message now show each of those tokens as `{token}` on those hosts, as in `https://merchant.revolut.com/api/public/orders/{token}/payments`. The SDK does not pause a Revolut payment yet. No API or Vault release is needed.
219
+ - Keep a Revolut order's token out of your events and error messages. Revolut's card field and card popup send the card to `https://merchant.revolut.com/api/public/orders/<order token>/payments` (`sandbox-merchant.revolut.com` in test) with no auth header that binds it, so the order token is the only credential that request needs: anyone who holds it can read the order, its amount and its merchant, and pay it. The frame then reads the payment at `/api/public/payments/<payment token>`, and Revolut's hosted checkout page names the order token in its own paths. `redactUrl`, every event and every error message now show each of those tokens as `{token}` on those hosts, as in `https://merchant.revolut.com/api/public/orders/{token}/payments`. No API or Vault release is needed.
163
220
 
164
221
  - Keep a Paytrail payment's id and a Payment Highway card page's session out of your events and error messages. Paytrail's card method posts to `/payments/<transaction>/click-to-pay/loading-and-redirect` on `services.paytrail.com`, and that id opens Paytrail's own payment wall for the payment (`pay.paytrail.com/pay/<transaction>`), its amount, its merchant and every method's form, to anyone who holds it. Payment Highway, Paytrail's card platform, names its card page's session in the same way (`/form/<session>/pay_with_click_to_pay` on `v1.api.paymenthighway.io`). `redactUrl`, every event and every error message now show them as `{transaction}` and `{session}`, as in `https://services.paytrail.com/payments/{transaction}/click-to-pay/loading-and-redirect`. The SDK does not pause a Paytrail payment yet. No API or Vault release is needed.
165
222
 
223
+ - Pause QuickPay's hosted payment window (where most Danish and Nordic webshops send the buyer) for the cardholder's device. The sync now also asks for `capabilities=quickpay_hosted_payment_page`, and the API serves QuickPay's recognizer only for a company Agentcard turned QuickPay on for. The SDK pauses the merchant's signed entry form to `https://payment.quickpay.net/`, 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 QuickPay, the window's card post, its fee lookup, its wallet token post and its 3-D Secure step, carries the card your agent typed or follows it, and so does the window's lookup of the card's first digits: the SDK fails each one and reads `unsupported` with reason `processor_card_page`. Your events and error messages name the window's session, the 32-hex first segment of its paths, as `{session}`. Matching API and Vault releases are required. QuickPay stays off for every company Agentcard has not turned it on for.
224
+
166
225
  ## 0.23.0
167
226
 
168
227
  ### Upgrading from 0.22.0
package/PREFLIGHT.md CHANGED
@@ -191,6 +191,8 @@ Juspay (`juspay`) is not in this table. Juspay launches dark: the direct SDK pay
191
191
 
192
192
  Datatrans Secure Fields (`datatrans`, PCI Proxy) has reviewed discovery rules for its `secure-fields-2.0.0` script and its card iframe on `pay.datatrans.com` and `pay.sandbox.datatrans.com`, 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, its assets are not evidence, and a Datatrans checkout stays `unknown`, even when your SDK build can pay it. A page that loads Secure Fields beside another processor's SDK reads exactly as it did before.
193
193
 
194
+ The TokenEx hosted iframe v3 (`tokenex_iframe`, JetBlue's card form) has reviewed discovery rules for its `Iframe-v3` parent library and its `/iframe/v3` card iframe on `htp.tokenex.com` and `test-htp.tokenex.com`, and waits the same way: it launches dark, so its assets are not evidence and a TokenEx checkout stays `unknown`, even when your SDK build can pay it for an org Agentcard has enabled. Read the [TokenEx iframe section of the README](./README.md#pay-a-tokenex-iframe-checkout-dark-launch) for what the SDK covers.
195
+
194
196
  ACI Worldwide's COPYandPAY widget (`aci`) is identified from its widget script and its card iframe on `eu-prod.oppwa.com`, `oppwa.com`, `eu-test.oppwa.com` and `test.oppwa.com`, but it is not in this table. It launches dark: the direct SDK pays it only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled, so the bundled catalog leaves it out and an ACI checkout stays `unknown`, even when your SDK build can pay it. Read the [ACI section of the README](./README.md#pay-an-aci-copyandpay-checkout) for what the SDK covers.
195
197
 
196
198
  Primer (`primer`) is identified from its Web SDK v2 script `PrimerCoreSDK.min.js` and its `hosted-input.html` and `api-controller.html` card frames on `sdk.primer.io`, 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 Primer checkout stays `unknown`, even when your SDK build can pay it. Primer Checkout (`@primer-io/primer-js`), whose hosted api-controller lives under `/web/hosted-pages/`, names its processor and returns `unknown` with `processor_variant_unverified`. Read the [Primer section of the README](./README.md#pay-a-primer-checkout) for what the SDK covers.
@@ -205,6 +207,8 @@ Shiji STS (`shiji_sts`), the card leg of MyCheck Wallet V3 at La Quinta by Wyndh
205
207
 
206
208
  Wix Payments (`wix_payments`) has no discovery rule in this table. Wix Cashier's card frame on `cashier-pci.wixapps.net` is the same for every provider a Wix site connects, and for Square and Wix's Stripe Gateway it sends the card to the processor's own SDK rather than to Wix's tokenizer, so the frame on a page is no evidence of this lane, and a Wix checkout stays `unknown`. The direct SDK pauses the card request the frame sends to Wix's tokenizer only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled. Read the Wix row of the [README](./README.md) processor table.
207
209
 
210
+ HiPay (`hipay`) has no discovery rule in this table. HiPay's JavaScript SDK (`libs.hipay.com/js/sdkjs.js`) loads on every HiPay checkout, hosted fields or HiPay's own hosted page, so the script on a page is no evidence that a payment can be approved, and a HiPay checkout stays `unknown`. The direct SDK pauses the token create HiPay's hosted fields send only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled. Read the HiPay row of the [README](./README.md) processor table.
211
+
208
212
  Gravy Secure Fields (`gravy`) is identified from the loader its merchants load, `/secure-fields/latest/secure-fields.js` on `cdn.<merchant>.gr4vy.app` (or `cdn.sandbox.<merchant>.gr4vy.app`), but it is not in this table. It launches dark: the direct SDK pauses Gravy's card request, a `PUT` from Gravy's controller frame, only after `syncRegistry()` and only for an org Agentcard has enabled, so the bundled catalog leaves it out and a Gravy checkout stays `unknown`.
209
213
 
210
214
  Paddle (`paddle_vgs`) has no discovery rule in this table. Paddle's checkout frame on `buy.paddle.com` names the tokenizer it sends the card through only after the buyer's first step, and Paddle can name another one for each checkout, so nothing on the page before that step says which one a checkout uses, and a Paddle checkout stays `unknown`. The direct SDK pauses the card request a VGS checkout sends to `vault.paddle.com/cards` only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled. Read the Paddle row of the [README](./README.md) processor table for what the SDK covers.
package/README.md CHANGED
@@ -57,7 +57,7 @@ const bundle = await agentcard.voice.challengeForHost(purchase.id, { lang: 'es'
57
57
  const reg = await agentcard.voice.appKeys.register(key, { user_id: person.user_id }); // the key the app's frame minted; reg.credential goes back to the phone
58
58
  ```
59
59
 
60
- One choice per business, not per purchase; there is no default, and a client built without `voice` has every `voice.*` verb throw `missing_approval_posture`.
60
+ One choice per business, not per purchase. Leave it out and the client approves on Agentcard's page, as with `approval: 'sheet'`; a value that is neither `'sheet'` nor `'in_app'` throws `missing_approval_posture` at construction.
61
61
 
62
62
  ### The card that pays
63
63
 
@@ -180,6 +180,12 @@ Merchant timeouts still apply to the request itself: Square's observed tokenizat
180
180
 
181
181
  `attachToCdp(…, { pauseOnly: true, onPaused })` pauses the card request without creating the purchase and hands you `{ request, recognizer, page_origin, controller }`. Create it with `voice.authorizations.create` as on any other purchase, read the result, and `controller.resume(result)` replays the processor's answer into the page. `recognizer` names the processor that claimed the request (`stripe`), and so does the `card_request_paused` event's detail, so nothing has to re-match the URL.
182
182
 
183
+ ### Hold the card request until the person confirms the total
184
+
185
+ `attachToCdp(…, { hold: true })` pauses the first card request and holds it with nothing created. `controller.hold.whenHeld()` resolves with what was held (the processor and the endpoint, never a body). Once the person confirmed the total and the page still shows it, `controller.release({ amount, currency, pageTotal })` creates the authorization at that amount: a live client answers the page with the device's replay, a sandbox client never sends the request on and resolves `approved_not_charged`. A page total that moved, or one you could not read (`pageTotal: null`), cancels the request instead. `upTo: true` releases at a ceiling while the page total stays at or under it. `controller.hold.cancel()` fails the request. A held request is cancelled before its processor's own wait runs out (Braintree 60 s, Bolt 20 s, 30 s for the rest), and every other card request, and every one after a decision, fails at once.
186
+
187
+ Hold mode also catches Bolt's tokenizer, which no device replay can answer (held, then only cancelled), and, with `hold: { placeholderNumbers }`, any request whose body carries the placeholder card, wherever it goes. In sandbox, `release({ …, continuePlaceholder: true })` sends a store's own card post on unchanged, since it carries the documented test number only. Site service workers are bypassed (`serviceWorkers: 'bypass'`) instead of refusing the page, and a shared worker is accepted unwatched (`sharedWorkers: 'accept'`): Fetch reaches no shared worker, so nothing it sends can be approved, and the browser only ever holds the placeholder card. `sharedWorkers: 'observe'` attaches to it and ends the checkout on a card request it sends, on a browser that allows that attach (Kernel does not).
188
+
183
189
  ### Recognisers and request builders
184
190
 
185
191
  Both are registries the SDK ships and you extend at runtime, so a processor or a store Agentcard does not know yet needs no release.
@@ -255,7 +261,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
255
261
  | 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
262
  | 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
263
  | 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
- | PayPal Card Fields (PayPal JS SDK `components=card-fields`, v5 and v6) and PayPal's guest card form (the "Debit or Credit Card" button) | not supported: no Agentcard payment takes over the card these send to `www.paypal.com` or `api-m.paypal.com`. The SDK fails each card request for every company, so PayPal never receives the dummy card, and the controller reads `unsupported` with reason `unsupported_paypal_card_fields` (or `unsupported_paypal_guest_checkout` for the guest form) and holds the attachment until you attach again; a PayPal wallet payment continues untouched |
264
+ | PayPal Card Fields (PayPal JS SDK `components=card-fields`, v5 and v6) and PayPal's guest card form (the "Debit or Credit Card" button) | Card Fields: dark launch. After `vault.syncRegistry()`, and only for an org Agentcard has enabled, the SDK pauses the frames' confirm to `/v2/checkout/orders/{order}/confirm-payment-source` on `www.paypal.com` or `api-m.paypal.com`, always with the cardholder's approval, and the page gets the order's id and status; pass `amount` and `currency`, since the confirm names none. The guest card form is not supported. For every other org, and for the guest form always, the SDK fails each card request, so PayPal never receives the dummy card, and the controller reads `unsupported` with reason `unsupported_paypal_card_fields` (or `unsupported_paypal_guest_checkout` for the guest form) and holds the attachment until you attach again; a PayPal wallet payment continues untouched |
259
265
  | CyberSource Secure Acceptance Silent Order POST (the merchant's own card form posted to CyberSource; Arrow Electronics) | dark launch: prepared checkout only, for an org Agentcard enabled. After `syncRegistry()`, for such an org, the SDK pauses the token create a merchant's form posts into a hidden iframe (`/silent/embedded/token/create` on `secureacceptance.cybersource.com`, or `testsecureacceptance.cybersource.com` in CyberSource's test environment), the cardholder's device sends the card to CyberSource, and the iframe gets CyberSource's own reply page. See [Pay a CyberSource Secure Acceptance checkout](#pay-a-cybersource-secure-acceptance-checkout-dark-launch). For every other company, and for every other card post to a `/silent/` path on those hosts or on `secureacceptance.in.cybersource.com` (India), no Agentcard payment takes the card over, and the SDK stops the post so CyberSource never receives the dummy card: a form the page submits as a page load, in the page or in a frame, ends on the page that says Agentcard stopped the payment, and a fetch or an XHR fails. The controller reads `unsupported` with reason `unsupported_cybersource_secure_acceptance` and holds the attachment until you attach again; a post with no `card_number`, such as a payment with a stored `payment_token`, continues untouched. The SDK does not stop Hosted Checkout, where your agent types the card on CyberSource's own page |
260
266
  | EBANX (EBANX.js and EBANX's hosted payment page; Nuuvem Colombia) | supported for the token request (`POST /ws/token`): the SDK pauses it, and the cardholder's device sends the card there. EBANX's page then sends the card again. 3-D Secure's `/ws/threedsecureserver-generateToken`, `-authentications` and `-authentication-results` carry the card. The hosted page's confirm, `/ws/checkout-v2/confirm`, carries the card code. For a company Agentcard turned it on for, the approval's read names until when it covers those sends (`followUpsUntil`). While the Vault page stays open, the cardholder's device sends each covered one, bound to the approved request. The cardholder sees no second prompt. The page receives EBANX's answer. The confirm is covered only once 3-D Secure succeeded for the invoice it pays. It pays in one instalment and keeps no card for one-click payments. A card checkout that runs no 3-D Secure, picks instalments or saves the card fails in the browser. Every other such send fails in the browser too, for every company. EBANX never receives the dummy card or code. The page shows its own payment error. The controller reads `unsupported` with reason `unsupported_ebanx_card_resend` and holds the attachment until you attach again. After one such send fails or is refused, the SDK sends nothing more on that attachment. It also sends nothing once the checkout no longer names that approval: a final merchant result such as `completed` or `failed`, a new token request, or a new attempt after a merchant failure, and nothing while a reconciliation runs. While a send is out, `reconcile()` and `retryAfterMerchantFailure()` throw, and a new card request fails in the browser. A confirm with no card code, such as a PSE bank transfer, continues untouched |
261
267
  | 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 |
@@ -270,6 +276,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
270
276
  | 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) |
271
277
  | 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 |
272
278
  | Datatrans Secure Fields (PCI Proxy; RaiseNow donations, 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`. The one live merchant account Agentcard has reviewed is RaiseNow's shared donation account, so a RaiseNow charity's one-time donation pays and every other live Datatrans checkout is refused as `datatrans_merchant_unregistered`. 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 |
279
+ | TokenEx hosted iframe v3 (JetBlue's card form) | dark launch: the PCI card-number frame's own card post only, after `vault.syncRegistry()`, and only for an org Agentcard has enabled (see [Pay a TokenEx iframe checkout](#pay-a-tokenex-iframe-checkout-dark-launch)). Pass `amount` and `currency`. The cardholder's device sends the card to TokenEx and the frame receives TokenEx's token. No TokenEx account is reviewed yet, so the API refuses every create as `unsupported_checkout` until Agentcard lists JetBlue's |
273
280
  | 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 |
274
281
  | Pelecard (Iframe/Redirect 2.0) | dark: paused only for an org Agentcard enables for Pelecard, in test mode and live. One-time card payments on `gateway21` and `gateway20.pelecard.biz`; pass `amount`, `currency` and the page's total as `pageAmount`. A live checkout pays only with a card the cardholder stored. See [Pay a Pelecard checkout](#pay-a-pelecard-checkout) |
275
282
  | CreditGuard (Hyp PPS hosted payment page) | dark launch, prepared checkout only (see "Pay a CreditGuard checkout"): served to this build (`capabilities=creditguard_pps_process`) only for a company Agentcard enabled for it, and prepared or paid only for that company. The cardholder's device sends the card and the page reads CreditGuard's answer. 3-D Secure, OTP, installments, save-my-card and currency conversion are refused. No UAT or live capture yet |
@@ -277,11 +284,13 @@ Coverage is specific to the processor request format, merchant setup, browser tr
277
284
  | Payrails web SDK (Blacklane, FlixBus) | dark launch (mode `device_encrypt`): only for an org Agentcard has enabled, and only the card form's single-use save and its single-use payment until card-on-file consent exists; see [Pay a Payrails checkout](#pay-a-payrails-checkout) |
278
285
  | Elavon Converge (Hosted Payment Page) | dark: paused only for an org Agentcard enables for Converge, always with the cardholder's approval, never autopilot. One credit card sale in US dollars per payment page on `api.convergepay.com` or `www.convergepay.com` (`api.demo.convergepay.com` in Converge's demo environment); no amount needed. See [Pay an Elavon Converge payment page](#pay-an-elavon-converge-payment-page) |
279
286
  | Moneris Hosted Tokenization (Moneris's card frame; Raisin donation pages) | dark launch: after `vault.syncRegistry()`, and only for an org Agentcard has enabled, the SDK pauses the frame's post to `/HPPtoken/request.php` on `www3.moneris.com` or `gateway.moneris.com` (`esqa.moneris.com` for a test row) when it carries the card number, expiry and CVD, always with the cardholder's approval. A live payment needs a merchant Agentcard reviewed: the profile id the frame names must be one pinned for the checkout page's origin (Jack.org's Raisin page today), or the create answers `unsupported_checkout`. A frame that leaves the expiry or CVD to the merchant's page, or a post to any other `request.php` URL, fails in the browser with a `blocked` event, `request_without_card`. The cardholder's device sends the card through the checkout relay, and the page gets Moneris's temporary token, which the merchant charges from its own server: pass `amount` and `currency`, the total the cardholder approves. Moneris Checkout (`/chkt/`, `/chktv2/`) is not supported |
287
+ | GoDaddy Payments (Poynt's card frame on a GoDaddy Online Store checkout) | dark launch: after `vault.syncRegistry()`, and only for an org Agentcard has enabled, the SDK pauses the frame's post to `/v1/commerce/stores/<store id>/payment-tokens/generate-nonce` on `services.poynt.net`, always with the cardholder's approval. A live payment needs a store Agentcard reviewed: the store id must be one pinned for the checkout page's origin, or the create answers `unsupported_checkout`; a test key is refused, since Poynt has no test host. A form that asks Poynt to keep the card fails in the browser with a `blocked` event. The cardholder's device sends the card through the checkout relay, and the page gets Poynt's nonce, which the store's server charges: pass `amount` and `currency`, the total the cardholder approves. The store's pay button runs an invisible reCAPTCHA, so a person presses it |
280
288
  | WSPay Components (WSPay's build of Monri Components) | dark launch: the card frame's confirm of a new card, after `vault.syncRegistry()`, with `amount` and `currency`, and only for an org Agentcard has enabled; for any other org the SDK leaves WSPay's requests alone. The cardholder's device sends the card to WSPay and the frame receives WSPay's answer. A confirm that asks WSPay to keep the card on file, as Valamar's does on every booking, is paid only as a hotel's EUR 1.00 card guarantee with a card the cardholder stored. Validated in local Chromium against a model of the card frame; no WSPay sandbox run or live merchant purchase yet. See [Pay a WSPay Components checkout](#pay-a-wspay-components-checkout) |
281
289
  | Shiji STS (MyCheck Wallet V3; Wyndham's booking pages) | dark launch, mode `cse` under a pinned key, and only for an org Agentcard has enabled. The card token a hotel booking mints guarantees the stay, so the cardholder approves with a card they stored, under a line that says the hotel can keep it on file. See [Shiji STS](#shiji-sts-mycheck-wallet-v3) |
282
290
  | Wix checkout (Wix Cashier's card frame; Wix Payments and classic Stripe on Wix) | dark launch: only in Agentcard's own account for now, after `vault.syncRegistry()`, and only through `prepare({ psp: 'wix_payments', environment: 'production' })` at a store Agentcard reviewed, with `amount` and `currency`. The frame's post to `cashier-pci.wixapps.net/_api/payment-gateway-web/tokenize/v2/tokens` pauses, the cardholder's device sends the card through the checkout relay, and the frame receives Wix's token. Wix has no test environment. The SDK refuses the frame's second card request after a 401 or 403 in the browser, and fails a request still unsent 20 seconds after the pause, before the card leaves (`wix_payments_send_window_closed`) |
291
+ | HiPay Enterprise (HiPay's hosted fields; France, Portugal, Italy, Spain) | dark launch: after `vault.syncRegistry()`, only for an org Agentcard has enabled, and only through `prepare({ psp: 'hipay', environment: 'production' })` (`'sandbox'` for HiPay's stage vault) at a merchant Agentcard reviewed, with `amount` and `currency` above zero. The hosted fields' post to `secure2-vault.hipay-tpp.com/rest/v2/token` pauses, the cardholder's device sends the card to HiPay directly, and the frame receives HiPay's token. The frame's number-only post and its `/update` fail in the browser (`hipay_number_only`, `hipay_update_refused`), and a request still unsent 12 seconds after the pause fails before the card leaves (`hipay_send_window_closed`). Agentcard has reviewed one merchant so far, HappyBulle: a live checkout prepares only at `https://www.happybulle.com`, with `merchant: 'HappyBulle'`; `prepare()` at any other page fails with `unsupported_checkout`, and so does the create for a card request under any public username but HappyBulle's own |
283
292
  | Amadeus Checkout (Air France, KLM) | dark: only for an org Agentcard enabled, and only through `prepare({ psp: 'amadeus_checkout', cardId })`. The Checkout Web SDK's card add pauses; every other action on its URL continues. Air France and KLM keep the expiry in their own form, so the agent types the stored card's real expiry there and the checkout pays only with that card. Every Data Collection Page store is refused as `amadeus_dcp_field_config_unreviewed`. No live purchase yet. See "Prepare an Amadeus Checkout card step" |
284
- | Paddle checkout (Paddle's card vault; Rectangle Pro) | dark launch: only in Agentcard's own account for now, after `vault.syncRegistry()`, and only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the paying card; without one, `prepare()` fails with `card_required`. On a Paddle checkout whose card method names VGS, the frame's post to `vault.paddle.com/cards` pauses, the cardholder's device sends the card itself, and the frame receives the stored card's id; its second request after its 10-second timeout gets the same answer. Type `placeholderCard()`'s facts for that card into Paddle's form, and pass `amount` and `currency`, the total the cardholder approves. Only Visa, Mastercard and American Express cards are paid (`paddle_vgs_card_unsupported` otherwise), and a typed expiry or placeholder that is not the card's fails the create with `paddle_vgs_card_mismatch`. On the live vault only a reviewed seller at its reviewed page is paid (Rectangle Pro at `https://rectangleapp.com`), and the approval says that seller can keep the card on file. Paddle's `/pay` that follows names the approved card's first six and last four digits, expiry and brand: the SDK writes them over the ones Paddle's form read from the placeholder, and aborts a `/pay` that names another stored card or comes more than a minute later. A Paddle checkout that sends the card through TokenEx or Spreedly is not supported, and no live payment has gone through this route yet |
293
+ | Paddle checkout (Paddle's card vault or TokenEx; any Paddle seller) | dark launch: after `vault.syncRegistry()`, and only for an org Agentcard has enabled. On a Paddle checkout whose card method names VGS, pay only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the paying card; without one, `prepare()` fails with `card_required`. The frame's post to `vault.paddle.com/cards` pauses, the cardholder's device sends the card itself, and the frame receives the stored card's id; its second request after its 10-second timeout gets the same answer. Type `placeholderCard()`'s facts for that card into Paddle's form, and pass `amount` and `currency`, the total the cardholder approves. Only Visa, Mastercard and American Express cards are paid (`paddle_vgs_card_unsupported` otherwise), and a typed expiry or placeholder that is not the card's fails the create with `paddle_vgs_card_mismatch`. On the live vault, Agentcard pays a checkout at any Paddle seller's own https page; from any other page, such as `http://localhost:4000`, `prepare()` fails with `unsupported_checkout`. Paddle's vault keeps the card, so Paddle can charge it again without another approval, at a subscription's renewal for example. Paddle's `/pay` that follows names the approved card's first six and last four digits, expiry and brand: the SDK writes them over the ones Paddle's form read from the placeholder, and aborts a `/pay` that names another stored card or comes more than a minute later. On a Paddle checkout whose card method names TokenEx, call no `prepare()`: the SDK pauses Paddle's `/pay`, and once the cardholder approves it, writes the card the cardholder's device encrypted over the placeholder's. With a `paddle_vgs` preparation on the attachment, the SDK refuses that `/pay` with `checkout_changed`. A Paddle checkout that sends the card through Spreedly is not supported |
285
294
 
286
295
  The recognizer list, and the status of each reviewed Adyen merchant profile on
287
296
  your client, are fetched from the API at runtime (`agentcard.checkout.syncRegistry()`),
@@ -289,13 +298,14 @@ so new processors work without you shipping a release. `attachToCdp` derives the
289
298
  `Fetch.enable` url patterns from that same list rather than a constant, which is
290
299
  why the sync call belongs before the attach. `agentcard.checkout.cardUrlPatterns()` returns
291
300
  those patterns if you arm a CDP connection yourself. Call
292
- `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,cybersource_sa_token_create,gravy_session_fields_put,moneris_hosted_tokenization,paddle_vgs_cards,redsys_hosted_payment_page,paymongo_checkout_pay`
301
+ `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,cybersource_sa_token_create,gravy_session_fields_put,moneris_hosted_tokenization,paddle_vgs_cards,paddle_tokenex_pay,redsys_hosted_payment_page,lyra_krypton_payment,payfast_card_pay,wix_payments_tokenize,paymongo_checkout_pay,pay_nl_card_token,hipay_vault_token,twoc2p_secure_pay,quickpay_hosted_payment_page,revolut_card_field,paypal_card_fields,gopay_card_form,comgate_hosted_payment_page,przelewy24_hosted_payment_page,makecommerce_card_frame,tokenex_iframe_tokenize,transbank_webpay_hosted_payment_page,sumup_checkout_process`
293
302
  for the list this build syncs. The API serves Fiserv's card capture only to a
294
303
  build that lists `fiserv_card_capture`, and only for a company Agentcard turned
295
304
  Fiserv on for. An older SDK never pauses a Fiserv request it could not finish,
296
305
  and no SDK pauses one for a company Fiserv is off for. The API serves Juspay's
297
306
  card flow the same way, behind `juspay_txns.v1`, Datatrans's Secure Fields
298
- card post behind `datatrans_secure_fields_tokenize`, the Authorize.Net DPM
307
+ card post behind `datatrans_secure_fields_tokenize`, the TokenEx iframe's card
308
+ post behind `tokenex_iframe_tokenize`, the Authorize.Net DPM
299
309
  or SIM form behind `authorize_net_dpm_form`, Pelecard's card submission
300
310
  behind `pelecard_transaction`, ACI's split card behind `aci_split_card.v1`,
301
311
  CreditGuard's hosted page submit behind `creditguard_pps_process`,
@@ -313,10 +323,58 @@ NMI never sees the card your agent typed. Redsys's hosted payment page
313
323
  (`redsys_hosted_payment_page`) is served the same way: this build pauses the
314
324
  merchant's signed entry form to `sis.redsys.es/sis/realizarPago` for the
315
325
  cardholder's device and fails every other post your browser makes to Redsys.
326
+ Transbank's Webpay Plus (`transbank_webpay_hosted_payment_page`) is served the
327
+ same way: this build pauses the merchant's redirect to
328
+ `webpay3g.transbank.cl/webpayserver/initTransaction?token_ws=<token>` in the
329
+ page or a frame of it, and with `attachToCdp` also in a window the page opened,
330
+ for the cardholder's device, and fails every request but a GET your browser
331
+ makes to Webpay, so the token is never spent in your browser and Webpay never
332
+ sees the card your agent typed. `attachToPlaywright` covers the page it is
333
+ attached to and that page's frames, and pauses the entry when one of them
334
+ navigates to it directly or by script. Playwright's `page.route` never sees the
335
+ hop of an HTTP redirect, so a merchant whose server answers its checkout with a
336
+ redirect to Webpay needs `attachToCdp`: with `attachToPlaywright`, Webpay's
337
+ page loads unpaused and the SDK fails its token submit as
338
+ `processor_card_page`, so nothing is paid and no approval starts. A checkout
339
+ that opens Webpay in a popup needs that popup attached on its own, and a popup's
340
+ first navigation needs the integration
341
+ [Browser integration and merchant outcomes](#browser-integration-and-merchant-outcomes)
342
+ describes.
316
343
  PayMongo's hosted checkout (`paymongo_checkout_pay`) is served on the same terms:
317
344
  this build pauses the checkout page's own card attach on `checkout.paymongo.com`,
318
345
  the cardholder's device sends it through the checkout relay, and the page gets
319
346
  PayMongo's answer.
347
+ Przelewy24's pay wall (`przelewy24_hosted_payment_page`) is served the same way:
348
+ this build pauses the pay wall link `secure.przelewy24.pl/trnRequest/<token>`
349
+ for the cardholder's device and fails every post a page it watches makes to the
350
+ pay wall, and its Saferpay resolver. A merchant's server often sends the buyer
351
+ to that link with a redirect or in a new window, and only `attachToCdp` pauses
352
+ a redirect and watches a window the page opens.
353
+ QuickPay's hosted payment window (`quickpay_hosted_payment_page`) is served the
354
+ same way: this build pauses the merchant's signed entry form to
355
+ `payment.quickpay.net` for the cardholder's device and fails every other post
356
+ your browser makes to QuickPay, and the window's lookup of the typed card's
357
+ first digits.
358
+ Revolut Business's card field (`revolut_card_field`) is served on the same
359
+ terms: this build pauses the frame's card payment on `merchant.revolut.com`,
360
+ the cardholder's device sends it to Revolut itself, and the frame gets
361
+ Revolut's answer.
362
+ GoPay's card form (`gopay_card_form`) is served only in Agentcard's own account
363
+ for now (dark launch), until a reviewed change binds GoPay's payee and amount
364
+ to the approval: this build pauses the card frame's own post on
365
+ `secure.gopay.com`, at a shop Agentcard reviewed, the cardholder's device sends
366
+ it through the checkout relay, and the frame gets GoPay's answer.
367
+ ComGate's payment gateway (`comgate_hosted_payment_page`) is served only to a
368
+ build that lists it, for a company Agentcard turned ComGate on for: this build
369
+ pauses the gateway link the merchant sends the buyer to,
370
+ `payN.comgate.cz/init?id=<transaction id>`, in the page, in a frame of it or in
371
+ a window it opened, for the cardholder's device, and fails every request but a
372
+ GET your browser makes to ComGate's API and card frame hosts.
373
+ SumUp's Payment Widget (`sumup_checkout_process`) is served on the same terms:
374
+ this build pauses the widget's card `PUT` to `api.sumup.com/v0.2/checkouts/<id>`,
375
+ the API takes it only for a reviewed SumUp merchant and at the `amount` the
376
+ payment page shows, the cardholder's device sends it to SumUp, and the widget
377
+ gets SumUp's answer.
320
378
  CyberSource Secure Acceptance's token
321
379
  create (`cybersource_sa_token_create`) is served on the same terms too; every
322
380
  other company keeps the stop every build applies to a card posted to Secure
@@ -565,6 +623,23 @@ mode; the difference matters if you drive `authorize()` yourself.
565
623
  reads `stopped_before_send`: the `PaymentOutcomeUnknownError` then carries
566
624
  `processorRequestStarted: false`.
567
625
 
626
+ D-EDGE's card frame, where HUMA Kotor Bay and other D-EDGE hotels take the
627
+ card, posts it as a page load of the frame, and the payment hub around the
628
+ frame loads the booking from its own host (`POST /load/<session>`). The SDK
629
+ records that answer, read-only, and sends it with the create, cut to what
630
+ the card pays now, the room's total and the part of it left for the stay,
631
+ the rate's deposit and scheduled-payment marks and the booking's status. A
632
+ deposit other than your integer `amount` is an `AmountMismatchError` against
633
+ the merchant's total. A rate that only guarantees the booking with the
634
+ card, a charge on a later date, or part of the room left to pay at the stay
635
+ is refused as `facts_refused` before any authorization: the hotel would
636
+ charge the card after a single-use card may have closed. A stay tax the
637
+ hotel collects at the desk is not part of the room, so a room paid in full
638
+ goes on. A voucher, points or a bank chosen in the hub after the booking
639
+ loaded leaves the booking unknown, and the payment is refused as
640
+ `facts_unavailable`. D-EDGE is in observe, so its card post ends on the
641
+ stopped page with reason `not_enabled` until Agentcard enables it.
642
+
568
643
  `syncRegistry()` asks the API for `SUPPORTED_MODES` only
569
644
  (`token,cse,hosted_form,device_encrypt`), so a processor whose flow this build
570
645
  cannot finish is never paused; the API serves `hosted_form` and `device_encrypt`
@@ -766,6 +841,15 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
766
841
  card-on-file policy refuses card on file,
767
842
  `datatrans_card_on_file_refused` themselves, before any create, and those
768
843
  two do not end interception for the page.
844
+ - TokenEx iframe refusals at create, all terminal for that page:
845
+ `CheckoutApiError` with `code` `processor_unavailable` (403, the TokenEx
846
+ iframe is not enabled for your org), `invalid_tokenex_iframe_request` (the
847
+ post is not the card-number frame's own card post), `unsupported_checkout`
848
+ (reason `tokenex_iframe_account_unreviewed`: the post names a TokenEx account
849
+ and origin Agentcard has not reviewed, which is every account today), or
850
+ `amount_required`. The adapters abort the posts behind
851
+ `invalid_tokenex_iframe_request` themselves, before any create, and that
852
+ does not end interception for the page.
769
853
  - `CardEncryptedError`: this processor encrypts the card in-page and its
770
854
  registry entry does not (yet) say the vault can produce that ciphertext;
771
855
  route the purchase to an Agentcard-issued card instead.
@@ -887,8 +971,9 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
887
971
  paused request is not the card request Agentcard reviewed; `detail` names
888
972
  fields, never values), `facts_unavailable` (the merchant keeps its own
889
973
  record of the order and this runtime read none for this checkout) or
890
- `facts_refused` (that record says the order renews, or that the checkout is
891
- in test mode). Every reason but `request_unsupported`, `facts_unavailable`
974
+ `facts_refused` (that record says the order renews, that the merchant
975
+ charges the card later, as a hotel rate that only guarantees the booking
976
+ does, or that the checkout is in test mode). Every reason but `request_unsupported`, `facts_unavailable`
892
977
  and `facts_refused` holds the attachment. The adapters also emit `card_endpoint_refused` with the
893
978
  profile, the endpoint origin and the reason.
894
979
  - `PageAmountRequiredError` (`code: 'page_amount_required'`, `psp`): the paused
@@ -1696,6 +1781,89 @@ const checkout = await attachToPlaywright(page, {
1696
1781
  on their device. `pay.sandbox.datatrans.com` takes only a test-mode client
1697
1782
  and `pay.datatrans.com` only a live one.
1698
1783
 
1784
+ ### Pay a TokenEx iframe checkout (dark launch)
1785
+
1786
+ JetBlue's booking checkout ("Review & Pay") takes the card number and CVV in
1787
+ TokenEx's hosted iframe v3: cross-origin frames on `htp.tokenex.com`
1788
+ (`test-htp.tokenex.com` in test) that JetBlue's page never reads. The
1789
+ cardholder name and the expiry are JetBlue's own fields. When JetBlue asks the
1790
+ frame to tokenize, the card-number frame reads the CVV from its CVV frame and
1791
+ sends the whole card in one request: `POST` to its own document URL, query
1792
+ included, as JSON. The SDK pauses that request, the cardholder's device sends
1793
+ the real card to TokenEx, and the frame receives TokenEx's token, to which it
1794
+ adds its own `cardType` before JetBlue's page reads it.
1795
+
1796
+ ```ts
1797
+ await vault.syncRegistry(); // the TokenEx recognizer is never in the built-in list
1798
+ const placeholder = await vault.placeholderCard({ user: 'your-user-id', psp: 'tokenex_iframe' });
1799
+ // Type placeholder.number and placeholder.securityCode into the TokenEx fields, and the
1800
+ // card's real expiry (placeholder.expiryMonth, placeholder.expiryYear) and name into JetBlue's own.
1801
+ const checkout = await attachToPlaywright(page, {
1802
+ vault, user: 'your-user-id', cardId: placeholder.cardId, merchant: 'JetBlue', amount: '452.10', currency: 'USD',
1803
+ resolveMerchantResult, // reads JetBlue's own order state; see "The same card, sent again" below
1804
+ });
1805
+ ```
1806
+
1807
+ - **Dark launch.** `syncRegistry()` declares the `tokenex_iframe_tokenize`
1808
+ capability, and the API serves the TokenEx recognizer only to an org
1809
+ Agentcard has enabled for it. Every other org's SDK leaves TokenEx requests
1810
+ alone, as before.
1811
+ - **Not live yet.** A token minted into a TokenEx account can be read back as
1812
+ the card number by that account's holder, so the API takes a post only for a
1813
+ TokenEx account and merchant origin Agentcard reviewed, and names that
1814
+ account on the approval. None is reviewed yet: every create answers
1815
+ `unsupported_checkout` (reason `tokenex_iframe_account_unreviewed`) until
1816
+ Agentcard lists JetBlue's. A TokenEx payment also needs the matching API,
1817
+ Vault and checkout relay releases.
1818
+ - **Pass the amount.** The card post names no amount: JetBlue's server charges
1819
+ the token later. `authorize()` refuses one without `amount` above zero and
1820
+ `currency` (`AmountRequiredError`), before anything is created; a page total
1821
+ alone is not enough. Send the total JetBlue's page shows.
1822
+ - **What pauses.** Only the PCI card-number frame's own card post with its CVV
1823
+ frame, exactly as TokenEx's frame writes it. Any other post on the frame's
1824
+ `/iframe/v3` path that carries a card (a stored token's CVV re-entry, a frame
1825
+ without a CVV frame, 3-D Secure or fraud services turned on, a key or query
1826
+ this build has not read, a content type other than JSON) is aborted with a
1827
+ `blocked` event, detail `invalid_tokenex_iframe_request`. Nothing is created,
1828
+ nobody is prompted, the controller's state does not change, and the
1829
+ placeholder never reaches TokenEx. TokenEx's frame waits for that request
1830
+ until it is reloaded, so the page has to load the card form again. A post
1831
+ with nothing typed continues untouched, and so do the frame documents and
1832
+ TokenEx's BinLookup and Forter calls.
1833
+ - **What the frame receives.** TokenEx's token as the API recorded it, rebuilt
1834
+ from Agentcard's allow-list (`token`, `firstSix`, `lastFour`,
1835
+ `referenceNumber`, `tokenHMAC`, `cvvIncluded`) under a JSON content type. An
1836
+ approval whose recorded answer is not a token, TokenEx's own refusal
1837
+ included, ends `PaymentOutcomeUnknownError` with reason
1838
+ `tokenex_iframe_token_unconfirmed`, and the frame gets nothing: the card may
1839
+ already have left the cardholder's device.
1840
+ - **The same card, sent again.** TokenEx's frame answers a repeat with nothing
1841
+ changed from its own cache, with no request. It sends the same bytes again
1842
+ only when its five-minute timer has run, or after a reset or a reload at the
1843
+ same URL. Once a post was approved, the SDK answers that post from the same
1844
+ frame, byte for byte, with the same token: no second prompt, no second card
1845
+ sent. The adapters emit `repeat_answered` with the `authorizationId`; it is
1846
+ not a second payment and must not be counted as one. Another placeholder or
1847
+ CVV is a new approval, because the frame names the brand from the digits
1848
+ typed; so is a re-created frame or one JetBlue minted a new key for. The
1849
+ answer lasts while that approval is the checkout's current one, and at most
1850
+ the iframe key's lifetime (TokenEx's `ExpiresInSeconds`, at most 20
1851
+ minutes), after which TokenEx would refuse the same post. Once the token
1852
+ reached the page, before each re-send the SDK calls your
1853
+ `resolveMerchantResult` and answers only while it reports `pending`; an
1854
+ attachment with no `resolveMerchantResult` answers no re-send after the
1855
+ token reached the page, and each one asks the cardholder again.
1856
+ - **Type a placeholder of the card's brand.** The frame names the brand to
1857
+ JetBlue's page from the digits the agent types, so the cardholder's device
1858
+ refuses a card whose brand differs, before anything is sent. Use
1859
+ `placeholderCard({ user, psp: 'tokenex_iframe' })`: it gives a Visa,
1860
+ Mastercard or American Express number the frame reads as the stored card's
1861
+ own brand, and throws `PlaceholderCardError` (`brand_unsupported`) for any
1862
+ other network. Agentcard-issued cards are Mastercard.
1863
+ - Autopilot never pays a TokenEx checkout; the cardholder approves each one on
1864
+ their device. `test-htp.tokenex.com` takes only a test-mode client and
1865
+ `htp.tokenex.com` only a live one.
1866
+
1699
1867
  ### Pay a Pelecard checkout
1700
1868
 
1701
1869
  Pelecard's hosted payment page (Iframe/Redirect 2.0) runs at
package/dist/amadeus.d.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  export declare const AMADEUS_SEND_WINDOW_MS = 20000;
3
3
  /** How long after the pause a paused Wix tokenize request may still be sent, of the 30 seconds Wix's widget waits. */
4
4
  export declare const WIX_PAYMENTS_SEND_WINDOW_MS = 20000;
5
+ /** How long after the pause a paused HiPay token create may still be sent, of the 20 seconds the merchant's page waits. */
6
+ export declare const HIPAY_SEND_WINDOW_MS = 12000;
5
7
  /**
6
8
  * True when a request the adapters refuse must fail as a network error (ERR_FAILED) rather than as
7
9
  * the cancellation (ERR_ABORTED) they use elsewhere. An XMLHttpRequest reports a cancellation only
@@ -11,13 +13,13 @@ export declare const WIX_PAYMENTS_SEND_WINDOW_MS = 20000;
11
13
  */
12
14
  export declare function failsAsNetworkError(url: string): boolean;
13
15
  /** The processor a send window's events and stop cause name. */
14
- export type SendWindowProcessor = 'amadeus' | 'secure_acceptance' | 'wix_payments';
16
+ export type SendWindowProcessor = 'amadeus' | 'secure_acceptance' | 'wix_payments' | 'hipay';
15
17
  /** Why a send window stopped its attempt: the API confirmed the card never left the device and never can. */
16
18
  export type SendWindowClosed = `${SendWindowProcessor}_send_window_closed`;
17
19
  /**
18
20
  * The send window for a request paused under a preparation for `psp`, or null when it has none: a
19
21
  * prepared Amadeus SDK add, a prepared CyberSource Secure Acceptance token create and a prepared Wix
20
- * tokenize request, each 20 seconds.
22
+ * tokenize request, each 20 seconds, and a prepared HiPay token create, 12 seconds.
21
23
  */
22
24
  export declare function preparedSendWindowMs(psp: string | undefined, url: string): number | null;
23
25
  /** The processor a prepared request's send window names in its events, or null when it has none. */