@agent-cards/sdk 0.23.9 → 0.23.11

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 (65) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/PREFLIGHT.md +7 -5
  3. package/README.md +211 -102
  4. package/dist/aci.d.ts +7 -5
  5. package/dist/aci.generated.d.ts +2 -1
  6. package/dist/aci.generated.js +14 -4
  7. package/dist/aci.js +28 -7
  8. package/dist/amadeus-checkout.generated.js +2 -2
  9. package/dist/builtin-registry.generated.js +1 -1
  10. package/dist/card-fields.generated.js +165 -10
  11. package/dist/cdp.js +86 -34
  12. package/dist/client.d.ts +73 -33
  13. package/dist/client.js +136 -45
  14. package/dist/creditguard.generated.d.ts +1 -1
  15. package/dist/creditguard.generated.js +1 -3
  16. package/dist/cse-body.d.ts +7 -6
  17. package/dist/cse-body.js +8 -7
  18. package/dist/datatrans.generated.js +8 -5
  19. package/dist/elavon-converge.d.ts +5 -2
  20. package/dist/elavon-converge.generated.js +9 -5
  21. package/dist/elavon-converge.js +8 -4
  22. package/dist/fiserv.d.ts +9 -5
  23. package/dist/fiserv.generated.d.ts +3 -3
  24. package/dist/fiserv.generated.js +50 -20
  25. package/dist/fiserv.js +10 -6
  26. package/dist/lifecycle.d.ts +9 -1
  27. package/dist/lifecycle.js +34 -0
  28. package/dist/payrails.d.ts +78 -18
  29. package/dist/payrails.generated.d.ts +12 -2
  30. package/dist/payrails.generated.js +213 -18
  31. package/dist/payrails.js +165 -44
  32. package/dist/pre-claim.d.ts +7 -0
  33. package/dist/pre-claim.js +98 -36
  34. package/dist/preflight-capabilities.generated.d.ts +0 -106
  35. package/dist/preflight-capabilities.generated.js +0 -153
  36. package/dist/preflight-catalog.json +61 -181
  37. package/dist/preflight-schemas.json +6 -6
  38. package/dist/preflight.generated.d.ts +0 -106
  39. package/dist/preflight.generated.js +27 -15
  40. package/dist/preparation.d.ts +2 -0
  41. package/dist/preparation.js +4 -2
  42. package/dist/prepared-processor.d.ts +19 -5
  43. package/dist/prepared-processor.js +21 -5
  44. package/dist/primer.generated.d.ts +5 -1
  45. package/dist/primer.generated.js +202 -34
  46. package/dist/registry.d.ts +18 -12
  47. package/dist/registry.js +11 -8
  48. package/dist/shiji-sts.d.ts +24 -22
  49. package/dist/shiji-sts.generated.d.ts +1 -1
  50. package/dist/shiji-sts.generated.js +12 -8
  51. package/dist/shiji-sts.js +49 -46
  52. package/dist/stripe-checkout.generated.js +157 -49
  53. package/dist/verifone.generated.js +192 -17
  54. package/dist/verifone.js +1 -1
  55. package/dist/worldline-mycheckout.d.ts +27 -0
  56. package/dist/worldline-mycheckout.generated.d.ts +29 -0
  57. package/dist/worldline-mycheckout.generated.js +162 -0
  58. package/dist/worldline-mycheckout.js +93 -0
  59. package/dist/wspay.d.ts +17 -16
  60. package/dist/wspay.generated.d.ts +6 -1
  61. package/dist/wspay.generated.js +13 -3
  62. package/dist/wspay.js +9 -11
  63. package/examples/preflight/kernel-native/README.md +1 -1
  64. package/examples/preflight/kernel-native/inventory.json +1 -19
  65. package/package.json +3 -3
package/README.md CHANGED
@@ -61,7 +61,7 @@ One choice per business, not per purchase; there is no default, and a client bui
61
61
 
62
62
  ### The card that pays
63
63
 
64
- Under the person's smart routing (on unless they turned it off in the Vault) the card that earns the most for the purchase pays when your create pins none, and every read names it: `purchase.card` is `{ id, name, last4, brand, reason }`, the reason the recommendation's sentence for it, null when your create pinned the card (`card_id`) or the person chose another. `purchase.cards` lists every card the person could pay with instead, each with `recommended` and the recommendation's reason where it scored the card. The key word line names the card and the reason before the word ("Paying 34 dollars and 99 cents to Epicdrops with your Sapphire, three times the points on shopping. Please say your secure passphrase, …"), and the bundle carries `card` so the app shows it beside the word: hearing the card, then speaking the passphrase, is the confirmation. "Another card", spoken or tapped, is `voice.authorizations.setCard(purchase.id, card.id)` before the approval; ask for the key word after it, so the line names the card that will pay. Once the purchase is decided the call answers `already_finalized`.
64
+ Under the person's smart routing (on unless they turned it off in the Vault) the card that earns the most for the purchase pays when your create pins none, and every read names it: `purchase.card` is `{ id, name, last4, brand, reason }`, the reason the recommendation's sentence for it, null when your create pinned the card (`card_id`) or the person chose another. `purchase.cards` lists every card the person could pay with instead, each with `recommended` and the recommendation's reason where it scored the card. The key word line names the card and the reason before the word ("Paying 34 dollars and 99 cents to Epicdrops with your Sapphire, three times the points on shopping. Please say your secure passphrase, …"), and the bundle carries `card` so the app shows it beside the word: hearing the card, then speaking the passphrase, is the confirmation. "Another card", spoken or tapped, is `voice.authorizations.setCard(purchase.id, card.id)` before the approval; ask for the key word after it, so the line names the card that will pay. Once the purchase is decided the call answers `already_finalized`. A prepared checkout pays only with the card the cardholder approved for it: there the call answers `card_not_stored` for a card a company added and `preparation_card_locked` for any other card.
65
65
 
66
66
  ### Where the key word goes
67
67
 
@@ -260,9 +260,9 @@ Coverage is specific to the processor request format, merchant setup, browser tr
260
260
  | ACI Worldwide COPYandPAY (Halfords) | dark launch: served only after `syncRegistry()` and only for an org Agentcard enabled; prepared checkout only. The card-number request is replayed from the cardholder's device, the page's own CVC and expiry requests are answered locally, and `/payment` continues once. See [Pay an ACI COPYandPAY checkout](#pay-an-aci-copyandpay-checkout) |
261
261
  | Tranzila | supported (mode `hosted_form`): the cardholder finishes on Tranzila's own page; the paused form navigation resolves to a synthetic page, and you poll the merchant's order state |
262
262
  | Fiserv Commerce Hub | implemented (mode `cse`, prepared checkout only) and off until Agentcard turns it on for your company: the card frame's card capture on `connect.fiservapis.com` (or `connect-cert.fiservapis.com` in the sandbox), with the approved card encrypted on the cardholder's device under a key Agentcard reviewed for the merchant. A merchant whose frame posts to Fiserv's `payment-link` endpoint is not recognized. No live merchant purchase is verified yet |
263
- | Worldline MyCheckout | implemented (mode `hosted_form`) and off until Agentcard turns it on for your company: the card form on Worldline's hosted payment page, a top-level form POST to `/checkout` on `payment.pay1` or `payment.pay2` under `checkout.worldline-solutions.com` (pre-production: `payment.pay1.preprod` and `payment.pay2.preprod`). The cardholder's device submits Worldline's form with the approved card; the paused navigation resolves to the synthetic page, and you poll the merchant's order state. The form names no amount, so pass `amount` and `currency`, above zero: that is what the cardholder approves. A card submit with a checked "remember" box, a Google Pay token or a Click to Pay answer is refused. Google Pay and Click to Pay payments that carry no card number continue untouched, as the page's cancel and its other payment methods do: no authorization, no prompt. A MyCheckout page on a merchant's own subdomain is not recognized. No live merchant purchase is verified yet |
264
- | Verifone eCommerce (verifone.js) | implemented (mode `cse`, prepared checkout only) and off until Agentcard turns it on for your company: the SDK pauses a request at a merchant whose page encrypts the card with verifone.js and posts it to the merchant's own server only when Agentcard reviewed that merchant as a Verifone profile. The Container Store is the one profile, in `observe`, and Agentcard has reviewed neither its card request nor its key yet, so the SDK reports and aborts a request there that carries a verifone.js blob, and nothing pays (see "Verifone merchants that encrypt the card in the page"). Agentcard has not verified a live merchant purchase yet |
265
- | Gravy (Gr4vy) Secure Fields | not supported: recognized, not intercepted, and the preflight reports `unsupported` with `request_method_not_supported`. The card request is Secure Fields' update of a checkout session: a JSON body to `/checkout/sessions/<session id>/fields` on the merchant's own host `api.<merchant>.gr4vy.app` (sandbox: `api.sandbox.<merchant>.gr4vy.app`), carrying `payment_method.number`, `payment_method.expiration_date` as one `MM/YY` value and `payment_method.security_code`. Gravy publishes this request as an HTTP PUT and the SDK pauses and replays POST requests only, so today the SDK recognizes the destination but the request continues to Gravy untouched. The shape follows Gravy's published Secure Fields contract and is not confirmed by a capture from a live merchant form. Updating the session stores the card with Gravy; the merchant creates the transaction from the session on its own server, so a completed request is not a payment. Gravy Embed, the hosted frame on `embed.<merchant>.gr4vy.app`, is not covered |
263
+ | Worldline MyCheckout | implemented (mode `hosted_form`) and off until Agentcard turns it on for your company: the card form on Worldline's hosted payment page, a top-level form POST to `/checkout` on `payment.pay1` or `payment.pay2` under `checkout.worldline-solutions.com` (pre-production: `payment.pay1.preprod` and `payment.pay2.preprod`). The cardholder's device submits Worldline's form with the approved card; the paused navigation resolves to the synthetic page, and you poll the merchant's order state. The form names no amount, so pass `amount` and `currency`, above zero: that is what the cardholder approves. A card submit with a checked "remember" box, a Google Pay token or a Click to Pay answer is refused. Google Pay and Click to Pay payments that carry no card number continue untouched, as the page's cancel and its other payment methods do: no authorization, no prompt. A MyCheckout page on a merchant's own subdomain is not recognized. On a page that asks the shopper to choose a card product first, the adapters hold the card submit to that product: Worldline's IIN lookup on the agent's placeholder can switch the form to another product of the same network (CLZ's page read the Visa placeholder as Visa Electron), and a submit whose `paymentProductId` is not the product chosen is aborted before any create, with a `blocked` event, `worldline_mycheckout_product_changed` (`worldline_mycheckout_product_missing` when it names none), without holding the attachment. `placeholderCard({ psp })` returns a processor's own placeholder for the card's network where one is reviewed; none is yet for Worldline, so its Visa placeholder can still be read as Visa Electron, and that submit is then refused. No live merchant purchase is verified yet |
264
+ | Verifone eCommerce (verifone.js) | implemented (mode `cse`, prepared checkout only) and off until Agentcard turns it on for your company: the SDK pauses a request at a merchant whose page encrypts the card with verifone.js and posts it to the merchant's own server only when Agentcard reviewed that merchant as a Verifone profile. The Container Store is the one profile, in `observe`, and Agentcard has reviewed its card request but not its key yet, so the SDK reports and aborts a request there that carries a verifone.js blob, and nothing pays (see "Verifone merchants that encrypt the card in the page"). Agentcard has not verified a live merchant purchase yet |
265
+ | Gravy (Gr4vy) Secure Fields | dark launch: for an org Agentcard has enabled, after `vault.syncRegistry()`, which declares `gravy_session_fields_put`; for any other org the SDK leaves Gravy's requests alone and the request continues to Gravy untouched. The card request is Secure Fields' update of a checkout session: a JSON `PUT` to `/checkout/sessions/<session id>/fields` on the merchant's own host `api.<merchant>.gr4vy.app` (sandbox: `api.sandbox.<merchant>.gr4vy.app`), sent from Gravy's hidden controller frame and carrying `payment_method.number`, `payment_method.expiration_date` as one `MM/YY` value and `payment_method.security_code`. The SDK pauses that `PUT`, never a `POST` to the same URL; the cardholder's device sends the card as the same `PUT`, and the frame receives Gravy's answer, a `204` with no body. The request names no amount, so pass `amount` and `currency`. Updating the session stores the card with Gravy; the merchant creates the transaction from the session on its own server, so a completed request is not a payment. Gravy Embed, the hosted frame on `embed.<merchant>.gr4vy.app`, is not covered |
266
266
  | GMO Payment Gateway | dark launch (mode `token`): only for an org Agentcard has enabled; see [Pay a GMO Payment Gateway checkout](#pay-a-gmo-payment-gateway-checkout) |
267
267
  | CyberSource Flex Microform | dark launch: Microform 2.12.1 card fields only, after `vault.syncRegistry()`, and only for an org Agentcard has enabled. For any other org the API does not serve the recognizer, so nothing pauses there. The cardholder's device seals the card for CyberSource and the card field receives the transient token. Validated in local Chromium; no live merchant purchase yet. See [Pay a CyberSource Flex Microform checkout](#pay-a-cybersource-flex-microform-checkout) |
268
268
  | 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) |
@@ -272,11 +272,12 @@ Coverage is specific to the processor request format, merchant setup, browser tr
272
272
  | Pelecard (Iframe/Redirect 2.0) | dark: paused only for an org Agentcard enables for Pelecard, in test mode only. One-time card payments on `gateway21` and `gateway20.pelecard.biz`; pass `amount`, `currency` and the page's total as `pageAmount`. See [Pay a Pelecard checkout](#pay-a-pelecard-checkout) |
273
273
  | 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 |
274
274
  | Primer (Web SDK v2 hosted fields; Printful, GetYourGuide) | dark launch (mode `token`): only for an org Agentcard has enabled; see [Pay a Primer checkout](#pay-a-primer-checkout) |
275
- | Payrails web SDK v6 (Blacklane) | dark launch (mode `device_encrypt`): only for an org Agentcard has enabled, and only the card form's single-use save until card-on-file consent exists; see [Pay a Payrails checkout](#pay-a-payrails-checkout) |
276
- | 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` (`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) |
277
- | WSPay Components (WSPay's build of Monri Components) | dark launch: the card frame's confirm of a new card for a one-time payment only, 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 is refused, and Valamar asks on every booking. 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) |
278
- | Shiji STS (MyCheck Wallet V3; La Quinta by Wyndham) | dark launch, mode `cse` under a pinned key, and only for an org Agentcard has enabled. Every card request is refused in the browser today (`shiji_sts_keep_on_file`): the card token a hotel booking mints guarantees the stay, and Agentcard cannot yet ask the cardholder to let the hotel keep the card. See [Shiji STS](#shiji-sts-mycheck-wallet-v3) |
279
- | Amadeus Checkout (Air France, KLM) | dark: only for an org Agentcard enabled, and only through `prepare({ psp: 'amadeus_checkout' })`. The Checkout Web SDK's card add pauses; every other action on its URL continues. Air France's and KLM's add is refused as `amadeus_merchant_held_expiry` and every Data Collection Page store as `amadeus_dcp_field_config_unreviewed`, so neither merchant completes yet. See "Prepare an Amadeus Checkout card step" |
275
+ | 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) |
276
+ | 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) |
277
+ | 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 |
278
+ | 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) |
279
+ | 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) |
280
+ | 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" |
280
281
 
281
282
  The recognizer list, and the status of each reviewed Adyen merchant profile on
282
283
  your client, are fetched from the API at runtime (`agentcard.checkout.syncRegistry()`),
@@ -284,7 +285,7 @@ so new processors work without you shipping a release. `attachToCdp` derives the
284
285
  `Fetch.enable` url patterns from that same list rather than a constant, which is
285
286
  why the sync call belongs before the attach. `agentcard.checkout.cardUrlPatterns()` returns
286
287
  those patterns if you arm a CDP connection yourself. Call
287
- `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`
288
+ `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`
288
289
  for the list this build syncs. The API serves Fiserv's card capture only to a
289
290
  build that lists `fiserv_card_capture`, and only for a company Agentcard turned
290
291
  Fiserv on for. An older SDK never pauses a Fiserv request it could not finish,
@@ -307,7 +308,9 @@ cardholder's device and fails every other post your browser makes to NMI, so
307
308
  NMI never sees the card your agent typed. CyberSource Secure Acceptance's token
308
309
  create (`cybersource_sa_token_create`) is served on the same terms too; every
309
310
  other company keeps the stop every build applies to a card posted to Secure
310
- Acceptance.
311
+ Acceptance. So is Gravy's session fields `PUT` (`gravy_session_fields_put`):
312
+ this build pauses that `PUT` and only that method, and for every other company
313
+ the request continues to Gravy untouched.
311
314
 
312
315
  ## Modes
313
316
 
@@ -355,10 +358,12 @@ mode; the difference matters if you drive `authorize()` yourself.
355
358
  against `bodySha256` (see "Adyen merchants that take the card on their own
356
359
  server"). A Fiserv card capture (a prepared checkout only) comes back with its
357
360
  envelope's four members at `source.encryptionData`: write them with
358
- `substituteFiservEnvelope(body, substitutions)`, never
359
- `substituteEncryptedFields`, and continue the request the same way.
360
- `substituteFiservEnvelope` throws, writing nothing, when the body is not the
361
- paused capture or the envelope names another key than the capture does. The
361
+ `substituteFiservEnvelope(body, substitutions, url)`, `url` being the paused
362
+ capture's own, never `substituteEncryptedFields`, and continue the request the
363
+ same way. `substituteFiservEnvelope` throws, writing nothing, when the body is
364
+ not the paused capture or the envelope names another key than the one Agentcard
365
+ pinned for that capture, and for a capture under a credentials session's key
366
+ when `url` is absent or is not the capture endpoint. The
362
367
  API serves a Fiserv envelope only in the moments after the approval; a later
363
368
  read raises `PaymentOutcomeUnknownError` with reason `cse_substitutions_expired`.
364
369
  - **`hosted_form`** (Tranzila, Authorize.Net DPM and SIM). The processor's
@@ -397,13 +402,16 @@ mode; the difference matters if you drive `authorize()` yourself.
397
402
  per-session key that the paused request does not carry: it came in the
398
403
  Payrails init payload the merchant's backend handed the page. The adapters
399
404
  read that payload from the page's own XHR and fetch answers and send its key
400
- with the create as `request.payrails.encryption_public_key`; a caller that
401
- runs its own interception passes it the same way, and a Payrails save
402
- without it throws before anything is created. The cardholder's device
403
- encrypts the real card under that key and sends the save to Payrails
404
- itself, and `authorize()` resolves with `{ mode: 'device_encrypt', status,
405
- headers, body }`: Payrails' 201, rebuilt from an allow-list, to fulfill the
406
- paused request with as in `token`. The ciphertext the device made never
405
+ with the create as `request.payrails.encryption_public_key`, and for a
406
+ card-form payment its `holderReference` as `request.payrails.holder_reference`;
407
+ a caller that runs its own interception passes them the same way, and a
408
+ Payrails request without them throws before anything is created. The
409
+ cardholder's device encrypts the real card under that key and sends the save
410
+ or payment to Payrails itself, and `authorize()` resolves with
411
+ `{ mode: 'device_encrypt', status, headers, body }`: Payrails' 201 for a save,
412
+ or for a payment its 202 naming the authorize action of that same payment (its
413
+ own execution, on its own host), rebuilt from an
414
+ allow-list, to fulfill the paused request with as in `token`. The ciphertext the device made never
407
415
  reaches your runtime. Any other answer throws `PaymentOutcomeUnknownError`
408
416
  (`payrails_answer_unconfirmed`), and autopilot never runs this mode. See
409
417
  [Pay a Payrails checkout](#pay-a-payrails-checkout).
@@ -573,13 +581,15 @@ one. `amountAuthority` on every replay is `stripe_payment_intent`,
573
581
 
574
582
  ### Shiji STS (MyCheck Wallet V3)
575
583
 
576
- La Quinta by Wyndham takes the card through MyCheck's wallet, whose Shiji proxy
584
+ Wyndham's booking pages (every brand on `www.wyndhamhotels.com`, La Quinta and
585
+ Days Inn among them) take the card through MyCheck's wallet, whose Shiji proxy
577
586
  (`sts.proxy.v2.1.js`) encrypts the PAN and the `MM/YY` expiry with RSA PKCS#1
578
- v1.5 under a key Shiji hardcodes and posts
579
- `{accessToken, pan, expires, keySequence}` to
587
+ v1.5 under a key Shiji hardcodes and posts `{accessToken, pan, expires}` to
580
588
  `/v1/token/getTokenId` on `iframe.vault.eu.prod.shijipayment.com` (sandbox:
581
589
  `iframe.vault.eu.uat.shijipayment.com`), then the CVV the same way to
582
- `/v1/token/getCvvId`. The processor ships dark: `syncRegistry()` declares
590
+ `/v1/token/getCvvId` under the merchant's `client_key`. Wyndham's wallet sends
591
+ no `keySequence` on `getTokenId`, and the API admits that shape only from
592
+ Wyndham's booking page. The processor ships dark: `syncRegistry()` declares
583
593
  `shiji_sts_pinned_key` (`SUPPORTED_CAPABILITIES`), and the API serves the
584
594
  recognizer only to an org Agentcard has enabled for it. Every other org's SDK
585
595
  recognizes neither URL, and the page's requests go through untouched, as
@@ -601,15 +611,20 @@ anything pauses:
601
611
  is answered from it. Shiji uses one key for every merchant, so a request of
602
612
  the same shape could name another merchant's `accessToken` and
603
613
  `keySequence`, and the proxy's own request never times out.
604
- - While Agentcard cannot ask the cardholder for consent to a hotel keeping the
605
- card, every such `getTokenId` is refused before any create or prompt:
614
+ - The hotel keeps the card token to guarantee the stay. The approval page says
615
+ the merchant can keep the card on file and charge it again later without
616
+ another approval, and it offers only a card the cardholder stored: the API
617
+ refuses a card a company added (409 `card_not_stored`), at the create when
618
+ the runtime names one and at the approval. If Agentcard turns the card-on-file
619
+ rule off, every such `getTokenId` is refused before any create or prompt:
606
620
  `unsupported_checkout` with reason `shiji_sts_keep_on_file`, and the
607
621
  attachment reads `unsupported`. Nothing reaches Shiji.
608
622
  - A `getCvvId` never prompts. It continues with the device's CVV only when it
609
623
  follows the `getTokenId` this attachment continued, from the same frame,
610
- with the same `accessToken`, under a `keySequence` the API reviewed as the
611
- booking page's own `getCvvId` key and served with the approval, within 120
612
- seconds, and only once (a `companion_continued` event). Every other
624
+ with the same `accessToken`, under a `keySequence` whose SHA-256 the API
625
+ reviewed as the booking page's own `getCvvId` key and served with the
626
+ approval (`shiji_sts_companion.key_sequence_sha256`), within 120 seconds, and
627
+ only once (a `companion_continued` event). Every other
613
628
  `getCvvId` is refused as `shiji_sts_companion_unbound`,
614
629
  `shiji_sts_companion_expired` or `shiji_sts_companion_used`, so the agent's
615
630
  placeholder CVV never rides beside a real card and the real one goes to no
@@ -683,15 +698,23 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
683
698
  field the device fills or the processor requires, `hosted_form_gated`
684
699
  (400) means it carries a live captcha token the device could never
685
700
  re-submit, and `hosted_form_field_refused` (400, with `field` and a
686
- `reason` of `stored_credential`, `not_a_sale` or `callback_host`) means
687
- the form asks the processor for something other than one plain sale
688
- reporting to the merchant you named (a reusable token in or out, a sale
689
- mode that is not a sale, a callback URL off the merchant's host: name the
690
- merchant by its hostname when the form carries callback URLs); all three
691
- are terminal for that page. An Authorize.Net DPM or SIM form adds more
692
- reasons (`duplicate_field`, `not_a_card_payment`, `relay_response_off`,
693
- `form_step`, `special_flow`, `merchant_credential`,
694
- `test_request_on_live_host`, `malformed_field`), terminal the same way,
701
+ `reason` of `stored_credential`, `not_a_sale`, `special_flow` or
702
+ `callback_host`) means the form asks the processor for something other
703
+ than one plain sale reporting to the merchant you named (a reusable token
704
+ in or out, a sale mode that is not a sale, installments or recurring
705
+ billing, a callback URL off the merchant's host other than the processor's
706
+ own result page: name the merchant by its hostname when the form carries
707
+ callback URLs); all three are terminal for that page. A Tranzila form is
708
+ also refused with `duplicate_field` (a field posted twice with different
709
+ values: Tranzila acts on the last copy) or `malformed_field` (a field name
710
+ Tranzila's server reads as another name, such as `cred.type`, or an
711
+ `altcha` field holding anything but the ALTCHA proof of work Tranzila's
712
+ own page writes there before it submits), terminal the same way. That
713
+ proof is the only filled-in challenge field the cardholder's device sends
714
+ with the card. An Authorize.Net DPM or SIM form adds more reasons
715
+ (`duplicate_field`, `not_a_card_payment`, `relay_response_off`,
716
+ `form_step`, `merchant_credential`, `test_request_on_live_host`,
717
+ `malformed_field`), terminal the same way,
695
718
  and `stale_fingerprint`, which is not: `CheckoutApiError.isStaleFingerprint`
696
719
  is true, the adapters quiet the page for `approvalCooldownMs`, and the form
697
720
  the page renders after a reload is judged afresh. `duplicate_submission` (409,
@@ -834,9 +857,10 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
834
857
  `invalid_wspay_request`, `wspay_keep_on_file_unsupported`, `amount_required`,
835
858
  `sandbox_host_on_live_row` or `live_host_on_sandbox_row` (400;
836
859
  `proxy-test.wspay.biz` takes only a test-mode client and `proxy.wspay.biz`
837
- only a live one). The adapters abort the requests behind
838
- `invalid_wspay_request` and `wspay_keep_on_file_unsupported` themselves, and
839
- `authorize()` refuses a missing amount, all before any create.
860
+ only a live one), or `card_not_stored` (409, a card guarantee pinned to a
861
+ card a company added). The adapters abort the requests behind
862
+ `invalid_wspay_request` themselves, and `authorize()` refuses a missing
863
+ amount, both before any create.
840
864
  - `PlaceholderCardError`: `placeholderCard()` found no such stored card
841
865
  (`card_not_found`), no placeholder for its network (`brand_unsupported`), or
842
866
  no expiry on it (`expiry_unknown`). Nothing was created.
@@ -1122,6 +1146,8 @@ await page.getByRole('button', { name: 'Pay', exact: true }).click();
1122
1146
 
1123
1147
  Use `environment: 'shared'` for Bambora, Mercado Pago, Recurly and Spreedly because the same endpoint serves test and live requests. Agentcard cannot establish the processor's test mode from that URL or a credential prefix. Configure test mode through the merchant's processor account when testing. Agentcard's own `sandbox` flag remains separate. Prepared Worldpay, Bambora, Mercado Pago and Recurly requests reject saved-card and recurring request bodies; a refused request retires the local preparation. Reconcile any existing merchant attempt before creating a new attachment.
1124
1148
 
1149
+ A prepared Bambora checkout has 5 seconds from Pay: Custom Checkout's card frame fails its tokenization request after that. The SDK reads a prepared Bambora approval every 100 ms for its first 10 seconds, so the cardholder's answer reaches the frame within one read of reaching the API. When the frame gives up first, the approval survives, as an unprepared one does: once the device answers, the controller reads `ready_to_submit` with reason `awaiting_merchant_retry`, and when the merchant's next Pay click sends the same request again from the same card frame, the SDK answers it from the same approval with no second prompt. The approval belongs to that card frame: the SDK refuses the same request from any other frame, and removing the card frame stops the attachment, as it does while the request is paused. A Bambora token pays nothing until the merchant's server charges it, and an approval the page never asks for again is retired as `merchant_never_retried`.
1150
+
1125
1151
  Prepare a Checkout.com checkout before the agent clicks Pay:
1126
1152
 
1127
1153
  ```ts
@@ -1145,7 +1171,7 @@ Prepare a Fiserv checkout before the agent clicks Pay:
1145
1171
  await controller.prepare({ psp: 'fiserv', environment: 'sandbox', merchantProfile: 'agentcard_sandbox' });
1146
1172
  ```
1147
1173
 
1148
- `merchantProfile` names the key Agentcard reviewed for the merchant, and the cardholder's approval page names that key's merchant. The checkout's `merchant` must be that merchant's name (`Agentcard sandbox` for `agentcard_sandbox`), so the approval, the preparation and the authorization name one merchant; any other name is refused with reason `merchant_profile_mismatch` before any network call. After approval, click Pay once. Fiserv's card frame encrypts the agent's placeholder and posts its card capture; the SDK pauses the capture and binds it to the approval, the cardholder's device encrypts the approved card under that key, and the capture continues from your browser with only the envelope's four members swapped. Fiserv answers your browser, so confirm the order with the merchant.
1174
+ `merchantProfile` names the key Agentcard reviewed for the merchant, and the cardholder's approval page names that key's merchant. The checkout's `merchant` must be that merchant's name (`Agentcard sandbox` for `agentcard_sandbox`), so the approval, the preparation and the authorization name one merchant; any other name is refused with reason `merchant_profile_mismatch` before any network call. Blinds.com and homedepot.com pay under `homedepot_orangepay`, whose merchant is `Home Depot`. Their card frame encrypts under a session key Home Depot's server hands it, which changed twice between 2026-09-30 and 2026-10-11; the cardholder's device seals the approved card under the key Home Depot publishes instead, and the capture continues naming that key. After approval, click Pay once. Fiserv's card frame encrypts the agent's placeholder and posts its card capture; the SDK pauses the capture and binds it to the approval, the cardholder's device encrypts the approved card under that key, and the capture continues from your browser with only the envelope's four members swapped. Fiserv answers your browser, so confirm the order with the merchant.
1149
1175
 
1150
1176
  A card capture with no preparation is refused before anyone is asked: the controller reads `failed` with reason `preparation_required`, nothing reaches Fiserv, and a later `prepare()` on that attachment is refused, so attach again. Fastlane, ACH, gift and EBT captures on the same endpoint continue untouched and leave the preparation for the card capture. Every other body on that endpoint counts as a card capture, including one the SDK cannot prove card-free (not JSON, over 64 KiB, or a plaintext card): with no preparation it is refused the same way, and with one the bind refuses it as `checkout_changed`. So does a capture carrying a header that neither Fiserv's card frame nor the browser writes, such as the `traceparent` header tracing tools add: it is not the request the cardholder approved. Every Fiserv payment needs the cardholder's approval: the envelope carries no expiry, so Autopilot never pays one. Fiserv is off for every company until Agentcard turns it on for yours; until then the API serves your SDK no Fiserv recognizer, so nothing pauses on a Fiserv checkout, and `prepare()` fails with reason `processor_unavailable`. A merchant whose key Agentcard has not turned on yet fails with `unsupported_checkout`.
1151
1177
 
@@ -1162,14 +1188,24 @@ Initial coverage includes the hosted iframe's new-card request. Saved-card CVV u
1162
1188
  Prepare an Amadeus Checkout card step before the agent clicks Pay:
1163
1189
 
1164
1190
  ```ts
1191
+ const card = await vault.placeholderCard({ user: 'your-user-id', cardId: 'vc_...' }); // what the agent types
1192
+ const controller = await attachToPlaywright(page, {
1193
+ vault, user: 'your-user-id', merchant: 'Air France', amount: 1100, currency: 'usd',
1194
+ cardId: card.cardId, // required: the stored card that pays, locked to the approval
1195
+ onApprovalUrl: deliverPrivatelyToCardholder,
1196
+ });
1197
+ // Once the payment step shows, type card.number and card.securityCode into Amadeus's card number and
1198
+ // security code fields, and card.expiryMonth and card.expiryYear into the airline's own expiry field.
1165
1199
  await controller.prepare({ psp: 'amadeus_checkout', environment: 'production' }); // 'sandbox' for Amadeus's test platforms
1166
1200
  ```
1167
1201
 
1202
+ Air France and KLM keep the expiry in a field of their own, outside Amadeus's card fields, and send it to their own server with the booking, so the add Amadeus receives carries an empty expiry. Type the stored card's real expiry into that field: `placeholderCard()` reads it from `GET /api/v2/vault_cards`, with a documented test number of the card's network for Amadeus's own fields. The cardholder's device writes the same month and year into the add beside the real card, so Amadeus and the airline hold one expiry. `cardId` is required (`card_required`), and the approval is locked to that card: the cardholder cannot approve another, and `prepare()` reads a ready checkout that names another card as `ready_unconfirmed`.
1203
+
1168
1204
  Amadeus Checkout ships dark: `syncRegistry()` declares `amadeus_checkout_actions.v1`, and the API serves the recognizer only to an org Agentcard enabled for it, so every other org's Amadeus pages run as before. The Checkout Web SDK sends every action to one URL through its tunnel iframe, starting with `load` as the payment step opens. Only the card `add` pauses. `load`, the BIN lookup and every other action continue untouched in every state, and none of them spends or blocks the preparation, so call `prepare()` once the payment step shows. The add binds when its `Amadeus-Checkout-Sdk-Host` header names the prepared page's host; the cardholder's device fills in the card and sends it, and the page's tunnel receives Amadeus's answer. The add names no amount, so the amount you pass is the one the cardholder approves.
1169
1205
 
1170
1206
  The tunnel gives up on the add 30 seconds after sending it, so the device has 20 seconds from the pause. When they pass with no answer on its way, the SDK asks the API to retire the authorization, or, while the create has not answered, the preparation and any request already bound to it. The API confirms a retirement only while the card is still on the device. Once it does, nothing was sent and nothing can be: the SDK fails the page's request, and the controller reads `failed` with reason `amadeus_send_window_closed`. The cardholder's device keeps an earlier boundary of its own, 12 seconds after it last saw the checkout unbound: past it the device sends nothing and declines the request with the same reason, and the SDK and the controller read it the same way. A prepared checkout is used once, so attach again and prepare again to retry. When the API cannot confirm, the card can already be on its way, so the SDK waits for the device's answer, and the tunnel's own 30 seconds still end the request.
1171
1207
 
1172
- The SDK refuses some requests itself, before any row or prompt, and the controller reads `failed` with the reason: Air France's and KLM's add, whose expiry their own form keeps (`amadeus_merchant_held_expiry`), a Data Collection Page store until the merchant's field configuration is reviewed (`amadeus_dcp_field_config_unreviewed`), and an add whose Host header names another page (`amadeus_header_binding`). Card saving, split payments, installments, currency conversion and Amadeus 3-D Secure data are refused the same way. The SDK judges an add before a card is chosen, so it cannot see the card's network: an add naming another network reaches the cardholder's device, which refuses it after approval (`vendor_mismatch`), with nothing sent. An add with no preparation is refused with `PreparationRequiredError`. An add that pays with a card the merchant saved carries no card number and continues untouched, and so does Air France's Nuvei payment with a `ccTempToken` after the add. Autopilot never pays an Amadeus payment.
1208
+ The SDK refuses some requests itself, before any row or prompt, and the controller reads `failed` with the reason: a Data Collection Page store until the merchant's field configuration is reviewed (`amadeus_dcp_field_config_unreviewed`), and an add whose Host header names another page (`amadeus_header_binding`). Card saving, split payments, installments, currency conversion and Amadeus 3-D Secure data are refused the same way. The SDK judges an add before a card is chosen, so it cannot see the card's network: an add naming another network reaches the cardholder's device, which refuses it after approval (`vendor_mismatch`), with nothing sent. An add with no preparation is refused with `PreparationRequiredError`. An add that pays with a card the merchant saved carries no card number and continues untouched, and so does Air France's Nuvei payment with a `ccTempToken` after the add. Autopilot never pays an Amadeus payment.
1173
1209
 
1174
1210
  Prepare Recurly before clicking the merchant's payment button:
1175
1211
 
@@ -1194,9 +1230,9 @@ await controller.prepare({ psp: 'aci', environment: 'production' }); // 'sandbox
1194
1230
  Type a placeholder card of the approved card's network (a Mastercard-range placeholder for an Agentcard-issued card), then click Pay once. The cardholder's device refuses a placeholder of another network before any card leaves it, and the authorization ends `declined` with reason `aci_brand_mismatch`. The SDK:
1195
1231
 
1196
1232
  - pauses the card-number request and binds it to the preparation. The cardholder's device sends it to ACI with the real number, CVC and expiry, and the page receives ACI's answer.
1197
- - holds the page's own CVC and expiry requests and answers them with that answer, so the placeholder values never reach ACI (`aci_leg_absorbed`). They fail when the card-number request fails, or when none begins within 5 seconds, which is what a one-click stored-card form that sends a CVC request alone gets.
1198
- - lets the holder request through untouched.
1199
- - lets the page's `/payment` submission continue once, only as the widget's own form post into its hidden iframe (a frame navigation) and only after its checkout's card-number request was answered (`aci_payment_continued`). In classic mode the placeholder expiry the page put there is removed (`stripped: true`). `paymentBrand` must be the brand of the placeholder you typed, and `shopperResultUrl` must be on the prepared merchant origin.
1233
+ - holds the page's own CVC and expiry requests and answers them with that answer, so the placeholder values never reach ACI (`aci_leg_absorbed`). They fail when the card-number request fails, or when none begins within 5 seconds, which is what a one-click stored-card form that sends a CVC request alone gets. A refused request that carries no card number, such as a device fingerprint longer than ACI takes, leaves them waiting for the card-number request.
1234
+ - lets the holder request and the ReD Shield device fingerprint the widget posts on load (`customer.browserFingerprint.value` alone, up to the 4,096 characters ACI takes) through untouched.
1235
+ - lets the page's `/payment` submission continue once, only as the widget's own form post into its hidden iframe (a frame navigation) and only after its checkout's card-number request was answered (`aci_payment_continued`). In classic mode the placeholder expiry the page put there is removed (`stripped: true`). `paymentBrand` must be the brand of the placeholder you typed, `shopperResultUrl` must be on the prepared merchant origin, and `shopperResultTarget`, when the page sets one, must be `_top`, ACI's own default.
1200
1236
 
1201
1237
  The SDK removes that expiry only on a browser that sends the body it replaced on a navigation, which the SDK's tests show on Chromium. `attachToCdp` drives Chromium. `attachToPlaywright` reads the page's browser engine, or, for a persistent context, checks whether a CDP session opens, which only Chromium allows. On Firefox or WebKit it refuses a classic-mode `/payment` as `aci_expiry_unstrippable` rather than send the placeholder expiry. A SAQ-A-mode `/payment` has nothing to remove and continues on any engine.
1202
1238
 
@@ -1205,7 +1241,7 @@ The `blocked` events the SDK adds for ACI, by `detail`:
1205
1241
  | `detail` | When |
1206
1242
  |---|---|
1207
1243
  | `aci_card_on_file_refused` | A request asks ACI to keep the card or pay with a stored one (`createRegistration`, `registrationId`, `registrations[...]`, standing instructions, the `/registration` endpoint). A ready preparation is retired too. |
1208
- | `aci_unsupported_request` | A leg the widget never sends, or a `/payment` that a page script sends itself instead of the widget's form. |
1244
+ | `aci_unsupported_request` | A leg the widget never sends, a device fingerprint longer than ACI takes, a `/payment` whose `shopperResultTarget` is not `_top`, or a `/payment` that a page script sends itself instead of the widget's form. |
1209
1245
  | `aci_checkout_reused` | A second card-number request arrives on a checkout whose first one is running or was answered in this attachment. |
1210
1246
  | `aci_leg_unanswered` | A CVC or expiry request's card-number request failed, or none began within 5 seconds. |
1211
1247
  | `aci_payment_unapproved` | A `/payment` arrives before its checkout's card-number request was answered. |
@@ -1240,14 +1276,21 @@ Braintree's native `ClientConfiguration` GraphQL query can run before, during or
1240
1276
 
1241
1277
  Readiness lasts up to 30 seconds (`preparation.expiresAt`) and appears as `ready_to_submit`, with `paymentStatus: 'not_started'`. Trigger the caller-owned Pay action immediately after the promise resolves. Expiry, navigation, cancellation, an early request or a changed checkout fails closed. A preparation and its attachment are single use; reconcile any bound authorization before creating a new attachment. The SDK never clicks Pay, reuses a stale request, changes native request deadlines, or automatically retries a failed prepared checkout.
1242
1278
 
1243
- After Pay, the processor's native deadline still covers fresh authorization binding, device replay and token handoff. A disconnected or backgrounded cardholder device, or a slow transport, can still miss it. A subsequent SCA challenge has its own lifetime after token handoff. If the page's own script abandons an unprepared `token` or `cse` request while the cardholder decides, the approval survives and answers the page's next request for the same purchase (see "The approval outlives the page's own request" above). If the frame that sent the request is removed, the document moves to another URL, the page closes or crashes, or a prepared request or a Shiji STS request is abandoned, the attachment blocks further requests and tries to retire the pre-replay authorization; a started replay or unconfirmed cancellation remains unknown. Without `prepare()`, approval loading and human interaction still share the native deadline, so a delayed approval finishes the page's retry rather than its first request.
1279
+ After Pay, the processor's native deadline still covers fresh authorization binding, device replay and token handoff. A disconnected or backgrounded cardholder device, or a slow transport, can still miss it. A subsequent SCA challenge has its own lifetime after token handoff. If the page's own script abandons an unprepared `token` or `cse` request while the cardholder decides, the approval survives and answers the page's next request for the same purchase (see "The approval outlives the page's own request" above). If the frame that sent the request is removed, the document moves to another URL, the page closes or crashes, or a prepared request other than Bambora's or a Shiji STS request is abandoned, the attachment blocks further requests and tries to retire the pre-replay authorization; a started replay or unconfirmed cancellation remains unknown. Without `prepare()`, approval loading and human interaction still share the native deadline, so a delayed approval finishes the page's retry rather than its first request.
1244
1280
 
1245
1281
  Lost authorization polling, local approval timeouts, or interrupted browser
1246
1282
  handoffs produce `outcome_unknown` and block automatic retry. The thrown
1247
1283
  `PaymentOutcomeUnknownError` carries `authorizationId` when creation was
1248
1284
  acknowledged. `checkout.cancel()` stops the local attachment and polling; it
1249
- does not revoke a pending approval link or undo a processor payment. Cancellation
1250
- after an attempt starts is therefore unknown until reconciled. A cancelled
1285
+ does not revoke a pending approval link or undo a processor payment. Cancelling
1286
+ an attempt still under way (a card request in flight, or an authorization not
1287
+ yet settled) therefore reads `outcome_unknown` with reason `local_cancel` until
1288
+ reconciled. A checkout that already ended keeps its state and reason:
1289
+ `completed`, `declined`, `failed`, `timed_out`, `unsupported`, `cancelled` and
1290
+ `outcome_unknown` read the same after `cancel()` once no card request is in
1291
+ flight, or once the request in flight has its own outcome recorded, as when
1292
+ your code cancels from `onStateChange` or the `failed` event. A cleanup that
1293
+ cancels never hides a processor refusal or a confirmed payment. A cancelled
1251
1294
  attachment cannot restart.
1252
1295
 
1253
1296
  ### Adyen merchants that take the card on their own server
@@ -1598,6 +1641,15 @@ const checkout = await attachToPlaywright(page, {
1598
1641
  sets its CVV length and contact fields by that brand, so the cardholder's
1599
1642
  device refuses a card whose brand differs, before anything is sent.
1600
1643
  Agentcard-issued cards are Mastercard: type a Mastercard test number.
1644
+ - **An expiry the merchant keeps.** RaiseNow's donation widget (Tamaro)
1645
+ collects the expiry in its own field and sends it to RaiseNow beside the
1646
+ token, so its card post carries no `expm` or `expy`. Such a post pauses like
1647
+ any other, and the cardholder's device writes the card number and CVV and
1648
+ adds no expiry, as the page sent none. Type the card's real expiry from
1649
+ `placeholderCard()` into the merchant's own expiry field: the merchant
1650
+ charges with the expiry its form holds. A post with only one of the two, or
1651
+ with no expiry while it asks to keep the card on file, is refused as
1652
+ `invalid_datatrans_request`.
1601
1653
  - **Merchant accounts.** A token minted into a Datatrans account can be read
1602
1654
  back as the card number by that account's holder, so the API refuses a
1603
1655
  merchant account Agentcard has not reviewed (`datatrans_merchant_unregistered`).
@@ -1745,34 +1797,53 @@ The other requests on Primer's hosts:
1745
1797
 
1746
1798
  A client session the merchant created to save every card after payment (Printful
1747
1799
  does this for a signed-in shopper) shows nothing in the request, so the SDK
1748
- cannot detect that save. Until the cardholder can consent to it, Primer pays
1749
- only for a test-mode client: for a live client the API answers 400
1750
- `invalid_primer_request` with reason `live_keep_on_file` before anything is
1751
- created, the SDK aborts the paused request with nothing sent, and the attachment
1752
- reads `failed`.
1800
+ cannot detect that save. A live payment therefore goes ahead only with a card
1801
+ the cardholder stored, on an approval that says the merchant keeps the card on
1802
+ file. Nothing in the request names the Primer merchant whose session keeps it,
1803
+ so the approval names the merchant as the `merchant` you sent and says Agentcard
1804
+ cannot confirm it. The SDK sends the checkout page's origin as `checkout_origin`
1805
+ when the browser exposes it, and a live payment needs an https one. Without it,
1806
+ the API answers 400 `invalid_primer_request` with reason
1807
+ `checkout_origin_required` before anything is created, the SDK aborts the paused
1808
+ request with nothing sent, and the attachment reads `failed`. A card a company
1809
+ added never pays a live Primer payment (409 `card_not_stored`).
1753
1810
 
1754
1811
  ### Pay a Payrails checkout
1755
1812
 
1756
- Payrails (web SDK v6 secure fields, as on Blacklane's consumer booking checkout)
1757
- launches dark: `syncRegistry()` asks for the `device_encrypt` mode and declares
1813
+ Payrails (web SDK secure fields, as on Blacklane's consumer booking checkout and
1814
+ FlixBus's guest checkout) launches dark: `syncRegistry()` asks for the `device_encrypt` mode and declares
1758
1815
  the `payrails_device_encrypt` capability, and the API serves the Payrails
1759
1816
  recognizer only to an org Agentcard has enabled for Payrails. For every other
1760
1817
  org nothing changes: no answer is read, no Payrails host is intercepted, and
1761
1818
  Payrails' requests go through untouched.
1762
1819
 
1763
- For an enabled org, the SDK pauses one request: the card form's save, a `POST`
1764
- to the session's `saveInstrument` link (`https://api-pub.payrails.io/public/payment/instruments`;
1765
- test hosts `api-pub.staging.payrails.io` and `sandbox-api-pub.staging.payrails.io`)
1766
- that carries the whole card as one JWE. The key it was encrypted under is not in
1767
- that request, so the adapters read the Payrails init payload (`{version, data}`)
1768
- from the page's own XHR and fetch answers and keep, in memory only, up to eight
1769
- sessions for fifteen minutes. A paused save binds to the one session whose token,
1770
- save link, `holderReference` and `providerConfigId` it names, seen at most nine
1771
- minutes before, and that session's key rides on the create. The cardholder's
1772
- device encrypts the real card under it and sends the save to Payrails itself;
1773
- the page receives Payrails' 201 rebuilt from an allow-list (the instrument, its
1774
- status, the network, the BIN cut to six digits, the last four, the expiry, the
1775
- holder name and the fingerprint). Call `syncRegistry()` before attaching, or the
1820
+ For an enabled org, the SDK pauses two requests, each carrying the whole card as
1821
+ one JWE: the card form's save, a `POST` to the session's `saveInstrument` link
1822
+ (`https://api-pub.payrails.io/public/payment/instruments`; test hosts
1823
+ `api-pub.staging.payrails.io` and `sandbox-api-pub.staging.payrails.io`), and the
1824
+ card form's payment, a `POST` to the session's execution authorize link
1825
+ (`/public/merchant/workflows/payment-acceptance/executions/{id}/authorize`), which
1826
+ FlixBus's Pay now button sends. Either may go to a merchant's own Payrails host,
1827
+ `api-pub.<merchant>.payrails.io` (FlixBus's is `api-pub.flix.payrails.io`). The key
1828
+ the card was encrypted under is not in the request, so the adapters read the
1829
+ Payrails init payload (`{version, data}`) from the page's own XHR and fetch
1830
+ answers, at the top of an answer or anywhere in its first eight levels (FlixBus
1831
+ nests it inside `/payment/configuration/list`), and keep, in memory only, up to
1832
+ eight sessions for fifteen minutes. A paused save binds to the one session whose
1833
+ token, save link, `holderReference` and `providerConfigId` it names, and a paused
1834
+ payment to the one whose token, execution, host and `providerConfigId` it names,
1835
+ seen at most nine minutes before. That session's key rides on the create, and for
1836
+ a payment its `holderReference` too. The cardholder's device encrypts the real
1837
+ card under the key and sends the request to Payrails itself. For a save the page
1838
+ receives Payrails' 201 rebuilt from an allow-list (the instrument, its status, the
1839
+ network, the BIN cut to six digits, the last four, the expiry, the holder name and
1840
+ the fingerprint); for a payment, Payrails' 202 rebuilt to the authorize action's
1841
+ name, id, time and execution link, which the page's own Payrails SDK polls for the
1842
+ issuer's answer and any 3-D Secure challenge. That link must name the payment the
1843
+ page sent, its own execution on its own host: an action naming another execution,
1844
+ or this one on another host, would have the page poll another payment's result, so
1845
+ it never reaches the page and `authorize()` throws `PaymentOutcomeUnknownError`
1846
+ (`payrails_answer_unconfirmed`). Call `syncRegistry()` before attaching, or the
1776
1847
  page's session answers are not read.
1777
1848
 
1778
1849
  ```ts
@@ -1785,7 +1856,8 @@ const controller = await attachToPlaywright(page, {
1785
1856
 
1786
1857
  A save names no amount (the merchant charges the saved card later), so pass
1787
1858
  `amount` and `currency`, or a `pageAmount` reader: without either, the save
1788
- throws before anything is created.
1859
+ throws before anything is created. A payment names the amount it authorizes, and
1860
+ the API holds the amount you pass, if any, to it.
1789
1861
 
1790
1862
  What the SDK refuses, before anything is created, with the reason on the
1791
1863
  `unsupported_checkout` event:
@@ -1798,17 +1870,30 @@ What the SDK refuses, before anything is created, with the reason on the
1798
1870
  `payrails_not_card_save`, `payrails_jwe_malformed`) are refused too. The
1799
1871
  attachment reads `unsupported` with that reason and holds, since the merchant's
1800
1872
  integration decides that shape.
1801
- - Paying with the card form or a stored card with a CVC field, which sends the
1802
- card inside `paymentComposition` to any Payrails host:
1803
- `unsupported_payrails_card_payment`. The attachment holds, and the agent's
1873
+ - A card-form payment that stores the card at the merchant (`storeInstrument`,
1874
+ enrollment in a network offers program, or `meta.order.processingType`
1875
+ `CardOnFile` or `UnscheduledCardOnFile`):
1876
+ `payrails_card_on_file_consent_required`; one that sets up a subscription
1877
+ (`meta.subscription`, or `processingType` `Subscription`):
1878
+ `payrails_future_usage_refused`. Any other payment that sends the card
1879
+ inside `paymentComposition` in a shape the SDK does not take over (a stored
1880
+ card with a CVC field, a split or installment payment, a preferred scheme,
1881
+ another link than the execution's authorize link, or a `meta` member outside
1882
+ the reviewed list): `unsupported_payrails_card_payment`. The reviewed `meta`
1883
+ is the SDK's `clientContext` facts, `risk.sessionId` or `sessions`,
1884
+ `risk.threeDSMode` `Default`, a `risk` skip flag set `false`, an `order` with
1885
+ only `processingType` `Regular`, and an empty `customer`. Payrails reads keys
1886
+ in any case, so a key spelled in another case, or two keys of one object that
1887
+ differ only in case, are refused as well. The attachment holds, and the agent's
1804
1888
  placeholder card never reaches an authorization.
1805
- - A save no captured session matches (`payrails_session_unavailable`), or whose
1806
- captured key is not the one the page's secure frame used (`payrails_key_invalid`,
1807
- `payrails_key_mismatch`): that request alone is aborted, the event carries
1808
- `message: 'Payrails session expired, reload the checkout'`, and the save after
1809
- a reload is judged afresh.
1810
-
1811
- A save with no card (a dynamic payment-method form posts to the same link)
1889
+ - A save or payment no captured session matches (`payrails_session_unavailable`),
1890
+ or whose captured key is not the one the page's secure frame used
1891
+ (`payrails_key_invalid`, `payrails_key_mismatch`): that request alone is aborted,
1892
+ the event carries `message: 'Payrails session expired, reload the checkout'`,
1893
+ and the request after a reload is judged afresh.
1894
+
1895
+ A save or payment with no card (a dynamic payment-method form posts to the save
1896
+ link; a wallet, a stored card or a redirect method pays on the authorize link)
1812
1897
  continues untouched. A `POST` to a Payrails host with no body the SDK can read,
1813
1898
  whether it has none, an empty one or a streamed one, is aborted alone with a
1814
1899
  `blocked` event, `unreadable_payrails_request`, by both adapters: Playwright
@@ -1816,8 +1901,8 @@ reports all three the same way. The Payrails SDK sends a JSON body with every
1816
1901
  `POST`, so none of its own requests is refused this way. Payrails' static host,
1817
1902
  `assets.payrails.io`, serves only files and takes no payment, so a bodyless
1818
1903
  `POST` another script sends there continues untouched. The matching API,
1819
- Vault and database releases are required, and the API also refuses a save for a
1820
- merchant whose Payrails workspace Agentcard has not reviewed.
1904
+ Vault and database releases are required, and the API also refuses a request for a
1905
+ merchant whose Payrails workspace or host Agentcard has not reviewed.
1821
1906
 
1822
1907
  ### Pay an Elavon Converge payment page
1823
1908
 
@@ -1829,7 +1914,9 @@ to its own `/hosted-payments/service/payment/hpp/process`. The SDK pauses that
1829
1914
  request, the cardholder approves the amount it names on their phone, their
1830
1915
  device sends the real card to Converge, and the page receives Converge's
1831
1916
  approval, so its own script shows the receipt and returns the buyer to the
1832
- merchant. Converge's demo environment serves the same page on
1917
+ merchant. Every Converge Buy Button and pay link opens the same page on
1918
+ `www.convergepay.com`, which posts the card to its own origin and is paid the
1919
+ same way. Converge's demo environment serves the same page on
1833
1920
  `api.demo.convergepay.com`, which pays in test mode only.
1834
1921
 
1835
1922
  Converge ships dark: `syncRegistry()` asks for the `converge_hpp_process`
@@ -1851,16 +1938,29 @@ const checkout = await attachToPlaywright(page, {
1851
1938
  grant never pays one.
1852
1939
  - Electronic check and financing payments on the same URL carry no card and
1853
1940
  continue untouched.
1941
+ - A Buy Button's page sends `recurring` as `"false"` in every request unless the
1942
+ buyer ticked the button's recurring box. That request is a one-time sale and
1943
+ pauses like any other; `"true"`, or any other value, is refused as recurring.
1944
+ - A page with its CAPTCHA on, as every Buy Button page read so far has it,
1945
+ sends its card request only after the CAPTCHA is solved, with the answer in
1946
+ `captchaToken`. A person solves it in the browser, never your agent, so a
1947
+ browser that solves CAPTCHAs itself, such as Kernel with stealth on, must not
1948
+ run a Converge page. The SDK
1949
+ pauses that request like any other, and the cardholder's device sends the
1950
+ answer to Converge unchanged beside the card. Google takes the answer once,
1951
+ for two minutes after the solve, so the cardholder approves inside that
1952
+ window.
1854
1953
  - Before any prompt, the SDK refuses every other shape on that URL with
1855
1954
  payment-core's own code (`ConvergeRefusalReason`, listed in
1856
1955
  `CONVERGE_REFUSAL_REASONS`), the one the API and the cardholder's device
1857
1956
  would answer: a wallet, the debit, gift card and push-to-card tabs, a
1858
- follow-up after a 3-D Secure, Pay Defense or surcharge step, a CAPTCHA, a
1859
- public-sector service fee, recurring or installment fields, card saving, a
1860
- tip or cash back, another currency, no amount, no page session, and anything
1861
- that is not the page's own request. The page's request is aborted, nothing is
1862
- created, and the attachment is not held, so the page's next payment is judged
1863
- afresh. The table below gives each code's meaning and the next step.
1957
+ follow-up after a 3-D Secure, Pay Defense or surcharge step, a CAPTCHA
1958
+ answer Google never issued, a public-sector service fee, recurring or
1959
+ installment fields, card saving, a tip or cash back, another currency, no
1960
+ amount, no page session, and anything that is not the page's own request.
1961
+ The page's request is aborted, nothing is created, and the attachment is not
1962
+ held, so the page's next payment is judged afresh. The table below gives each
1963
+ code's meaning and the next step.
1864
1964
  - A terminal with 3-D Secure 2, Pay Defense or a surcharge answers the first
1865
1965
  request with a step instead of a result. The Vault never hands that answer to
1866
1966
  the page: the authorization ends declined as a processor refusal with
@@ -1903,7 +2003,7 @@ of the same code. `converge_step_unsupported`, `converge_declined` and
1903
2003
  | `converge_gift_card_refused` | The payment came from the page's gift card tab. | Pay with the credit card form. |
1904
2004
  | `converge_push_to_card_refused` | The payment would send money to the card instead of charging it. | None: an Agentcard card never receives money. |
1905
2005
  | `converge_payment_type_refused` | The body names a payment type other than a credit card sale, or an electronic check or financing payment carries a card number. | Pay with the credit card form. |
1906
- | `converge_captcha_refused` | The page sent a CAPTCHA answer with the payment. | The buyer pays this merchant without Agentcard: the cardholder's device cannot answer a CAPTCHA. |
2006
+ | `converge_captcha_refused` | The request's `captchaToken` is not a solved reCAPTCHA answer: it is empty, not text, longer than 4,096 characters, or holds a character other than `A-Z`, `a-z`, `0-9`, `_` and `-`. | Have a person solve the page's CAPTCHA, then let the page send its own request. |
1907
2007
  | `converge_service_fee_refused` | The merchant adds a public-sector service fee, which Converge charges as a second payment. | The buyer pays this merchant without Agentcard: a one-time card cannot pay the second charge. |
1908
2008
  | `converge_3ds_follow_up_refused` | The body follows a 3-D Secure or Pay Defense step the page already ran. | Start a new checkout from the merchant's page. A terminal that runs the step on every payment cannot be paid through Agentcard yet. |
1909
2009
  | `converge_surcharge_refused` | The body follows a surcharge quote, or skips it with the debit bypass. | Start a new checkout from the merchant's page. A surcharging terminal cannot be paid through Agentcard yet. |
@@ -2200,8 +2300,8 @@ const checkout = await attachToPlaywright(page, {
2200
2300
  `charged_amount_mismatch`. A 3-D Secure step names no amount, so nothing is
2201
2301
  compared on that path (below). Without `amount` and `currency` the confirm
2202
2302
  fails with `AmountRequiredError` before anything is created.
2203
- - **What pauses.** Only the frame's confirm of a new card for a one-time
2204
- payment, with the frame's own headers and body. A saved card (`saved_card`),
2303
+ - **What pauses.** Only the frame's confirm of a new card, with the frame's
2304
+ own headers and body. A saved card (`saved_card`),
2205
2305
  a card tokenized earlier (`temp_card_id`), a card-shaped object the page
2206
2306
  built into the transaction, or any other request on that URL is aborted with
2207
2307
  a `blocked` event, detail `unsupported_wspay_payment_method`. Nothing is
@@ -2209,12 +2309,21 @@ const checkout = await attachToPlaywright(page, {
2209
2309
  page's next confirm is judged afresh. The frame's other requests (`/show`,
2210
2310
  `/status`, `/pan-info`) continue untouched.
2211
2311
  - **Keep on file.** A confirm with `tokenize_pan: true` asks WSPay to keep the
2212
- card so the merchant can charge it later without another approval. Only the
2213
- cardholder can agree to that and no approval asks them yet, so such a
2214
- confirm is aborted with a `blocked` event, detail
2215
- `wspay_keep_on_file_unsupported`, and nothing is created. Valamar sets it on
2216
- every booking, so a Valamar checkout is refused this way until the approval
2217
- asks the cardholder for that consent.
2312
+ card so the merchant can charge it later without another approval. Valamar
2313
+ sets it on every booking: its payment step is a card guarantee that
2314
+ authorizes EUR 1.00 and keeps the card for the stay. Such a confirm pauses
2315
+ like any other, and the API pays it only as that guarantee: pass `amount:
2316
+ 100, currency: 'eur'` when the merchant's payment step is a EUR 1.00
2317
+ guarantee. Any other amount fails the create with
2318
+ `wspay_keep_on_file_unsupported` and nothing is created. The cardholder's
2319
+ approval says the merchant keeps the card on file, and it pays only with a
2320
+ card the cardholder stored: a card a company added fails with
2321
+ `card_not_stored`. When WSPay approves, the frame receives the token WSPay
2322
+ keeps for the merchant (`pan_token`), as it would without Agentcard, and
2323
+ never the card's expiry or masked number. The API serves that answer for 15
2324
+ minutes after the approval, the SDK's default window, and then drops it, so
2325
+ a later read gets `response_expired`. The stay itself is charged later,
2326
+ outside this approval.
2218
2327
  - **3-D Secure.** When WSPay answers with its 3-D Secure step, the frame runs
2219
2328
  the bank's challenge in your browser, and nothing is charged until the
2220
2329
  cardholder completes it there. The controller reads `requires_user_action`
@@ -2236,7 +2345,7 @@ const checkout = await attachToPlaywright(page, {
2236
2345
  Some merchants (The Container Store) have Verifone's verifone.js encrypt the card in the page, under the merchant's own Verifone key, and post the result to the merchant's own server. As for the Adyen merchants above, the card request goes to no processor host, so Agentcard reviews each such merchant as a Verifone profile: its checkout page, its card request and the key verifone.js must encrypt the card under. Verifone ships off. `syncRegistry()` declares `verifone_card_capture`, and the API serves the Verifone profiles only to a build that declares it, for a company Agentcard turned Verifone on for. Until a sync serves one, the adapters pause no request at a Verifone merchant, as before. A served profile arms every request to the merchant's origin in both adapters:
2237
2346
 
2238
2347
  - A request that carries no verifone.js blob (a page, a basket call, a preflight, a request with no body) continues as before. When the browser holds a body it did not hand over, such as a blob or a file, `attachToCdp` aborts the request as one the profile cannot pay, except on a `GET`, `HEAD` or `OPTIONS`. `attachToPlaywright` cannot tell such a body from no body, so it reads one as no body.
2239
- - A profile in `observe` never pays. The adapters abort a request that carries a blob before it reaches the merchant, and report `verifone_profile_observed` with the profile, the URL (the origin and a reviewed path template, never a query), the verdict and `keyPathSha256`, then `unsupported_checkout`. The controller reads `unsupported` with reason `merchant_profile_observe`. `containerstore_checkout` ships in `observe`: Agentcard has reviewed neither its card request nor its key.
2348
+ - A profile in `observe` never pays. The adapters abort a request that carries a blob before it reaches the merchant, and report `verifone_profile_observed` with the profile, the URL (the origin and a reviewed path template, never a query), the verdict and `keyPathSha256`, then `unsupported_checkout`. The controller reads `unsupported` with reason `merchant_profile_observe`. `containerstore_checkout` ships in `observe`: Agentcard has reviewed its card request (`POST /checkout/api/submit`) but not its key.
2240
2349
  - The adapters abort a blob request the profile cannot pay (another path, a second card, saving the card, a gift card, a key Agentcard did not pin) and report `unsupported_checkout` with the reason.
2241
2350
  - The profile's reviewed card request, once Agentcard enables the profile for your client and this build, pays only through a preparation for that profile. Without one, the adapters abort it before any pause, prompt or authorization, as for an Adyen merchant.
2242
2351