@agent-cards/sdk 0.23.10 → 0.23.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Keep a Rapyd hosted page's id and a Stripe Checkout Session's id out of your events and error messages. Rapyd's checkout page sends the card to `/v1/checkout/checkout_<id>` on `api.rapyd.net` (a card page to `/v1/hosted/collect/card/hp_card_<id>/payment_method`) with no auth header, so the page's id is the only credential that request needs: anyone who holds it can read the page and post a card to it until it expires. Stripe Checkout confirms a session at `/v1/payment_pages/<session id>/confirm` with the merchant's publishable key, which is public, so the session id is what lets anyone confirm a payment on it. Earlier releases named both ids in the `card_request_paused` event and in the errors `authorize()` throws, so they reached your `onEvent` handler and every log that recorded the event or the error. `redactUrl`, every event and every error message now show them as `{token}` and `{session}`, as in `https://api.rapyd.net/v1/checkout/{token}` and `https://api.stripe.com/v1/payment_pages/{session}/confirm`. `redactUrl` also masks an ACI checkout id in the shapes it used to keep: a host with a trailing dot, an escaped or doubled slash, and a string that is not a URL. A PaymentIntent or SetupIntent id stays, since confirming one also needs its client_secret. No API or Vault release is needed.
6
+
7
+ - 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.
8
+
9
+ - 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.
10
+
11
+ - Continue a Stripe card save for every company. The API judged a SetupIntent confirm after a `/v1/payment_methods` approval only for a company Agentcard had turned card-save continuations on for, and answered every other company `continuation_invalid`, so the SDK aborted the confirm and the page reported the card could not be added. Every company now gets the card save under the same rules: an approval the cardholder gave on the Vault page, not by voice, with no `amount` and no `currency`, paid with exactly the approved payment method on the same Stripe account, and a SetupIntent Stripe reports still confirmable. No SDK upgrade is needed: 0.22.0 and later continue the confirm once the API release deploys.
12
+
13
+ - Refuse a Worldline MyCheckout card submit whose card product is not the one its page chose. A MyCheckout page that offers several card products asks the shopper to choose one, then looks the typed number's IIN up with Worldline and switches the form to the product the answer names. With the agent's placeholder typed, that answer describes the placeholder: on 2026-10-12 CLZ's page read the Visa placeholder (411111) as Visa Electron (122) and switched the form from the Visa (1) the agent chose, and the cardholder's device, which checks only the card's network, would have submitted a Visa credit card as Visa Electron. Both adapters now remember the product each hosted checkout's select-payment-product step chose (that post carries no card and continues untouched) and abort a card submit that names another, before any create or prompt, with a `blocked` event, `worldline_mycheckout_product_changed`, or `worldline_mycheckout_product_missing` for one that names no product. Like the other per-request refusals it neither holds the attachment nor changes its state. A card-group page, and a hosted checkout whose choice the attachment never saw, are judged as before. `placeholderCard()` also takes `psp`, the processor whose card form the agent types into, so a processor can have its own placeholder for a network; none is set yet, so every processor still gets the network's default. Worldline stays dark for every company Agentcard has not turned it on for. No API or Vault release is needed.
14
+
15
+ - Keep a checkout's outcome when your code cancels the attachment after the checkout ended. `cancel()` rewrote every state: a checkout with an authorization or a card request in flight read `outcome_unknown` with reason `local_cancel`, and any other read `cancelled`, so an integration that cancels at cleanup lost a definite answer. On 2026-10-12 a Rapyd checkout the processor refused read `declined` (`processor_refused`) until the runner's cleanup cancel turned it into `outcome_unknown`; a `completed` checkout would have gone the same way. A checkout that reads `completed`, `declined`, `failed`, `timed_out`, `unsupported`, `cancelled` or `outcome_unknown` now keeps its status and reason through `cancel()` once no card request is in flight, or once the request in flight has its own outcome recorded, so an application that cancels from `onStateChange` or the `failed` event after a processor refusal reads `declined`. `cancel()` still stops polling, aborts any paused request and refuses every later card request, and reports no state change. A checkout still under way is unchanged, and so is one whose card request is still in flight with no outcome of its own, such as a request the page dropped mid-approval, since that authorization can still be approved on the cardholder's device: `outcome_unknown` with reason `local_cancel` once a card request is in flight or an authorization exists, `cancelled` before. No API or Vault release is needed.
16
+
5
17
  - Pay IKEA US's ACI checkout as its live payment step sends it. With ACI's ReD Shield screening on, the widget posts the device fingerprint, `customer.browserFingerprint.value` alone, to the checkout's card URL on load, and 0.23.9 refused it as `aci_unsupported_request`. That refusal also failed the checkout's CVC request at once whenever it reached the SDK before the card-number request at Pay, so the widget's Pay failed. The fingerprint now continues untouched, up to the 4,096 characters ACI takes; a longer one is still refused, and a refused request that names no card number no longer fails the checkout's CVC or expiry requests, which wait for the card-number request as before. `/payment` now continues with `shopperResultTarget=_top`, the value IKEA's widget options set and ACI's own default for its final redirect, which 0.23.9 refused after the cardholder's device had already sent the card; any other target is still refused as `aci_unsupported_request`. No API or Vault release is needed.
6
18
 
7
19
  - Pay an APCOA airport drop-off entry on NMI's hosted payment page (`ekashu`). Every APCOA entry carries APCOA's own ASP.NET Core anti-forgery token, `__RequestVerificationToken`, and the API refused it as a field of NMI's card page (`hosted_form_field_refused`, reason `not_an_entry`), so an attached checkout at Heathrow's drop-off could not get past the create. The API now takes the merchant's token and still refuses an entry that carries `ekashu_stage`, `ekashu_context` or `ekashu_submission_id`. No SDK upgrade is needed: the API takes the entry once it deploys, and the cardholder's phone fills NMI's card page from the next Vault release. NMI stays off for every company Agentcard has not turned it on for.
@@ -20,9 +32,13 @@
20
32
 
21
33
  - Pay a Wyndham hotel booking through Shiji STS (MyCheck Wallet V3), for a company Agentcard turned Shiji STS on for. Wyndham's wallet sends its `getTokenId` with no `keySequence`, because its merchant records hold no `clientKey`, and the SDK refused every Shiji `getTokenId` in the browser as `shiji_sts_keep_on_file` while the API could bind none without a `keySequence`. The SDK now pauses that request for the one prompt, writes the cardholder's `pan` and `expires` over the page's own and continues it, then continues the page's `getCvvId` once with the cardholder's CVV under the key whose SHA-256 the API serves with the approval. That field is now `shiji_sts_companion.key_sequence_sha256` and replaces `key_sequences`: an approval in the old shape resolves as `PaymentOutcomeUnknownError` (`cse_substitutions_malformed`) and nothing continues. The cardholder approves with a card they stored, under a line that says the hotel can keep it on file, and the API refuses a card a company added with `409 card_not_stored`. Shiji STS stays dark for every company Agentcard has not turned on, and a payment also needs the matching API and Vault releases.
22
34
 
23
- - Hold a Tranzila card form to one charge that reports to the merchant or to Tranzila, read the way Tranzila's server reads it. Tranzila's own card form, on `direct.tranzila.com` and `directng.tranzila.com`, names its callbacks `success_url`, `fail_url` and `notify_url`, and the API held only the `*_url_address` names the merchant posts to Tranzila to the merchant's host. It now holds all six to the merchant's host or to Tranzila's own result page exactly (`https://direct.tranzila.com/done/`, `https://directng.tranzila.com/process/fail`) and refuses any other with `callback_host`. A form that asks for installments, credit or recurring billing (`cred_type` or `tash` other than 1, `npay` other than 0, any `fpay`, `spay` or `recur_*` value, a checked `make_recur_transaction`) is refused with `special_flow` where it passed as a plain sale. Tranzila's PHP server acts on the last copy of a repeated field and reads `cred.type` as `cred_type`, while the API read the first copy under the name as written, so a second `sum` could charge another amount than the one approved: a field posted twice with different values is now refused with `duplicate_field`, and a name PHP renames with `malformed_field`. Every Tranzila card form read on 2026-10-10 also carries Tranzila's ALTCHA field, which the API still refuses first with `hosted_form_gated`.
35
+ - Hold a Tranzila card form to one charge that reports to the merchant or to Tranzila, read the way Tranzila's server reads it. Tranzila's own card form, on `direct.tranzila.com` and `directng.tranzila.com`, names its callbacks `success_url`, `fail_url` and `notify_url`, and the API held only the `*_url_address` names the merchant posts to Tranzila to the merchant's host. It now holds all six to the merchant's host or to Tranzila's own result page exactly (`https://direct.tranzila.com/done/`, `https://directng.tranzila.com/process/fail`) and refuses any other with `callback_host`. A form that asks for installments, credit or recurring billing (`cred_type` or `tash` other than 1, `npay` other than 0, any `fpay`, `spay` or `recur_*` value, a checked `make_recur_transaction`) is refused with `special_flow` where it passed as a plain sale. Tranzila's PHP server acts on the last copy of a repeated field and reads `cred.type` as `cred_type`, while the API read the first copy under the name as written, so a second `sum` could charge another amount than the one approved: a field posted twice with different values is now refused with `duplicate_field`, and a name PHP renames with `malformed_field`.
36
+
37
+ - Pay a Tranzila card form that carries Tranzila's own ALTCHA proof of work. Every Tranzila card form read on 2026-10-10 (`iframenew.php` on `direct.tranzila.com` and `directng.tranzila.com`, the legacy `iframe.php` and the terminal page) runs an ALTCHA widget that searches for the number whose SHA-256 with the page's salt is its challenge, writes that proof into `altcha` and only then submits, and the API refused every such form with `hosted_form_gated`. The API now takes that one field, on Tranzila only, when it holds one ALTCHA proof whose hash holds, and the cardholder's device sends it once with the card, as the page wrote it. Anything else in `altcha` is refused with `hosted_form_field_refused`, reason `malformed_field`, and every other filled-in challenge field (reCAPTCHA, hCaptcha, Turnstile, or `altcha` on another processor) is still `hosted_form_gated`. No SDK or Vault release is needed: the API takes the form once it deploys.
24
38
 
25
- - Pause an Elavon Converge payment page opened on `www.convergepay.com`, where every Converge Buy Button and pay link sends the buyer, for a company Agentcard turned Converge on for. That page posts the card to its own origin, `https://www.convergepay.com/hosted-payments/service/payment/hpp/process`, and the SDK recognized the process call only on `api.convergepay.com` and the demo host, so the agent's placeholder card reached Converge. `www.convergepay.com` is a production host: a test row never pays there. A Buy Button's page also sends `recurring` as `"false"` in every request; that one-time sale now pauses where it was refused as `converge_recurring_refused`, and `"true"` or any other value is still refused. A page with its CAPTCHA on still stops with `converge_captcha_refused`, and every Buy Button page read so far has one.
39
+ - Pause an Elavon Converge payment page opened on `www.convergepay.com`, where every Converge Buy Button and pay link sends the buyer, for a company Agentcard turned Converge on for. That page posts the card to its own origin, `https://www.convergepay.com/hosted-payments/service/payment/hpp/process`, and the SDK recognized the process call only on `api.convergepay.com` and the demo host, so the agent's placeholder card reached Converge. `www.convergepay.com` is a production host: a test row never pays there. A Buy Button's page also sends `recurring` as `"false"` in every request; that one-time sale now pauses where it was refused as `converge_recurring_refused`, and `"true"` or any other value is still refused.
40
+
41
+ - Pause an Elavon Converge payment page with its CAPTCHA on once a person solved it, for a company Agentcard turned Converge on for. Every Buy Button and pay link page read so far shows a reCAPTCHA and sends its card request only after it is solved, with the answer in `captchaToken`, and earlier releases refused every such request as `converge_captcha_refused` before any create. The request now pauses like any other, and the cardholder's device sends the answer to Converge unchanged beside the card. A person solves the CAPTCHA in the browser, never your agent, and Google takes the answer once, for two minutes after the solve, so the cardholder approves inside that window. A `captchaToken` that is empty, not text, longer than 4,096 characters, or holds a character other than `A-Z`, `a-z`, `0-9`, `_` and `-` is still refused as `converge_captcha_refused`. A payment also needs the matching API and Vault releases.
26
42
 
27
43
  - Answer a prepared Bambora checkout inside Custom Checkout's 5 seconds. The card frame fails its tokenization request 5 seconds after Pay, and 0.23.9 read a prepared Bambora approval every 2 seconds, so a cardholder's answer that missed the first read waited for the read 4 seconds after the bind, at or past the frame's timer. The SDK now reads it every 100 ms for its first 10 seconds, and the API's bind names that interval on the row, so no read is refused with `slow_down`. When the frame gives up first anyway, the approval now outlives it, 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 stays with that card frame: the SDK refuses the same request from any other frame, and the page removing the card frame stops the attachment, as it does while the request is still paused, so a replacement frame never receives the token. 0.23.9 ended that checkout `outcome_unknown` and needed a new attachment and a second approval. Every other prepared checkout still answers only the request it was bound to. A payment also needs the matching API release.
28
44
 
@@ -80,6 +96,8 @@
80
96
 
81
97
  - Read the card number on the D-EDGE card frame the way the frame groups each card network: 4-6-5 for an American Express number, 4-6-4 for a Diners Club number starting 36 or 300 to 305, and groups of four for any other. The SDK's copy of D-EDGE's rules read a grouped number only in groups of four, so an American Express or Diners Club placeholder as the frame groups it was not D-EDGE's reviewed card request, and the same number in groups of four, which the frame never posts, was. D-EDGE stays in `observe`, so its card post still gets the page that says Agentcard has not turned it on.
82
98
 
99
+ - Pay a Paddle checkout that sends the card to Paddle's card vault, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it has confirmed that a card stored for one Paddle seller cannot pay another. On a checkout whose card method names VGS, Paddle's checkout frame on `buy.paddle.com` posts the card to `https://vault.paddle.com/cards` (`https://sandbox-vault.paddle.com/cards` in test) and sends that request once more when it gets no answer within 10 seconds, too soon for an approval after Pay. So the card is paid only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the card whose `placeholderCard()` facts you typed into Paddle's form; without one, `prepare()` fails with `card_required`, and the approval is locked to that card. The sync now also asks for `capabilities=paddle_vgs_cards`. Once the cardholder approves, the frame's request pauses, the cardholder's device sends the card to Paddle's vault itself, and the frame receives the stored card's id and nothing else; the frame's second request after its 10-second timeout, the same body under the same bearer, gets the same answer, and any other request is refused. Paddle's request names no amount, so the cardholder approves the `amount` and `currency` you pass. The lane pays Visa, Mastercard and American Express cards: `prepare()` fails with `paddle_vgs_card_unsupported` for a card on another network, and the create fails with `paddle_vgs_card_mismatch` when the expiry or placeholder you typed is not the card's. On Paddle's live vault, Agentcard pays only a seller it has reviewed, at the checkout page it reviewed (Rectangle Pro at `https://rectangleapp.com` today), and the approval names that seller and says it can keep the card on file, because the card stays stored in Paddle's vault. Paddle's own `/pay` call, which follows the stored card's id, still carries the placeholder's first six and last four digits that you typed; whether Paddle accepts them beside the real card is not proven yet. A Paddle checkout that sends the card through TokenEx or Spreedly is not paid. No live payment has gone through this route yet. A payment also needs the matching API and Vault releases.
100
+
83
101
  ## 0.23.0
84
102
 
85
103
  ### Upgrading from 0.22.0
package/PREFLIGHT.md CHANGED
@@ -205,6 +205,8 @@ Shiji STS (`shiji_sts`), the card leg of MyCheck Wallet V3 at La Quinta by Wyndh
205
205
 
206
206
  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`.
207
207
 
208
+ 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.
209
+
208
210
  The bundled direct SDK profile declares processor support for `one_time` purchases. The catalog carries each processor's request limitations and known exclusions. A detection rule does not cover every product or regional variant sold under the processor's name. For example, Moneris Hosted Tokenization and Moneris Checkout use different payment paths; the implemented adapter covers Hosted Tokenization.
209
211
 
210
212
  The catalog also carries reviewed hosted, regional, and versioned variants observed on live checkouts: Adyen secured fields on the regional live hosts and any release, the Square card element frame, dLocal Smart Fields releases, Mercado Pago secure fields and the guest Checkout Pro card form, and the Rapyd hosted checkout page. A variant the implemented adapter does not admit, such as the Tranzila terminal-root page, the Authorize.Net hosted payment form, or a Worldpay hosted payment page, identifies its processor and returns `unknown` with `processor_variant_unverified`. A new fingerprint never declares payment support by itself.
package/README.md CHANGED
@@ -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 |
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
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 |
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. The session id is the request's only credential, so event URLs and error messages show it as `{session}`. 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) |
@@ -278,6 +278,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
278
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
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
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" |
281
+ | 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` still carries the placeholder's first six and last four digits beside the stored card's id, and whether Paddle accepts them is not proven yet. A Paddle checkout that sends the card through TokenEx or Spreedly is not supported, and no live payment has gone through this route yet |
281
282
 
282
283
  The recognizer list, and the status of each reviewed Adyen merchant profile on
283
284
  your client, are fetched from the API at runtime (`agentcard.checkout.syncRegistry()`),
@@ -707,8 +708,11 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
707
708
  callback URLs); all three are terminal for that page. A Tranzila form is
708
709
  also refused with `duplicate_field` (a field posted twice with different
709
710
  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`), terminal
711
- the same way. An Authorize.Net DPM or SIM form adds more reasons
711
+ Tranzila's server reads as another name, such as `cred.type`, or an
712
+ `altcha` field holding anything but the ALTCHA proof of work Tranzila's
713
+ own page writes there before it submits), terminal the same way. That
714
+ proof is the only filled-in challenge field the cardholder's device sends
715
+ with the card. An Authorize.Net DPM or SIM form adds more reasons
712
716
  (`duplicate_field`, `not_a_card_payment`, `relay_response_off`,
713
717
  `form_step`, `merchant_credential`, `test_request_on_live_host`,
714
718
  `malformed_field`), terminal the same way,
@@ -1063,9 +1067,9 @@ and the reason becomes `stripe_payment_continued`. One confirm continues per
1063
1067
  approval; every other follow-up, including an unrelated intent, a changed
1064
1068
  amount and a retry, stays blocked.
1065
1069
 
1066
- A card save can follow a `/v1/payment_methods` approval the same way, once
1067
- Agentcard turns card-save continuations on for your company: the page's
1068
- SetupIntent confirm pays with the approved payment method. A Payment Element
1070
+ A card save can follow a `/v1/payment_methods` approval the same way, for
1071
+ every company: the page's SetupIntent confirm pays with the approved payment
1072
+ method. A Payment Element
1069
1073
  page that creates the method itself (`paymentMethodCreation: 'manual'`, then
1070
1074
  `confirmSetup({ elements, confirmParams: { payment_method } })`) confirms with
1071
1075
  that method and no card, and the SDK continues the confirm untouched once the
@@ -1081,9 +1085,10 @@ emits `stripe_setup_continued` with
1081
1085
  `{ authorizationId, setupIntentId, rewritten }`, where `rewritten` is true
1082
1086
  when the SDK wrote the approved payment method in place of a card the confirm
1083
1087
  carried, and the reason becomes `stripe_setup_continued`; the merchant then
1084
- keeps the card for later charges. Until your company is turned on, the API
1085
- refuses the check (`continuation_invalid`) and the confirm is aborted, as
1086
- before. Either kind of intent uses up the approval's one continuation.
1088
+ keeps the card for later charges. Refused, the confirm is aborted, and the
1089
+ error's code says why: `amount_mismatch` for an approval that named an
1090
+ amount, for example. Either kind of intent uses up the approval's one
1091
+ continuation.
1087
1092
 
1088
1093
  Unrecognized merchant-server endpoints remain outside this guard unless listed
1089
1094
  in `paymentEndpoints`; this is not a guarantee against a merchant charging a
@@ -1279,8 +1284,15 @@ Lost authorization polling, local approval timeouts, or interrupted browser
1279
1284
  handoffs produce `outcome_unknown` and block automatic retry. The thrown
1280
1285
  `PaymentOutcomeUnknownError` carries `authorizationId` when creation was
1281
1286
  acknowledged. `checkout.cancel()` stops the local attachment and polling; it
1282
- does not revoke a pending approval link or undo a processor payment. Cancellation
1283
- after an attempt starts is therefore unknown until reconciled. A cancelled
1287
+ does not revoke a pending approval link or undo a processor payment. Cancelling
1288
+ an attempt still under way (a card request in flight, or an authorization not
1289
+ yet settled) therefore reads `outcome_unknown` with reason `local_cancel` until
1290
+ reconciled. A checkout that already ended keeps its state and reason:
1291
+ `completed`, `declined`, `failed`, `timed_out`, `unsupported`, `cancelled` and
1292
+ `outcome_unknown` read the same after `cancel()` once no card request is in
1293
+ flight, or once the request in flight has its own outcome recorded, as when
1294
+ your code cancels from `onStateChange` or the `failed` event. A cleanup that
1295
+ cancels never hides a processor refusal or a confirmed payment. A cancelled
1284
1296
  attachment cannot restart.
1285
1297
 
1286
1298
  ### Adyen merchants that take the card on their own server
@@ -1931,16 +1943,26 @@ const checkout = await attachToPlaywright(page, {
1931
1943
  - A Buy Button's page sends `recurring` as `"false"` in every request unless the
1932
1944
  buyer ticked the button's recurring box. That request is a one-time sale and
1933
1945
  pauses like any other; `"true"`, or any other value, is refused as recurring.
1946
+ - A page with its CAPTCHA on, as every Buy Button page read so far has it,
1947
+ sends its card request only after the CAPTCHA is solved, with the answer in
1948
+ `captchaToken`. A person solves it in the browser, never your agent, so a
1949
+ browser that solves CAPTCHAs itself, such as Kernel with stealth on, must not
1950
+ run a Converge page. The SDK
1951
+ pauses that request like any other, and the cardholder's device sends the
1952
+ answer to Converge unchanged beside the card. Google takes the answer once,
1953
+ for two minutes after the solve, so the cardholder approves inside that
1954
+ window.
1934
1955
  - Before any prompt, the SDK refuses every other shape on that URL with
1935
1956
  payment-core's own code (`ConvergeRefusalReason`, listed in
1936
1957
  `CONVERGE_REFUSAL_REASONS`), the one the API and the cardholder's device
1937
1958
  would answer: a wallet, the debit, gift card and push-to-card tabs, a
1938
- follow-up after a 3-D Secure, Pay Defense or surcharge step, a CAPTCHA, a
1939
- public-sector service fee, recurring or installment fields, card saving, a
1940
- tip or cash back, another currency, no amount, no page session, and anything
1941
- that is not the page's own request. The page's request is aborted, nothing is
1942
- created, and the attachment is not held, so the page's next payment is judged
1943
- afresh. The table below gives each code's meaning and the next step.
1959
+ follow-up after a 3-D Secure, Pay Defense or surcharge step, a CAPTCHA
1960
+ answer Google never issued, a public-sector service fee, recurring or
1961
+ installment fields, card saving, a tip or cash back, another currency, no
1962
+ amount, no page session, and anything that is not the page's own request.
1963
+ The page's request is aborted, nothing is created, and the attachment is not
1964
+ held, so the page's next payment is judged afresh. The table below gives each
1965
+ code's meaning and the next step.
1944
1966
  - A terminal with 3-D Secure 2, Pay Defense or a surcharge answers the first
1945
1967
  request with a step instead of a result. The Vault never hands that answer to
1946
1968
  the page: the authorization ends declined as a processor refusal with
@@ -1983,7 +2005,7 @@ of the same code. `converge_step_unsupported`, `converge_declined` and
1983
2005
  | `converge_gift_card_refused` | The payment came from the page's gift card tab. | Pay with the credit card form. |
1984
2006
  | `converge_push_to_card_refused` | The payment would send money to the card instead of charging it. | None: an Agentcard card never receives money. |
1985
2007
  | `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. |
1986
- | `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. |
2008
+ | `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. |
1987
2009
  | `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. |
1988
2010
  | `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. |
1989
2011
  | `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. |
@@ -1,6 +1,6 @@
1
1
  // @ts-nocheck
2
2
  // Generated from @agent-cards/payment-core. Do not edit.
3
- // artifact-sha256: 6a0cf6104aac41a93fb4788532c63075d6831419127b76b87228dd7839c2a106
3
+ // artifact-sha256: c8e0308b7790c3577c56ec0428577a42c308ccdbeff3eb041c6103d9e9b338a8
4
4
  // src/card-number-key.js
5
5
  var CARD_NUMBER_KEY = /^(number|card_number|cardnumber|cardNum|card_num|pan)$/i;
6
6
  // src/json-path.js
@@ -75,6 +75,7 @@ var ONE_TIME_RECURRING_FLAG = "false";
75
75
  var CARD_SAVING_FLAGS = /* @__PURE__ */ Object.freeze(["ssl_get_token", "ssl_add_token"]);
76
76
  var CREDENTIAL_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_account_id", "ssl_merchant_id", "ssl_user_id", "ssl_pin", "ssl_vendor_id"]);
77
77
  var AMOUNT_ADD_ON_FIELDS = /* @__PURE__ */ Object.freeze(["ssl_tip_amount", "ssl_cashback_amount"]);
78
+ var SOLVED_CAPTCHA = /^[A-Za-z0-9_-]{1,4096}$/;
78
79
  var CARD_NUMBER = /^\d{12,19}$/;
79
80
  var EXPIRY_MMYY = /^(0[1-9]|1[0-2])\d{2}$/;
80
81
  var CVC = /^\d{3,4}$/;
@@ -130,7 +131,7 @@ function convergeBodyVerdict(body) {
130
131
  return refuse("converge_credential_refused");
131
132
  if (WALLET_FIELDS.some((key) => owns(data, key)))
132
133
  return refuse("converge_wallet_refused");
133
- if (owns(parsed, "captchaToken"))
134
+ if (owns(parsed, "captchaToken") && !(typeof parsed.captchaToken === "string" && SOLVED_CAPTCHA.test(parsed.captchaToken)))
134
135
  return refuse("converge_captcha_refused");
135
136
  if (SERVICE_FEE_FIELDS.some((key) => owns(data, key)))
136
137
  return refuse("converge_service_fee_refused");
@@ -339,8 +340,79 @@ function amadeusBodyKind(body) {
339
340
  return record2(value.data) && holdsPan(value.data.mopdata) ? "card" : "other";
340
341
  return "other";
341
342
  }
343
+ // src/paddle-vgs.js
344
+ var PADDLE_VGS_PSP = "paddle_vgs";
345
+ var PADDLE_VGS_CARDS_URLS = /* @__PURE__ */ Object.freeze({
346
+ production: "https://vault.paddle.com/cards",
347
+ sandbox: "https://sandbox-vault.paddle.com/cards"
348
+ });
349
+ var BODY_LIMIT2 = 4096;
350
+ var ROOT_KEYS2 = /* @__PURE__ */ Object.freeze(["data"]);
351
+ var DATA_KEYS = /* @__PURE__ */ Object.freeze(["type", "attributes"]);
352
+ var ATTRIBUTE_KEYS = /* @__PURE__ */ Object.freeze(["pan", "cvc", "exp_month", "exp_year", "cardholder"]);
353
+ var CARD_NUMBER4 = /^[0-9]{12,19}$/;
354
+ var SECURITY_CODE2 = /^[0-9]{3,4}$/;
355
+ var NAME_LIMIT = 256;
356
+ var CONTROL = /[\u0000-\u001f\u007f]/;
357
+ var cardLikeName = (name) => (name.match(/[0-9]/g) ?? []).length >= 12;
358
+ var record3 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
359
+ var sameKeys = (value, keys) => {
360
+ const own = Object.keys(value);
361
+ return own.length === keys.length && own.every((key, at) => key === keys[at]);
362
+ };
363
+ var refuse4 = (code) => Object.freeze({ verdict: "refuse", code });
364
+ var CARD4 = /* @__PURE__ */ Object.freeze({ verdict: "card" });
365
+ function paddleVgsEnvironment(url) {
366
+ if (url === PADDLE_VGS_CARDS_URLS.production)
367
+ return "production";
368
+ if (url === PADDLE_VGS_CARDS_URLS.sandbox)
369
+ return "sandbox";
370
+ return null;
371
+ }
372
+ function isPaddleVgsCardsUrl(url) {
373
+ return paddleVgsEnvironment(url) !== null;
374
+ }
375
+ function readBody2(body) {
376
+ if (typeof body !== "string" || body === "" || body.length > BODY_LIMIT2)
377
+ return null;
378
+ let value;
379
+ try {
380
+ value = JSON.parse(body);
381
+ if (!record3(value) || JSON.stringify(value) !== body)
382
+ return null;
383
+ }
384
+ catch {
385
+ return null;
386
+ }
387
+ return value;
388
+ }
389
+ function paddleVgsBodyVerdict(body) {
390
+ const value = readBody2(body);
391
+ if (value === null || !sameKeys(value, ROOT_KEYS2) || !record3(value.data) || !sameKeys(value.data, DATA_KEYS) || value.data.type !== "cards" || !record3(value.data.attributes) || !sameKeys(value.data.attributes, ATTRIBUTE_KEYS)) {
392
+ return refuse4("paddle_vgs_body_unsupported");
393
+ }
394
+ const card = value.data.attributes;
395
+ const holder = card.cardholder;
396
+ if (typeof card.pan !== "string" || !CARD_NUMBER4.test(card.pan) || typeof card.cvc !== "string" || !SECURITY_CODE2.test(card.cvc) || !Number.isInteger(card.exp_month) || card.exp_month < 1 || card.exp_month > 12 || !Number.isInteger(card.exp_year) || card.exp_year < 0 || card.exp_year > 99 || !record3(holder) || !sameKeys(holder, ["name"]) || typeof holder.name !== "string" || holder.name.length > NAME_LIMIT || CONTROL.test(holder.name) || cardLikeName(holder.name)) {
397
+ return refuse4("paddle_vgs_body_unsupported");
398
+ }
399
+ return CARD4;
400
+ }
401
+ var pin2 = (fields) => Object.freeze({
402
+ ...fields,
403
+ origins: Object.freeze([...fields.origins]),
404
+ sellerClaim: fields.sellerClaim ? Object.freeze({ ...fields.sellerClaim }) : null
405
+ });
406
+ var PADDLE_VGS_MERCHANT_PINS = /* @__PURE__ */ Object.freeze([
407
+ pin2({ id: "rectangle_pro", merchant: "Rectangle Pro", origins: ["https://rectangleapp.com"], sellerClaim: null })
408
+ ]);
342
409
  // src/card-fields.js
343
410
  function processorGate(recognizer, url, body) {
411
+ if (recognizer && recognizer.psp === PADDLE_VGS_PSP) {
412
+ if (url !== void 0 && !isPaddleVgsCardsUrl(url))
413
+ return { verdict: "refuse", code: "paddle_vgs_url_refused" };
414
+ return paddleVgsBodyVerdict(body);
415
+ }
344
416
  if (recognizer && recognizer.psp === GRAVY_PSP)
345
417
  return gravyBodyVerdict(body);
346
418
  if (recognizer && recognizer.psp === MONERIS_PSP) {
package/dist/cdp.js CHANGED
@@ -10,6 +10,7 @@ import { ACI_CHECKOUT_REUSED, AciGate, playwrightReplacesNavigationBody } from '
10
10
  import { EprotectGate } from './eprotect.js';
11
11
  import { dispatchPreClaim, eventUrl } from './pre-claim.js';
12
12
  import { GMO_TOKEN_V1_PATTERNS, gmoRequestShop, isGmoTokenV1Request, recognizesGmo } from './gmo.js';
13
+ import { isPaddleVgsCardsUrl } from './paddle-vgs.generated.js';
13
14
  import { DatatransRepeats } from './datatrans.js';
14
15
  import { SHIJI_STS_COMPANION_WINDOW_MS, ShijiStsCompanions, ShijiStsError, isShijiStsHost, shijiStsTokenContinuation, } from './shiji-sts.js';
15
16
  import { isArmedEkashuUrl } from './ekashu.js';
@@ -30,6 +31,7 @@ import { CARD_ENDPOINT_REFUSED_STEP_ANSWER, CARD_ENDPOINT_WITHHELD_ANSWER, CardE
30
31
  import { CardEndpointFactsWatch } from './card-endpoint-facts.js';
31
32
  import { PAYRAILS_GUARD_PATTERNS, PAYRAILS_INIT_BODY_LIMIT, PayrailsSessionCapture, isPayrailsHost, payrailsInitCandidate, recognizesPayrails, } from './payrails.js';
32
33
  import { cardEndpointStoppedPage, refusedNavigationOutcome } from './card-endpoint-page.js';
34
+ import { WorldlineMyCheckoutProducts } from './worldline-mycheckout.js';
33
35
  /**
34
36
  * How an adapter fails a paused request it will not let through: as a network error for a
35
37
  * CyberSource Flex request (refusedRequestFailure, so the Microform field reports it to the page)
@@ -1240,14 +1242,32 @@ function bodyShape(body) {
1240
1242
  * the one the Flex gate read from the field iframe (cybersource-flex.ts: microform type, fields,
1241
1243
  * the capture context's key id and the context itself), never the ciphertext.
1242
1244
  */
1243
- function requestIdentity(opts, url, method, body, documentUrl, pageAmount, flexShape) {
1245
+ function requestIdentity(opts, url, method, body, documentUrl, pageAmount, flexShape, headers) {
1244
1246
  const shaped = flexShape !== undefined ? { shape: flexShape, namesAmount: false } : bodyShape(body);
1245
1247
  // A GMO token is minted for the one shop its request names, and the body's shape leaves that value
1246
1248
  // out, so the shop joins the identity: a retry that names another shop needs its own approval.
1247
1249
  const shop = gmoRequestShop(url, body);
1248
- return { url, method, documentUrl, pageAmount, body: shop === undefined ? shaped.shape : `${shaped.shape};gmo_shop:${JSON.stringify(shop)}`,
1250
+ const identity = shop === undefined ? shaped.shape : `${shaped.shape};gmo_shop:${JSON.stringify(shop)}`;
1251
+ return { url, method, documentUrl, pageAmount, body: `${identity}${paddleVgsRetryKey(url, body, headers)}`,
1249
1252
  amountEvidence: opts.amount != null || pageAmount !== undefined || shaped.namesAmount };
1250
1253
  }
1254
+ /**
1255
+ * What a Paddle VGS card request's retry must repeat byte for byte, beyond its shape: Paddle's frame
1256
+ * sends its second request with the same body under the same bearer, and the bearer names the
1257
+ * checkout the card is stored for. So the whole body and the bearer and content type join the
1258
+ * identity: a request under another checkout's bearer, or with any value changed, never receives
1259
+ * the stored card's id the cardholder approved for the first. Empty for every other request.
1260
+ */
1261
+ function paddleVgsRetryKey(url, body, headers) {
1262
+ if (!isPaddleVgsCardsUrl(url))
1263
+ return '';
1264
+ const record = headers !== null && typeof headers === 'object' ? headers : {};
1265
+ const one = (name) => {
1266
+ const found = Object.keys(record).filter((key) => key.toLowerCase() === name);
1267
+ return found.length === 1 && typeof record[found[0]] === 'string' ? record[found[0]] : null;
1268
+ };
1269
+ return `;paddle_vgs:${JSON.stringify([one('authorization'), one('content-type'), body])}`;
1270
+ }
1251
1271
  /** A CDP frame or request URL with its fragment, which CDP reports apart (urlFragment). */
1252
1272
  function withFragment(value) {
1253
1273
  if (typeof value?.url !== 'string')
@@ -1290,7 +1310,9 @@ function sameCheckoutRequest(first, retry) {
1290
1310
  * answer once the page has moved on), a prepared checkout (single use, bound
1291
1311
  * to one native request by design) other than a Bambora token create, whose
1292
1312
  * card frame gives up 5 seconds after sending it and whose page asks again
1293
- * with the same body (preparedApprovalAnswersRetry), from that same card frame
1313
+ * with the same body (preparedApprovalAnswersRetry), or a Paddle VGS card
1314
+ * request, whose frame sends its second request byte for byte under the same
1315
+ * bearer (paddleVgsRetryKey), from that same card frame
1294
1316
  * only (sameFrame), a native Stripe Checkout step, a
1295
1317
  * Mercado Pago request whose claim is bound to one document read, a Juspay
1296
1318
  * /txns (the device pays that request's own order; a page that clicks Pay
@@ -1988,6 +2010,8 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
1988
2010
  let lastSubmitted = null;
1989
2011
  // The approved Datatrans answers, per frameId: the repeat rule (datatrans.ts).
1990
2012
  const datatransRepeats = new DatatransRepeats();
2013
+ // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
2014
+ const worldlineProducts = new WorldlineMyCheckoutProducts();
1991
2015
  // The Shiji STS getCvvId companion of the getTokenId this attachment last continued (shiji-sts.ts).
1992
2016
  // A cancelled attachment drops it, and the device's CVV ciphertext with it, at once: no getCvvId
1993
2017
  // can use it any more (pre-claim.ts refuses every one once the lifecycle is aborted). The page,
@@ -2455,6 +2479,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2455
2479
  frame: typeof frameId === 'string' && frameId ? frameId : undefined,
2456
2480
  payrails,
2457
2481
  shijiSts: { companions: shijiCompanions, page: () => cdpShijiPage(sessionId, frameId) },
2482
+ worldlineMyCheckout: worldlineProducts,
2458
2483
  });
2459
2484
  const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
2460
2485
  if (preClaim.kind !== 'pause')
@@ -2520,7 +2545,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2520
2545
  if (awaitingApproval && activeRequest?.attempt.lost && !terminal && !setupStop.signal.aborted && !lifecycle.isCancelled()
2521
2546
  && typeof networkId === 'string' && networkId) {
2522
2547
  const retryBody = pausedBody(request);
2523
- const retry = retryBody === null ? null : requestIdentity(opts, request.url, request.method, retryBody, documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape);
2548
+ const retry = retryBody === null ? null : requestIdentity(opts, request.url, request.method, retryBody, documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape, request.headers);
2524
2549
  // A prepared approval is answered only in the card frame it was bound in: the same request from
2525
2550
  // another frame (a replacement for a removed one, or a second card frame) is refused below.
2526
2551
  if (retry && activeRequest?.attempt.lost && sameCheckoutRequest(activeRequest.identity, retry)
@@ -2643,7 +2668,7 @@ export async function attachToCdp(cdp, pageSessionId, opts) {
2643
2668
  }
2644
2669
  attempt.bind({ requestId, sessionId, request });
2645
2670
  activeRequest = { attempt, requestId, networkId, sessionId, frameId, request,
2646
- identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape),
2671
+ identity: requestIdentity(opts, request.url, request.method, body, undefined, undefined, flexPause?.shape, request.headers),
2647
2672
  preparedFrame: preparation ? { sessionId, frameId } : undefined };
2648
2673
  // An ACI card-number request: the page's CVC and expiry requests for its checkout are held
2649
2674
  // until this approval ends (see AciGate). One whose checkout already has a card-number
@@ -3318,6 +3343,8 @@ export async function attachToPlaywright(page, opts) {
3318
3343
  let lastSubmitted = null;
3319
3344
  // The approved Datatrans answers, per Frame: the repeat rule (datatrans.ts).
3320
3345
  const datatransRepeats = new DatatransRepeats();
3346
+ // The card product each Worldline MyCheckout hosted checkout chose (worldline-mycheckout.ts).
3347
+ const worldlineProducts = new WorldlineMyCheckoutProducts();
3321
3348
  const frameOf = (request) => { try {
3322
3349
  return request.frame?.() ?? undefined;
3323
3350
  }
@@ -3457,6 +3484,7 @@ export async function attachToPlaywright(page, opts) {
3457
3484
  frame: frameOf(request),
3458
3485
  payrails,
3459
3486
  shijiSts: { companions: shijiCompanions, page: async () => (await playwrightShijiDocumentPage(request)) ?? `pw:none:${++playwrightShijiSeq}` },
3487
+ worldlineMyCheckout: worldlineProducts,
3460
3488
  });
3461
3489
  const preClaim = preClaimResult instanceof Promise ? await preClaimResult : preClaimResult;
3462
3490
  if (preClaim.kind !== 'pause')
@@ -3508,7 +3536,7 @@ export async function attachToPlaywright(page, opts) {
3508
3536
  // for, after its own request timed out: bind it to that approval (see
3509
3537
  // merchantAttempt and the same check in attachToCdp).
3510
3538
  if (awaitingApproval && activeRequest?.attempt.lost && !terminal && !setupStop.signal.aborted && !lifecycle.isCancelled()) {
3511
- const retry = requestIdentity(opts, request.url(), request.method(), request.postData() ?? '', documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape);
3539
+ const retry = requestIdentity(opts, request.url(), request.method(), request.postData() ?? '', documentKey(await documentUrlOf(readDocumentUrl, lifecycle.abort.signal)), await pageAmountOf(opts), flexPause?.shape, request.headers());
3512
3540
  const retryFrames = [];
3513
3541
  try {
3514
3542
  for (let frame = request.frame?.(); frame; frame = frame.parentFrame?.())
@@ -3631,7 +3659,7 @@ export async function attachToPlaywright(page, opts) {
3631
3659
  const payToInterceptMs = readPayToInterceptMs(opts.payClickedAt, approvalStartedAt);
3632
3660
  attempt.bind({ route, request, frames });
3633
3661
  activeRequest = { request, frames, attempt,
3634
- identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape),
3662
+ identity: requestIdentity(opts, request.url(), request.method(), body, undefined, undefined, flexPause?.shape, request.headers()),
3635
3663
  preparedFrames: preparation ? [...frames] : undefined };
3636
3664
  // An ACI card-number request: see the same step in attachToCdp.
3637
3665
  if (!aci.begin(request.url(), request.method(), body, preparation?.merchantOrigin)) {
package/dist/client.d.ts CHANGED
@@ -204,6 +204,14 @@ export declare const SUPPORTED_REGISTRY_FEATURES: readonly string[];
204
204
  * the same terms: only to a build that lists it, for a company Agentcard turned
205
205
  * Moneris on for. A gated recognizer never sits in BUILTIN_REGISTRY, so it is paused
206
206
  * only after a sync.
207
+ *
208
+ * `paddle_vgs_cards`: the card request Paddle's checkout frame sends to its VGS card
209
+ * vault (vault.paddle.com/cards) on a checkout that names VGS, only through a prepared
210
+ * checkout: the frame aborts the request 10 seconds after Pay and sends it once more,
211
+ * and this build answers that second request from the same approval. The cardholder's
212
+ * device sends the card itself and the frame gets back the stored card's id alone.
213
+ * Served on the same terms: only to a build that lists it, for a company Agentcard
214
+ * turned Paddle on for.
207
215
  */
208
216
  export declare const SUPPORTED_CAPABILITIES: readonly string[];
209
217
  /**
@@ -739,6 +747,12 @@ export interface PlaceholderCard {
739
747
  /** The stored card's expiry year, four digits ("2031"). */
740
748
  readonly expiryYear: string;
741
749
  }
750
+ /**
751
+ * The placeholder number for a network (lowercase, as GET /api/v2/vault_cards names it) on a
752
+ * processor's page: the processor's own number for that network when `overrides` names one, else
753
+ * the network's default, else undefined for a network with no placeholder.
754
+ */
755
+ export declare function placeholderNumber(brand: string, psp?: string, overrides?: Readonly<Record<string, Readonly<Record<string, string>>>>): string | undefined;
742
756
  /**
743
757
  * The recognizer says this processor is preparation-required, and no preparation
744
758
  * was passed. Its card may be sent only after the cardholder approved a
@@ -1082,8 +1096,13 @@ export declare class CheckoutAuthError extends Error {
1082
1096
  * so it reads {txId}, as does any other id-shaped segment there.
1083
1097
  * On the Monri and WSPay hosts the secret is a path segment, so it is replaced
1084
1098
  * with `{client_secret}`: https://proxy.wspay.biz/v2/payment/{client_secret}/confirm.
1099
+ * On Gravy's hosts the checkout session id is the credential, so it reads
1100
+ * `{session}`: https://api.<merchant>.gr4vy.app/checkout/sessions/{session}/fields.
1101
+ * On Rapyd's hosts the hosted page's id reads `{token}`: https://api.rapyd.net/v1/checkout/{token}.
1102
+ * On api.stripe.com a Checkout Session id reads `{session}`:
1103
+ * https://api.stripe.com/v1/payment_pages/{session}/confirm.
1085
1104
  * A string that is not a URL keeps the part before any `?` or `#`, with the
1086
- * same segment masked.
1105
+ * same segments masked.
1087
1106
  */
1088
1107
  export declare function redactUrl(raw: string): string;
1089
1108
  export interface VaultClientOptions {
@@ -1336,12 +1355,16 @@ export declare class VaultClient {
1336
1355
  * preselects that card: without a pin the page preselects its own choice (its recommended
1337
1356
  * card, or the first card the cardholder's device can open), which can be another card. After
1338
1357
  * prepare(), pass the prepared checkout's `cardId`, the card the cardholder already approved.
1339
- * The facts hold only while the cardholder pays with that card. Throws PlaceholderCardError
1340
- * when there is no such card, its network has no placeholder, or it carries no expiry.
1358
+ * The facts hold only while the cardholder pays with that card. `psp` names the processor
1359
+ * whose card form the agent types into (the recognizer's name, `worldline_mycheckout`): a
1360
+ * processor that needs its own number for the card's network gets it, and every other gets the
1361
+ * network's default. Throws PlaceholderCardError when there is no such card, its network has no
1362
+ * placeholder, or it carries no expiry.
1341
1363
  */
1342
1364
  placeholderCard(input: {
1343
1365
  user: string;
1344
1366
  cardId?: string;
1367
+ psp?: string;
1345
1368
  signal?: AbortSignal;
1346
1369
  }): Promise<PlaceholderCard>;
1347
1370
  /** Wait for real device approval before the caller starts native tokenization. */
@@ -1418,11 +1441,12 @@ export declare class VaultClient {
1418
1441
  * approval produced: a SetupIntent confirm that carries no card and pays with
1419
1442
  * exactly the approved payment method (the page's own, or written by the
1420
1443
  * adapters in place of a card the confirm carries again).
1421
- * The API allows a card save only for a company Agentcard turned it on for,
1422
- * for an approval given on the Vault page, not by voice, with no amount, and
1423
- * a SetupIntent Stripe reports still confirmable on the same Stripe account;
1424
- * any other answer rejects with a CheckoutApiError whose code says why (for
1425
- * example `continuation_invalid` where it is off, `amount_mismatch`). Nothing
1444
+ * The API allows a card save for any company, for an approval given on the
1445
+ * Vault page, not by voice, with no amount, and a SetupIntent Stripe reports
1446
+ * still confirmable on the same Stripe account; any other answer rejects with
1447
+ * a CheckoutApiError whose code says why (for example `amount_mismatch`, or
1448
+ * `continuation_invalid` for a confirm that is not a card-free SetupIntent
1449
+ * confirm). Nothing
1426
1450
  * is charged: the adapters continue the page's request once this resolves.
1427
1451
  */
1428
1452
  checkStripeSetupContinuation(authorizationId: string, request: {