@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 +20 -2
- package/PREFLIGHT.md +2 -0
- package/README.md +41 -19
- package/dist/card-fields.generated.js +74 -2
- package/dist/cdp.js +35 -7
- package/dist/client.d.ts +32 -8
- package/dist/client.js +164 -19
- package/dist/elavon-converge.d.ts +4 -1
- package/dist/elavon-converge.generated.js +3 -2
- package/dist/elavon-converge.js +4 -1
- package/dist/lifecycle.d.ts +9 -1
- package/dist/lifecycle.js +34 -0
- package/dist/paddle-vgs.generated.d.ts +50 -0
- package/dist/paddle-vgs.generated.js +289 -0
- package/dist/payrails.generated.js +8 -12
- package/dist/pre-claim.d.ts +7 -0
- package/dist/pre-claim.js +47 -7
- package/dist/prepared-processor.d.ts +12 -4
- package/dist/prepared-processor.js +17 -5
- package/dist/primer.generated.js +90 -18
- package/dist/stripe-checkout.generated.js +59 -2
- package/dist/worldline-mycheckout.d.ts +27 -0
- package/dist/worldline-mycheckout.generated.d.ts +29 -0
- package/dist/worldline-mycheckout.generated.js +162 -0
- package/dist/worldline-mycheckout.js +93 -0
- package/package.json +2 -2
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`.
|
|
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.
|
|
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
|
|
711
|
-
|
|
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,
|
|
1067
|
-
|
|
1068
|
-
|
|
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.
|
|
1085
|
-
|
|
1086
|
-
|
|
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.
|
|
1283
|
-
|
|
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
|
|
1939
|
-
public-sector service fee, recurring or
|
|
1940
|
-
tip or cash back, another currency, no
|
|
1941
|
-
that is not the page's own request.
|
|
1942
|
-
|
|
1943
|
-
afresh. The table below gives each
|
|
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
|
|
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:
|
|
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
|
-
|
|
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),
|
|
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
|
|
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.
|
|
1340
|
-
*
|
|
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
|
|
1422
|
-
*
|
|
1423
|
-
*
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
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: {
|