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