@agent-cards/sdk 0.23.8 → 0.23.10
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 +42 -0
- package/PREFLIGHT.md +7 -5
- package/README.md +181 -92
- 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 +163 -9
- package/dist/cdp.js +79 -34
- package/dist/client.d.ts +61 -31
- package/dist/client.js +99 -42
- 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/cybersource-secure-acceptance.generated.js +4 -3
- package/dist/datatrans.generated.js +8 -5
- package/dist/elavon-converge.d.ts +1 -1
- package/dist/elavon-converge.generated.js +7 -4
- package/dist/elavon-converge.js +4 -3
- 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/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.js +51 -29
- 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 +200 -33
- 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 +146 -49
- package/dist/verifone.generated.js +192 -17
- package/dist/verifone.js +1 -1
- 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
|
|
|
@@ -261,8 +261,8 @@ Coverage is specific to the processor request format, merchant setup, browser tr
|
|
|
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
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 |
|
|
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,20 @@ 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`), terminal
|
|
711
|
+
the same way. An Authorize.Net DPM or SIM form adds more reasons
|
|
712
|
+
(`duplicate_field`, `not_a_card_payment`, `relay_response_off`,
|
|
713
|
+
`form_step`, `merchant_credential`, `test_request_on_live_host`,
|
|
714
|
+
`malformed_field`), terminal the same way,
|
|
695
715
|
and `stale_fingerprint`, which is not: `CheckoutApiError.isStaleFingerprint`
|
|
696
716
|
is true, the adapters quiet the page for `approvalCooldownMs`, and the form
|
|
697
717
|
the page renders after a reload is judged afresh. `duplicate_submission` (409,
|
|
@@ -834,9 +854,10 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
|
|
|
834
854
|
`invalid_wspay_request`, `wspay_keep_on_file_unsupported`, `amount_required`,
|
|
835
855
|
`sandbox_host_on_live_row` or `live_host_on_sandbox_row` (400;
|
|
836
856
|
`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
|
|
857
|
+
only a live one), or `card_not_stored` (409, a card guarantee pinned to a
|
|
858
|
+
card a company added). The adapters abort the requests behind
|
|
859
|
+
`invalid_wspay_request` themselves, and `authorize()` refuses a missing
|
|
860
|
+
amount, both before any create.
|
|
840
861
|
- `PlaceholderCardError`: `placeholderCard()` found no such stored card
|
|
841
862
|
(`card_not_found`), no placeholder for its network (`brand_unsupported`), or
|
|
842
863
|
no expiry on it (`expiry_unknown`). Nothing was created.
|
|
@@ -1122,6 +1143,8 @@ await page.getByRole('button', { name: 'Pay', exact: true }).click();
|
|
|
1122
1143
|
|
|
1123
1144
|
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
1145
|
|
|
1146
|
+
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`.
|
|
1147
|
+
|
|
1125
1148
|
Prepare a Checkout.com checkout before the agent clicks Pay:
|
|
1126
1149
|
|
|
1127
1150
|
```ts
|
|
@@ -1145,7 +1168,7 @@ Prepare a Fiserv checkout before the agent clicks Pay:
|
|
|
1145
1168
|
await controller.prepare({ psp: 'fiserv', environment: 'sandbox', merchantProfile: 'agentcard_sandbox' });
|
|
1146
1169
|
```
|
|
1147
1170
|
|
|
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.
|
|
1171
|
+
`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
1172
|
|
|
1150
1173
|
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
1174
|
|
|
@@ -1162,14 +1185,24 @@ Initial coverage includes the hosted iframe's new-card request. Saved-card CVV u
|
|
|
1162
1185
|
Prepare an Amadeus Checkout card step before the agent clicks Pay:
|
|
1163
1186
|
|
|
1164
1187
|
```ts
|
|
1188
|
+
const card = await vault.placeholderCard({ user: 'your-user-id', cardId: 'vc_...' }); // what the agent types
|
|
1189
|
+
const controller = await attachToPlaywright(page, {
|
|
1190
|
+
vault, user: 'your-user-id', merchant: 'Air France', amount: 1100, currency: 'usd',
|
|
1191
|
+
cardId: card.cardId, // required: the stored card that pays, locked to the approval
|
|
1192
|
+
onApprovalUrl: deliverPrivatelyToCardholder,
|
|
1193
|
+
});
|
|
1194
|
+
// Once the payment step shows, type card.number and card.securityCode into Amadeus's card number and
|
|
1195
|
+
// security code fields, and card.expiryMonth and card.expiryYear into the airline's own expiry field.
|
|
1165
1196
|
await controller.prepare({ psp: 'amadeus_checkout', environment: 'production' }); // 'sandbox' for Amadeus's test platforms
|
|
1166
1197
|
```
|
|
1167
1198
|
|
|
1199
|
+
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`.
|
|
1200
|
+
|
|
1168
1201
|
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
1202
|
|
|
1170
1203
|
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
1204
|
|
|
1172
|
-
The SDK refuses some requests itself, before any row or prompt, and the controller reads `failed` with the reason:
|
|
1205
|
+
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
1206
|
|
|
1174
1207
|
Prepare Recurly before clicking the merchant's payment button:
|
|
1175
1208
|
|
|
@@ -1194,9 +1227,9 @@ await controller.prepare({ psp: 'aci', environment: 'production' }); // 'sandbox
|
|
|
1194
1227
|
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
1228
|
|
|
1196
1229
|
- 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,
|
|
1230
|
+
- 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.
|
|
1231
|
+
- 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.
|
|
1232
|
+
- 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
1233
|
|
|
1201
1234
|
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
1235
|
|
|
@@ -1205,7 +1238,7 @@ The `blocked` events the SDK adds for ACI, by `detail`:
|
|
|
1205
1238
|
| `detail` | When |
|
|
1206
1239
|
|---|---|
|
|
1207
1240
|
| `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. |
|
|
1241
|
+
| `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
1242
|
| `aci_checkout_reused` | A second card-number request arrives on a checkout whose first one is running or was answered in this attachment. |
|
|
1210
1243
|
| `aci_leg_unanswered` | A CVC or expiry request's card-number request failed, or none began within 5 seconds. |
|
|
1211
1244
|
| `aci_payment_unapproved` | A `/payment` arrives before its checkout's card-number request was answered. |
|
|
@@ -1240,7 +1273,7 @@ Braintree's native `ClientConfiguration` GraphQL query can run before, during or
|
|
|
1240
1273
|
|
|
1241
1274
|
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
1275
|
|
|
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.
|
|
1276
|
+
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
1277
|
|
|
1245
1278
|
Lost authorization polling, local approval timeouts, or interrupted browser
|
|
1246
1279
|
handoffs produce `outcome_unknown` and block automatic retry. The thrown
|
|
@@ -1598,6 +1631,15 @@ const checkout = await attachToPlaywright(page, {
|
|
|
1598
1631
|
sets its CVV length and contact fields by that brand, so the cardholder's
|
|
1599
1632
|
device refuses a card whose brand differs, before anything is sent.
|
|
1600
1633
|
Agentcard-issued cards are Mastercard: type a Mastercard test number.
|
|
1634
|
+
- **An expiry the merchant keeps.** RaiseNow's donation widget (Tamaro)
|
|
1635
|
+
collects the expiry in its own field and sends it to RaiseNow beside the
|
|
1636
|
+
token, so its card post carries no `expm` or `expy`. Such a post pauses like
|
|
1637
|
+
any other, and the cardholder's device writes the card number and CVV and
|
|
1638
|
+
adds no expiry, as the page sent none. Type the card's real expiry from
|
|
1639
|
+
`placeholderCard()` into the merchant's own expiry field: the merchant
|
|
1640
|
+
charges with the expiry its form holds. A post with only one of the two, or
|
|
1641
|
+
with no expiry while it asks to keep the card on file, is refused as
|
|
1642
|
+
`invalid_datatrans_request`.
|
|
1601
1643
|
- **Merchant accounts.** A token minted into a Datatrans account can be read
|
|
1602
1644
|
back as the card number by that account's holder, so the API refuses a
|
|
1603
1645
|
merchant account Agentcard has not reviewed (`datatrans_merchant_unregistered`).
|
|
@@ -1745,34 +1787,53 @@ The other requests on Primer's hosts:
|
|
|
1745
1787
|
|
|
1746
1788
|
A client session the merchant created to save every card after payment (Printful
|
|
1747
1789
|
does this for a signed-in shopper) shows nothing in the request, so the SDK
|
|
1748
|
-
cannot detect that save.
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1790
|
+
cannot detect that save. A live payment therefore goes ahead only with a card
|
|
1791
|
+
the cardholder stored, on an approval that says the merchant keeps the card on
|
|
1792
|
+
file. Nothing in the request names the Primer merchant whose session keeps it,
|
|
1793
|
+
so the approval names the merchant as the `merchant` you sent and says Agentcard
|
|
1794
|
+
cannot confirm it. The SDK sends the checkout page's origin as `checkout_origin`
|
|
1795
|
+
when the browser exposes it, and a live payment needs an https one. Without it,
|
|
1796
|
+
the API answers 400 `invalid_primer_request` with reason
|
|
1797
|
+
`checkout_origin_required` before anything is created, the SDK aborts the paused
|
|
1798
|
+
request with nothing sent, and the attachment reads `failed`. A card a company
|
|
1799
|
+
added never pays a live Primer payment (409 `card_not_stored`).
|
|
1753
1800
|
|
|
1754
1801
|
### Pay a Payrails checkout
|
|
1755
1802
|
|
|
1756
|
-
Payrails (web SDK
|
|
1757
|
-
launches dark: `syncRegistry()` asks for the `device_encrypt` mode and declares
|
|
1803
|
+
Payrails (web SDK secure fields, as on Blacklane's consumer booking checkout and
|
|
1804
|
+
FlixBus's guest checkout) launches dark: `syncRegistry()` asks for the `device_encrypt` mode and declares
|
|
1758
1805
|
the `payrails_device_encrypt` capability, and the API serves the Payrails
|
|
1759
1806
|
recognizer only to an org Agentcard has enabled for Payrails. For every other
|
|
1760
1807
|
org nothing changes: no answer is read, no Payrails host is intercepted, and
|
|
1761
1808
|
Payrails' requests go through untouched.
|
|
1762
1809
|
|
|
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
|
-
|
|
1810
|
+
For an enabled org, the SDK pauses two requests, each carrying the whole card as
|
|
1811
|
+
one JWE: the card form's save, a `POST` to the session's `saveInstrument` link
|
|
1812
|
+
(`https://api-pub.payrails.io/public/payment/instruments`; test hosts
|
|
1813
|
+
`api-pub.staging.payrails.io` and `sandbox-api-pub.staging.payrails.io`), and the
|
|
1814
|
+
card form's payment, a `POST` to the session's execution authorize link
|
|
1815
|
+
(`/public/merchant/workflows/payment-acceptance/executions/{id}/authorize`), which
|
|
1816
|
+
FlixBus's Pay now button sends. Either may go to a merchant's own Payrails host,
|
|
1817
|
+
`api-pub.<merchant>.payrails.io` (FlixBus's is `api-pub.flix.payrails.io`). The key
|
|
1818
|
+
the card was encrypted under is not in the request, so the adapters read the
|
|
1819
|
+
Payrails init payload (`{version, data}`) from the page's own XHR and fetch
|
|
1820
|
+
answers, at the top of an answer or anywhere in its first eight levels (FlixBus
|
|
1821
|
+
nests it inside `/payment/configuration/list`), and keep, in memory only, up to
|
|
1822
|
+
eight sessions for fifteen minutes. A paused save binds to the one session whose
|
|
1823
|
+
token, save link, `holderReference` and `providerConfigId` it names, and a paused
|
|
1824
|
+
payment to the one whose token, execution, host and `providerConfigId` it names,
|
|
1825
|
+
seen at most nine minutes before. That session's key rides on the create, and for
|
|
1826
|
+
a payment its `holderReference` too. The cardholder's device encrypts the real
|
|
1827
|
+
card under the key and sends the request to Payrails itself. For a save the page
|
|
1828
|
+
receives Payrails' 201 rebuilt from an allow-list (the instrument, its status, the
|
|
1829
|
+
network, the BIN cut to six digits, the last four, the expiry, the holder name and
|
|
1830
|
+
the fingerprint); for a payment, Payrails' 202 rebuilt to the authorize action's
|
|
1831
|
+
name, id, time and execution link, which the page's own Payrails SDK polls for the
|
|
1832
|
+
issuer's answer and any 3-D Secure challenge. That link must name the payment the
|
|
1833
|
+
page sent, its own execution on its own host: an action naming another execution,
|
|
1834
|
+
or this one on another host, would have the page poll another payment's result, so
|
|
1835
|
+
it never reaches the page and `authorize()` throws `PaymentOutcomeUnknownError`
|
|
1836
|
+
(`payrails_answer_unconfirmed`). Call `syncRegistry()` before attaching, or the
|
|
1776
1837
|
page's session answers are not read.
|
|
1777
1838
|
|
|
1778
1839
|
```ts
|
|
@@ -1785,7 +1846,8 @@ const controller = await attachToPlaywright(page, {
|
|
|
1785
1846
|
|
|
1786
1847
|
A save names no amount (the merchant charges the saved card later), so pass
|
|
1787
1848
|
`amount` and `currency`, or a `pageAmount` reader: without either, the save
|
|
1788
|
-
throws before anything is created.
|
|
1849
|
+
throws before anything is created. A payment names the amount it authorizes, and
|
|
1850
|
+
the API holds the amount you pass, if any, to it.
|
|
1789
1851
|
|
|
1790
1852
|
What the SDK refuses, before anything is created, with the reason on the
|
|
1791
1853
|
`unsupported_checkout` event:
|
|
@@ -1798,17 +1860,30 @@ What the SDK refuses, before anything is created, with the reason on the
|
|
|
1798
1860
|
`payrails_not_card_save`, `payrails_jwe_malformed`) are refused too. The
|
|
1799
1861
|
attachment reads `unsupported` with that reason and holds, since the merchant's
|
|
1800
1862
|
integration decides that shape.
|
|
1801
|
-
-
|
|
1802
|
-
|
|
1803
|
-
`
|
|
1863
|
+
- A card-form payment that stores the card at the merchant (`storeInstrument`,
|
|
1864
|
+
enrollment in a network offers program, or `meta.order.processingType`
|
|
1865
|
+
`CardOnFile` or `UnscheduledCardOnFile`):
|
|
1866
|
+
`payrails_card_on_file_consent_required`; one that sets up a subscription
|
|
1867
|
+
(`meta.subscription`, or `processingType` `Subscription`):
|
|
1868
|
+
`payrails_future_usage_refused`. Any other payment that sends the card
|
|
1869
|
+
inside `paymentComposition` in a shape the SDK does not take over (a stored
|
|
1870
|
+
card with a CVC field, a split or installment payment, a preferred scheme,
|
|
1871
|
+
another link than the execution's authorize link, or a `meta` member outside
|
|
1872
|
+
the reviewed list): `unsupported_payrails_card_payment`. The reviewed `meta`
|
|
1873
|
+
is the SDK's `clientContext` facts, `risk.sessionId` or `sessions`,
|
|
1874
|
+
`risk.threeDSMode` `Default`, a `risk` skip flag set `false`, an `order` with
|
|
1875
|
+
only `processingType` `Regular`, and an empty `customer`. Payrails reads keys
|
|
1876
|
+
in any case, so a key spelled in another case, or two keys of one object that
|
|
1877
|
+
differ only in case, are refused as well. The attachment holds, and the agent's
|
|
1804
1878
|
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
|
|
1879
|
+
- A save or payment no captured session matches (`payrails_session_unavailable`),
|
|
1880
|
+
or whose captured key is not the one the page's secure frame used
|
|
1881
|
+
(`payrails_key_invalid`, `payrails_key_mismatch`): that request alone is aborted,
|
|
1882
|
+
the event carries `message: 'Payrails session expired, reload the checkout'`,
|
|
1883
|
+
and the request after a reload is judged afresh.
|
|
1884
|
+
|
|
1885
|
+
A save or payment with no card (a dynamic payment-method form posts to the save
|
|
1886
|
+
link; a wallet, a stored card or a redirect method pays on the authorize link)
|
|
1812
1887
|
continues untouched. A `POST` to a Payrails host with no body the SDK can read,
|
|
1813
1888
|
whether it has none, an empty one or a streamed one, is aborted alone with a
|
|
1814
1889
|
`blocked` event, `unreadable_payrails_request`, by both adapters: Playwright
|
|
@@ -1816,8 +1891,8 @@ reports all three the same way. The Payrails SDK sends a JSON body with every
|
|
|
1816
1891
|
`POST`, so none of its own requests is refused this way. Payrails' static host,
|
|
1817
1892
|
`assets.payrails.io`, serves only files and takes no payment, so a bodyless
|
|
1818
1893
|
`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.
|
|
1894
|
+
Vault and database releases are required, and the API also refuses a request for a
|
|
1895
|
+
merchant whose Payrails workspace or host Agentcard has not reviewed.
|
|
1821
1896
|
|
|
1822
1897
|
### Pay an Elavon Converge payment page
|
|
1823
1898
|
|
|
@@ -1829,7 +1904,9 @@ to its own `/hosted-payments/service/payment/hpp/process`. The SDK pauses that
|
|
|
1829
1904
|
request, the cardholder approves the amount it names on their phone, their
|
|
1830
1905
|
device sends the real card to Converge, and the page receives Converge's
|
|
1831
1906
|
approval, so its own script shows the receipt and returns the buyer to the
|
|
1832
|
-
merchant. Converge
|
|
1907
|
+
merchant. Every Converge Buy Button and pay link opens the same page on
|
|
1908
|
+
`www.convergepay.com`, which posts the card to its own origin and is paid the
|
|
1909
|
+
same way. Converge's demo environment serves the same page on
|
|
1833
1910
|
`api.demo.convergepay.com`, which pays in test mode only.
|
|
1834
1911
|
|
|
1835
1912
|
Converge ships dark: `syncRegistry()` asks for the `converge_hpp_process`
|
|
@@ -1851,6 +1928,9 @@ const checkout = await attachToPlaywright(page, {
|
|
|
1851
1928
|
grant never pays one.
|
|
1852
1929
|
- Electronic check and financing payments on the same URL carry no card and
|
|
1853
1930
|
continue untouched.
|
|
1931
|
+
- A Buy Button's page sends `recurring` as `"false"` in every request unless the
|
|
1932
|
+
buyer ticked the button's recurring box. That request is a one-time sale and
|
|
1933
|
+
pauses like any other; `"true"`, or any other value, is refused as recurring.
|
|
1854
1934
|
- Before any prompt, the SDK refuses every other shape on that URL with
|
|
1855
1935
|
payment-core's own code (`ConvergeRefusalReason`, listed in
|
|
1856
1936
|
`CONVERGE_REFUSAL_REASONS`), the one the API and the cardholder's device
|
|
@@ -2200,8 +2280,8 @@ const checkout = await attachToPlaywright(page, {
|
|
|
2200
2280
|
`charged_amount_mismatch`. A 3-D Secure step names no amount, so nothing is
|
|
2201
2281
|
compared on that path (below). Without `amount` and `currency` the confirm
|
|
2202
2282
|
fails with `AmountRequiredError` before anything is created.
|
|
2203
|
-
- **What pauses.** Only the frame's confirm of a new card
|
|
2204
|
-
|
|
2283
|
+
- **What pauses.** Only the frame's confirm of a new card, with the frame's
|
|
2284
|
+
own headers and body. A saved card (`saved_card`),
|
|
2205
2285
|
a card tokenized earlier (`temp_card_id`), a card-shaped object the page
|
|
2206
2286
|
built into the transaction, or any other request on that URL is aborted with
|
|
2207
2287
|
a `blocked` event, detail `unsupported_wspay_payment_method`. Nothing is
|
|
@@ -2209,12 +2289,21 @@ const checkout = await attachToPlaywright(page, {
|
|
|
2209
2289
|
page's next confirm is judged afresh. The frame's other requests (`/show`,
|
|
2210
2290
|
`/status`, `/pan-info`) continue untouched.
|
|
2211
2291
|
- **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
|
-
|
|
2292
|
+
card so the merchant can charge it later without another approval. Valamar
|
|
2293
|
+
sets it on every booking: its payment step is a card guarantee that
|
|
2294
|
+
authorizes EUR 1.00 and keeps the card for the stay. Such a confirm pauses
|
|
2295
|
+
like any other, and the API pays it only as that guarantee: pass `amount:
|
|
2296
|
+
100, currency: 'eur'` when the merchant's payment step is a EUR 1.00
|
|
2297
|
+
guarantee. Any other amount fails the create with
|
|
2298
|
+
`wspay_keep_on_file_unsupported` and nothing is created. The cardholder's
|
|
2299
|
+
approval says the merchant keeps the card on file, and it pays only with a
|
|
2300
|
+
card the cardholder stored: a card a company added fails with
|
|
2301
|
+
`card_not_stored`. When WSPay approves, the frame receives the token WSPay
|
|
2302
|
+
keeps for the merchant (`pan_token`), as it would without Agentcard, and
|
|
2303
|
+
never the card's expiry or masked number. The API serves that answer for 15
|
|
2304
|
+
minutes after the approval, the SDK's default window, and then drops it, so
|
|
2305
|
+
a later read gets `response_expired`. The stay itself is charged later,
|
|
2306
|
+
outside this approval.
|
|
2218
2307
|
- **3-D Secure.** When WSPay answers with its 3-D Secure step, the frame runs
|
|
2219
2308
|
the bank's challenge in your browser, and nothing is charged until the
|
|
2220
2309
|
cardholder completes it there. The controller reads `requires_user_action`
|
|
@@ -2236,7 +2325,7 @@ const checkout = await attachToPlaywright(page, {
|
|
|
2236
2325
|
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
2326
|
|
|
2238
2327
|
- 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
|
|
2328
|
+
- 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
2329
|
- 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
2330
|
- 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
2331
|
|
package/dist/aci.d.ts
CHANGED
|
@@ -140,15 +140,17 @@ export declare class AciGate {
|
|
|
140
140
|
/** The card-number request's approval ended, answered or not; unanswered siblings fail. */
|
|
141
141
|
end(): void;
|
|
142
142
|
/**
|
|
143
|
-
* A request on this checkout was refused
|
|
144
|
-
*
|
|
145
|
-
* one fail now, and so does every later sibling
|
|
146
|
-
*
|
|
143
|
+
* A request on this checkout was refused. When it names the card number (a card-number request
|
|
144
|
+
* that asks ACI to keep the card, or carries a field the widget never sends with it) and no
|
|
145
|
+
* card-number request began, the siblings waiting for one fail now, and so does every later sibling
|
|
146
|
+
* until a card-number request begins. A refused request that names no card number, such as a
|
|
147
|
+
* fingerprint longer than ACI takes, says nothing about the card-number request a later Pay sends,
|
|
148
|
+
* so its siblings keep waiting for it. A running or answered card-number request keeps its siblings.
|
|
147
149
|
*/
|
|
148
150
|
refused(leg: {
|
|
149
151
|
host: string;
|
|
150
152
|
checkoutId: string;
|
|
151
|
-
}): void;
|
|
153
|
+
}, body: string | null | undefined): void;
|
|
152
154
|
/**
|
|
153
155
|
* A sibling leg (the page's CVC or expiry request): the answer to hand it, once its checkout's
|
|
154
156
|
* card-number request was answered, or null when that request failed, never began within
|
package/dist/aci.generated.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ declare var ACI_ABSORBED_FIELDS: readonly string[];
|
|
|
11
11
|
declare var ACI_PASSED_FIELDS: readonly string[];
|
|
12
12
|
declare var ACI_SIBLING_KEYS: readonly string[];
|
|
13
13
|
declare var ACI_REMOVED_ON_CONTINUATION: readonly string[];
|
|
14
|
+
declare var ACI_FINGERPRINT_FIELD: string;
|
|
14
15
|
declare function aciEndpoint(url: any): {
|
|
15
16
|
kind: string;
|
|
16
17
|
host: string;
|
|
@@ -84,4 +85,4 @@ declare function aciResponseOutcome(status: any, text: any): {
|
|
|
84
85
|
reason?: undefined;
|
|
85
86
|
};
|
|
86
87
|
declare function sanitizeAciResponse(text: any): string;
|
|
87
|
-
export { ACI_ABSORBED_FIELDS, ACI_BRAND_MISMATCH, ACI_CARD_FIELD, ACI_CARD_ON_FILE_REFUSED, ACI_CHECKOUT_UPDATED, ACI_EXPIRY_UNSTRIPPABLE, ACI_HOSTS, ACI_PASSED_FIELDS, ACI_REMOVED_ON_CONTINUATION, ACI_RESULT_URL_REFUSED, ACI_SIBLING_KEYS, ACI_TEST_HOSTS, ACI_TEST_PANS, ACI_UNSUPPORTED_REQUEST, aciBrandForNetwork, aciBrandOfCardLeg, aciCardBrand, aciEndpoint, aciEnvironment, aciFacts, aciHostEnvironment, aciPlaceholderBrandRefusal, aciResponseOutcome, classifyAciRequest, injectAciCard, isAciCardOnFileKey, isAciCardRequest, isPreparedAciRequest, prepareAciContinuation, readAciForm, sanitizeAciResponse };
|
|
88
|
+
export { ACI_ABSORBED_FIELDS, ACI_BRAND_MISMATCH, ACI_CARD_FIELD, ACI_CARD_ON_FILE_REFUSED, ACI_CHECKOUT_UPDATED, ACI_EXPIRY_UNSTRIPPABLE, ACI_FINGERPRINT_FIELD, ACI_HOSTS, ACI_PASSED_FIELDS, ACI_REMOVED_ON_CONTINUATION, ACI_RESULT_URL_REFUSED, ACI_SIBLING_KEYS, ACI_TEST_HOSTS, ACI_TEST_PANS, ACI_UNSUPPORTED_REQUEST, aciBrandForNetwork, aciBrandOfCardLeg, aciCardBrand, aciEndpoint, aciEnvironment, aciFacts, aciHostEnvironment, aciPlaceholderBrandRefusal, aciResponseOutcome, classifyAciRequest, injectAciCard, isAciCardOnFileKey, isAciCardRequest, isPreparedAciRequest, prepareAciContinuation, readAciForm, sanitizeAciResponse };
|