@agent-cards/sdk 0.23.6 → 0.23.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,9 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Pay Arrow Electronics' card save as its live checkout signs it. Arrow's server adds an id of its own, `merchant_defined_data8` (digits), to the form and to the end of `unsigned_field_names` when it signs the save, and 0.23.7 refused that request with `secure_acceptance_unsigned_fields_unreviewed` on 2026-10-10, after the cardholder approved and before any card was sent. The request now passes with that field or without it; a list that names it anywhere but last, names it twice or posts it unlisted is still refused, and a value other than at most 20 digits is refused as `secure_acceptance_merchant_reference`. Nothing else about Arrow's lists changed. A payment also needs the matching API and Vault releases.
6
+
5
7
  - Pause a Worldpay eProtect registration whatever Worldpay key its page names. Worldpay rotates its production eProtect key daily (`26457000011` on 2026-10-08), and 0.23.3 refused every registration whose `publicKeyId` was not `26455500012`, the key it shipped, so an attached checkout at any eProtect merchant read `unsupported`. The SDK now takes any key id in Worldpay's digits; the cardholder's device encrypts the card under the key it reads from Worldpay's own `eProtect-api3.js` and writes that key's id into its copy, so no later rotation needs an SDK release. SmartPak Equine (paypageId `zhs8KzuRAryaq4cs` at `https://www.smartpakequine.com`) and Nordstrom (`fL33BPkRvbRqJ29S` at `https://www.nordstrom.com`) are reviewed eProtect merchants: a live checkout there passes `merchant: 'SmartPak Equine'` or `merchant: 'Nordstrom'` exactly, and `prepare()` refuses another name with `merchant_profile_mismatch` before any network call. eProtect stays dark for every org Agentcard has not enabled, and a payment also needs the matching API and Vault releases.
6
8
 
7
- - Stop the card your agent typed from reaching CyberSource through Secure Acceptance's Silent Order POST. A merchant that takes the card in its own form, such as Arrow Electronics, has its server sign the order fields, then posts them with the card fields (`card_number`, `card_expiry_date`, `card_cvn`) to a `/silent/` path on `secureacceptance.cybersource.com` (`testsecureacceptance.cybersource.com` in CyberSource's test environment, `secureacceptance.in.cybersource.com` in India); Arrow submits that form into a hidden iframe. No Agentcard payment takes that post over, and earlier releases let it through, so CyberSource received the dummy card. Both adapters now stop it for every company, whatever `syncRegistry()` served: a form the page submits as a page load, in the page or in a frame, gets the page that says Agentcard stopped the payment before the card reached CyberSource, and a fetch or an XHR fails. A `card_number` in the URL's query, as a form with method GET sends it, stops the request the same way. The controller reads `unsupported` with reason `unsupported_cybersource_secure_acceptance` and holds, like every `unsupported` reason: to pay another way on the same page, attach again. A post with no `card_number`, or an empty one, such as a payment with a stored `payment_token`, continues untouched, and so does Hosted Checkout, the same hosts without `/silent/`, where your agent types the card on CyberSource's own page. The SDK stops a post whose body it cannot read with a `blocked` event, `unreadable_cybersource_secure_acceptance_request`, and does not hold the checkout. TypeScript: the controller's `unsupported()` reasons gain `unsupported_cybersource_secure_acceptance`; code that switches exhaustively over them needs the new case.
9
+ - Pay Arrow Electronics through the CyberSource Secure Acceptance lane, for a company Agentcard turned the lane on for. Arrow's reviewed entry, `arrow_token_create`, now ships enabled and pins the profile id and access key Arrow's checkout at `https://www.arrow.com` sends, read from Arrow's public page model. `prepare({ psp: 'cybersource_secure_acceptance', environment: 'production' })` at that page fills Arrow's profile id and needs `merchant: 'Arrow Electronics'`, or it fails with `merchant_profile_mismatch` before any call. Once CyberSource's page reaches Arrow's receipt, the SDK watches 10 seconds for Arrow's payment method request and emits `secure_acceptance_reply_unused` when none comes. The API still refuses any other profile id, access key or page on the production host (`unsupported_checkout`), and every company Agentcard has not turned the lane on for keeps the stop.
10
+
11
+ - Pay a CyberSource Secure Acceptance checkout through a prepared checkout (dark launch). The sync now also asks for `capabilities=cybersource_sa_token_create`, and the API serves Secure Acceptance's token create only for a company Agentcard turned it on for; every other company keeps the stop below. For such a company the SDK pauses the token create a merchant's form posts into a hidden iframe (`POST /silent/embedded/token/create` on `secureacceptance.cybersource.com`, or `testsecureacceptance.cybersource.com` for `environment: 'sandbox'`), as Arrow Electronics' checkout does when your agent clicks Save, binds it to the preparation, and answers the iframe with CyberSource's own reply page once the cardholder's device sent the card and neither the device nor the API found a card in the reply, so the merchant's receipt page receives CyberSource's signed reply in your browser's session. Call `prepare({ psp: 'cybersource_secure_acceptance', environment, secureAcceptanceProfileId? })` before Save, with the attachment's `cardId` (required, `card_required`); on production the SDK fills the profile id of a merchant Agentcard reviewed for the page's origin, refuses another merchant name (`merchant_profile_mismatch`) and refuses a checkout it can name no profile id for (`secure_acceptance_profile_id_invalid`). The same post as the page's own navigation, from a fetch or an XHR, or with no preparation, ends on the stopped page or fails, and nothing reaches CyberSource from your browser. 20 seconds after the pause with nothing handed over, the SDK retires the attempt (`secure_acceptance_send_window_closed`, or `secure_acceptance_send_window_unconfirmed` while the device may be sending), and a request the cardholder's device stopped at its own earlier boundary reads the same `failed`; a Save while Agentcard has not turned the relay on ends on the stopped page with nothing created, and the controller reads `failed` with reason `cybersource_sa_relay_unavailable`; 45 seconds after it with no reply the outcome is unknown (`PaymentOutcomeUnknownError`, reason `secure_acceptance_reply_late`); a reply that fails the rule is `secure_acceptance_reply_unconfirmed`. After delivery, for a merchant whose reviewed entry names its next request, the SDK watches 10 seconds for it and emits `secure_acceptance_reply_unused` with `{ authorizationId, last4 }` when none comes. The checkout pays with a card the cardholder stored, as every prepared checkout does, and the approval screen says the merchant can keep the card on file; Agentcard has turned no production merchant on yet. The raw CDP adapter also tells a page's own navigation and an iframe target's without reading the frame tree, which Chromium does not answer while that navigation is paused. TypeScript: `PrepareCheckoutOptions` and `PreparedCheckout` gain `secureAcceptanceProfileId`, and `SecureAcceptanceCheckoutRefusal` and `SecureAcceptanceUnknownReason` are exported. Matching API, Vault and relay releases are required.
12
+
13
+ - Read a prepared Amadeus add that the cardholder's device stopped at its own send boundary as the send window closing, not as the cardholder declining. The device sends nothing once 12 seconds have passed since it last saw the checkout unbound, earlier than the SDK's own 20 second window, and declines the request with reason `amadeus_send_window_closed`; the controller read that as `declined` with reason `approval_declined`. It now reads `failed` with reason `amadeus_send_window_closed`, as when the SDK's window closes first, and `authorize()` rejects with `CheckoutPreparationError` rather than `ApprovalDeclinedError`. Nothing was sent either way. A prepared checkout is used once, so attach again and prepare again to retry.
14
+
15
+ - Stop the card your agent typed from reaching CyberSource through Secure Acceptance's Silent Order POST. A merchant that takes the card in its own form, such as Arrow Electronics, has its server sign the order fields, then posts them with the card fields (`card_number`, `card_expiry_date`, `card_cvn`) to a `/silent/` path on `secureacceptance.cybersource.com` (`testsecureacceptance.cybersource.com` in CyberSource's test environment, `secureacceptance.in.cybersource.com` in India); Arrow submits that form into a hidden iframe. No Agentcard payment takes that post over, and earlier releases let it through, so CyberSource received the dummy card. Both adapters now stop it for every company, whatever `syncRegistry()` served, except the token create a company Agentcard turned the Secure Acceptance lane on for pays (above): a form the page submits as a page load, in the page or in a frame, gets the page that says Agentcard stopped the payment before the card reached CyberSource, and a fetch or an XHR fails. A `card_number` in the URL's query, as a form with method GET sends it, stops the request the same way. The controller reads `unsupported` with reason `unsupported_cybersource_secure_acceptance` and holds, like every `unsupported` reason: to pay another way on the same page, attach again. A post with no `card_number`, or an empty one, such as a payment with a stored `payment_token`, continues untouched, and so does Hosted Checkout, the same hosts without `/silent/`, where your agent types the card on CyberSource's own page. The SDK stops a post whose body it cannot read with a `blocked` event, `unreadable_cybersource_secure_acceptance_request`, and does not hold the checkout. TypeScript: the controller's `unsupported()` reasons gain `unsupported_cybersource_secure_acceptance`; code that switches exhaustively over them needs the new case.
8
16
 
9
17
  - Name CyberSource Secure Acceptance's Silent Order POST in a preflight inspection. A form, a frame or a document on a `/silent/` path of `secureacceptance.cybersource.com`, `testsecureacceptance.cybersource.com` or `secureacceptance.in.cybersource.com` now names `cybersource_secure_acceptance`, and the result stays `unknown`: `processor_variant_unverified` for a visible form, and `payment_signal_inactive` for a hidden one such as Arrow's. The catalog's `catalog_sha256` changes; `catalog_version` stays `2026-09-15.1`.
10
18
 
package/PREFLIGHT.md CHANGED
@@ -209,7 +209,7 @@ The catalog also carries reviewed hosted, regional, and versioned variants obser
209
209
 
210
210
  PayPal's legacy Hosted Fields (`paypal`) are identified from the card fields PayPal's JS SDK renders with `components=hosted-fields`: braintree-web builds whose version carries `-payments-sdk`, such as `3.32.0-payments-sdk-dev`, served from `assets.braintreegateway.com` as `hosted-fields-input-frame.min.html` and `hosted-fields-payments-sdk-tokenization-frame.min.html`. PayPal's frames send the card to PayPal, not to Braintree, so a page that shows them names `paypal` and returns `unknown` with `processor_variant_unverified`, and no braintree-web file from a `-payments-sdk` build counts as Braintree evidence. The SDK fails the card request those frames send; see the PayPal Hosted Fields row in the [README's processor table](./README.md#supported-processors).
211
211
 
212
- An inspection identifies CyberSource Secure Acceptance's Silent Order POST (`cybersource_secure_acceptance`) from the form a merchant's own page posts the card from, and from a frame or a document on the same path: a `/silent/` path, such as `/silent/pay` or `/silent/embedded/token/create`, on `secureacceptance.cybersource.com`, `testsecureacceptance.cybersource.com` or `secureacceptance.in.cybersource.com`. No Vault mode takes that post over, so a page that shows the form names `cybersource_secure_acceptance` and returns `unknown`: with `processor_variant_unverified` when the form is visible, and with `payment_signal_inactive` when it is hidden, as on Arrow Electronics' checkout. An inspection does not name Hosted Checkout, the same hosts without `/silent/`. The SDK stops the card post; see the CyberSource Secure Acceptance row in the [README's processor table](./README.md#supported-processors).
212
+ An inspection identifies CyberSource Secure Acceptance's Silent Order POST (`cybersource_secure_acceptance`) from the form a merchant's own page posts the card from, and from a frame or a document on the same path: a `/silent/` path, such as `/silent/pay` or `/silent/embedded/token/create`, on `secureacceptance.cybersource.com`, `testsecureacceptance.cybersource.com` or `secureacceptance.in.cybersource.com`. No Vault mode takes that post over, so a page that shows the form names `cybersource_secure_acceptance` and returns `unknown`: with `processor_variant_unverified` when the form is visible, and with `payment_signal_inactive` when it is hidden, as on Arrow Electronics' checkout. The processor ships dark for its token create, `/silent/embedded/token/create`: only an org Agentcard enabled pays it, and only through a prepared checkout, so the bundled catalog leaves it out and an inspection stays `unknown`. An inspection does not name Hosted Checkout, the same hosts without `/silent/`. The SDK stops the card post; see the CyberSource Secure Acceptance row in the [README's processor table](./README.md#supported-processors).
213
213
 
214
214
  Adyen's `supported` covers the Sessions payment request on Adyen's own hosts. Some Adyen merchants post the four encrypted fields to their own server instead, and their pages load the same secured fields, so an inspection identifies Adyen on both and cannot tell the two flows apart. The SDK pays a merchant's own endpoint only through a merchant profile Agentcard reviewed for it; the reviewed profiles (Dunelm and Cinemark) are in `observe`, so the SDK aborts their card requests and nothing is charged. The `actual_payment_request_validation` requirement is where that difference shows: check the paused request's host before treating an Adyen result as payable.
215
215
 
package/README.md CHANGED
@@ -255,7 +255,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
255
255
  | Mercado Pago | Card tokenization and prepared checkout are implemented. Guest Checkout Pro in Mexico also corrects the issuer for one native card association when the selected card has the same brand and type. One live MXN 40 Lotería Chida purchase with published SDK 0.10.0 completed automatically through Pay; the merchant confirmed paid status and PDF fulfillment. The historical SDK result remains unknown because the private receipt adapter rejected a relative download URL; a separate read with the corrected adapter confirms that same paid receipt. Independent processor capture/settlement and other country or integration paths remain unverified. |
256
256
  | VGS Collect (Very Good Security; Wolt) | not supported: VGS's proxy aliases only submissions from its own iframe, so a replay from the cardholder's device is refused by the merchant (verified on Wolt, 2026-09-03). Not recognized, so the agent's browser is not paused there |
257
257
  | PayPal Hosted Fields (the legacy card fields of PayPal's JS SDK; Planet Golf UK) | not supported: no Agentcard payment takes over the card these fields send to `cors.api.paypal.com`. The SDK fails that request for every company, so PayPal's payment confirm never receives the dummy card, and the controller reads `unsupported` with reason `unsupported_paypal_hosted_fields` and holds the attachment until you attach again; a PayPal wallet payment continues untouched |
258
- | CyberSource Secure Acceptance Silent Order POST (the merchant's own card form posted to CyberSource; Arrow Electronics) | not supported: no Agentcard payment takes over the card a merchant's page posts to a `/silent/` path on `secureacceptance.cybersource.com` (`testsecureacceptance.cybersource.com` in CyberSource's test environment, `secureacceptance.in.cybersource.com` in India). The SDK stops that post for every company, so CyberSource never receives the dummy card: a form the page submits as a page load, in the page or in a frame, ends on the page that says Agentcard stopped the payment, and a fetch or an XHR fails. The controller reads `unsupported` with reason `unsupported_cybersource_secure_acceptance` and holds the attachment until you attach again; a post with no `card_number`, such as a payment with a stored `payment_token`, continues untouched. The SDK does not stop Hosted Checkout, where your agent types the card on CyberSource's own page |
258
+ | CyberSource Secure Acceptance Silent Order POST (the merchant's own card form posted to CyberSource; Arrow Electronics) | dark launch: prepared checkout only, for an org Agentcard enabled. After `syncRegistry()`, for such an org, the SDK pauses the token create a merchant's form posts into a hidden iframe (`/silent/embedded/token/create` on `secureacceptance.cybersource.com`, or `testsecureacceptance.cybersource.com` in CyberSource's test environment), the cardholder's device sends the card to CyberSource, and the iframe gets CyberSource's own reply page. See [Pay a CyberSource Secure Acceptance checkout](#pay-a-cybersource-secure-acceptance-checkout-dark-launch). For every other company, and for every other card post to a `/silent/` path on those hosts or on `secureacceptance.in.cybersource.com` (India), no Agentcard payment takes the card over, and the SDK stops the post so CyberSource never receives the dummy card: a form the page submits as a page load, in the page or in a frame, ends on the page that says Agentcard stopped the payment, and a fetch or an XHR fails. The controller reads `unsupported` with reason `unsupported_cybersource_secure_acceptance` and holds the attachment until you attach again; a post with no `card_number`, such as a payment with a stored `payment_token`, continues untouched. The SDK does not stop Hosted Checkout, where your agent types the card on CyberSource's own page |
259
259
  | Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it. Sessions flow: the paused request is `/checkoutshopper/v1/sessions/{id}/payments` on Adyen's own hosts. A merchant that posts the encrypted fields to its own server pauses only when Agentcard reviewed it as a merchant profile. Cinemark and Dick's Sporting Goods' own storefront take payments for a company Agentcard turned them on for; every other reviewed merchant is in `observe`, so its card requests are reported and aborted and nothing pays there yet (see "Adyen merchants that take the card on their own server"). Without a preparation, approval starts at Pay and must land before Adyen Web's own request timeout (observed at 60 seconds on Adyen Web 6.41 and 6.44; not enforced by the SDK); `prepare({ psp: 'adyen' })` moves the approval before Pay |
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 |
@@ -284,7 +284,7 @@ so new processors work without you shipping a release. `attachToCdp` derives the
284
284
  `Fetch.enable` url patterns from that same list rather than a constant, which is
285
285
  why the sync call belongs before the attach. `agentcard.checkout.cardUrlPatterns()` returns
286
286
  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`
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
288
  for the list this build syncs. The API serves Fiserv's card capture only to a
289
289
  build that lists `fiserv_card_capture`, and only for a company Agentcard turned
290
290
  Fiserv on for. An older SDK never pauses a Fiserv request it could not finish,
@@ -304,7 +304,10 @@ same holds for the Verifone profiles and `verifone_card_capture`. NMI's hosted
304
304
  payment page (`ekashu_hosted_payment_page`) is served on the same terms: this
305
305
  build pauses the merchant's entry form to `live.ekashu.com` for the
306
306
  cardholder's device and fails every other post your browser makes to NMI, so
307
- NMI never sees the card your agent typed.
307
+ NMI never sees the card your agent typed. CyberSource Secure Acceptance's token
308
+ create (`cybersource_sa_token_create`) is served on the same terms too; every
309
+ other company keeps the stop every build applies to a card posted to Secure
310
+ Acceptance.
308
311
 
309
312
  ## Modes
310
313
 
@@ -1164,7 +1167,7 @@ await controller.prepare({ psp: 'amadeus_checkout', environment: 'production' })
1164
1167
 
1165
1168
  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.
1166
1169
 
1167
- 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`. 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.
1170
+ 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.
1168
1171
 
1169
1172
  The SDK refuses some requests itself, before any row or prompt, and the controller reads `failed` with the reason: Air France's and KLM's add, whose expiry their own form keeps (`amadeus_merchant_held_expiry`), a Data Collection Page store until the merchant's field configuration is reviewed (`amadeus_dcp_field_config_unreviewed`), and an add whose Host header names another page (`amadeus_header_binding`). Card saving, split payments, installments, currency conversion and Amadeus 3-D Secure data are refused the same way. The SDK judges an add before a card is chosen, so it cannot see the card's network: an add naming another network reaches the cardholder's device, which refuses it after approval (`vendor_mismatch`), with nothing sent. An add with no preparation is refused with `PreparationRequiredError`. An add that pays with a card the merchant saved carries no card number and continues untouched, and so does Air France's Nuvei payment with a `ccTempToken` after the add. Autopilot never pays an Amadeus payment.
1170
1173
 
@@ -2027,6 +2030,145 @@ does not support a merchant timeout of 5 seconds or less. The pre-live host
2027
2030
  receives only Worldpay's documented test cards. Autopilot never pays eProtect.
2028
2031
  The matching API, Vault and relay releases are required.
2029
2032
 
2033
+ ### Pay a CyberSource Secure Acceptance checkout (dark launch)
2034
+
2035
+ CyberSource Secure Acceptance support ships dark. A merchant that takes the
2036
+ card in its own form, such as Arrow Electronics, has its server sign the order
2037
+ fields, then submits them with the four card fields as a hidden form into an
2038
+ iframe: `POST https://secureacceptance.cybersource.com/silent/embedded/token/create`
2039
+ (`testsecureacceptance.cybersource.com` in CyberSource's test environment, the
2040
+ `sandbox` environment), a `create_payment_token` at a signed amount of 0.
2041
+ CyberSource answers inside that iframe with a page whose form posts its signed
2042
+ reply, with a payment token only the merchant's CyberSource account can charge,
2043
+ to the merchant's receipt page; the merchant charges its own cart total against
2044
+ that token later, at place order, server to server. `syncRegistry()` declares
2045
+ the `cybersource_sa_token_create` capability, and the API serves the Secure
2046
+ Acceptance recognizer to a build that declares it only for an org Agentcard has
2047
+ enabled. Every other org keeps the stop described in the processor table.
2048
+
2049
+ Who can pay: an org Agentcard turned the lane on for, with a card the
2050
+ cardholder stored in their Vault, as for every prepared checkout. The merchant
2051
+ keeps a token for that card and charges its own cart total later, at place
2052
+ order, so the cardholder approves the total your agent read, and the approval
2053
+ screen says the merchant can keep the card on file, as it does for Worldpay
2054
+ eProtect. In the sandbox the stored card is one of CyberSource's test cards.
2055
+
2056
+ The merchant's page asks its iframe for the reply 30 times, a second apart,
2057
+ from Save, which is too soon for a prompt that waits on a person. A Secure
2058
+ Acceptance checkout is therefore always prepared, before the agent clicks Save:
2059
+
2060
+ ```ts
2061
+ const card = await vault.placeholderCard({ user: 'your-user-id', cardId: 'vc_...' }); // what the agent types
2062
+ const checkout = await attachToPlaywright(page, {
2063
+ vault, user: 'your-user-id', merchant: 'Arrow Electronics',
2064
+ amount: 1234, currency: 'usd', // the cart total the agent read: what the cardholder approves
2065
+ cardId: card.cardId, // required: the stored card that pays, locked to the approval
2066
+ onApprovalUrl: deliverPrivatelyToCardholder,
2067
+ });
2068
+ // The agent types card.number, card.securityCode and the card's real expiry, the card's own billing
2069
+ // name and address, and leaves "save for future use" and "set as default" unticked. Then:
2070
+ await checkout.prepare({ psp: 'cybersource_secure_acceptance', environment: 'production' });
2071
+ await page.getByRole('button', { name: 'Save', exact: true }).click();
2072
+ ```
2073
+
2074
+ - What the agent types: the placeholder number of the paying card's network
2075
+ and its placeholder security code (`placeholderCard()`), the card's real
2076
+ expiry, and the card's own billing name and address, which the merchant's
2077
+ server signs and its charge checks. The merchant's "save for future use" and
2078
+ "set as default" stay unticked: a request that keeps the card for later
2079
+ orders, or edits a saved one, is refused.
2080
+ - `secureAcceptanceProfileId` names the merchant's Secure Acceptance profile
2081
+ (a UUID shape): the CyberSource merchant the token is created for. On
2082
+ production, leave it out for a merchant Agentcard reviewed: the SDK fills the
2083
+ profile id the reviewed entry pins for the checkout page's origin, and
2084
+ `merchant` must be that entry's name, the one the approval shows
2085
+ (`merchant_profile_mismatch`). A checkout it can name no profile id for is
2086
+ refused with `secure_acceptance_profile_id_invalid`. In the sandbox, name your
2087
+ own test profile's id. A token request naming another profile id never spends
2088
+ the approval.
2089
+ - `cardId` is required (`card_required`), and the approval is locked to that
2090
+ card.
2091
+
2092
+ After Save, the SDK pauses the iframe's post and binds it to the preparation
2093
+ only when payment-core's rule admits it for the prepared environment, profile
2094
+ id and checkout page, which the post's own `Origin` header names. The
2095
+ cardholder's device, still on the approval page, writes the real card into the
2096
+ four card fields and sends the post to CyberSource through Agentcard's
2097
+ encrypted relay. When CyberSource's reply page carries no card, which the device
2098
+ and the API each check, the SDK answers the paused iframe with that page, byte
2099
+ for byte, status 200 and its one content type, so the browser loads it as
2100
+ CyberSource's own page and its form delivers CyberSource's signed reply to the
2101
+ merchant's receipt page in your browser's session, as it would without
2102
+ Agentcard. The merchant's page then sets its payment method. The SDK reads the
2103
+ authorization every 500 ms for 30 seconds, so the reply lands inside the
2104
+ merchant's poll.
2105
+
2106
+ | Request to Secure Acceptance | What the adapters do |
2107
+ | --- | --- |
2108
+ | The token create a merchant's form posts into an iframe, naming the prepared profile id, from the prepared page, on the prepared environment's host | Paused, bound to the preparation and answered with CyberSource's page |
2109
+ | The same post with no preparation on the attachment | The iframe gets the stopped page (`stopped_before_send`), nothing is created, and the controller reads `failed` with reason `preparation_required` |
2110
+ | The same post when the preparation is not ready or spent, names another profile id or page, carries no `Origin`, or keeps the card | The iframe gets the stopped page, and the preparation is retired with `checkout_changed` or payment-core's reason (`secure_acceptance_origin_header`, `secure_acceptance_keeps_card`, ...) |
2111
+ | The token create sent as the page's own navigation, from a fetch or from an XHR | Stopped as an unsupported checkout (`unsupported_cybersource_secure_acceptance`) |
2112
+ | Every other `/silent/` path, the India host, another spelling of the URL, a `sale` or an `authorization` | Stopped for every company, as the processor table says |
2113
+ | A post with no card number | Continues untouched |
2114
+
2115
+ The clocks, from the pause:
2116
+
2117
+ - 20 seconds with nothing being handed to the iframe: the SDK retires the
2118
+ preparation or cancels the authorization. Confirmed (the card never left the
2119
+ device): the iframe gets the stopped page, and the controller reads `failed`
2120
+ with reason `secure_acceptance_send_window_closed` (a `blocked` event of that
2121
+ name). Not confirmed, since the device may already be sending: it keeps
2122
+ waiting (`secure_acceptance_send_window_unconfirmed`). The cardholder's
2123
+ device keeps an earlier boundary of its own, 12 seconds after it last saw
2124
+ the checkout unbound: past it the device sends nothing and declines the
2125
+ request with the same reason, and the iframe and the controller read the
2126
+ same as when the SDK's window closes.
2127
+ - 45 seconds with no reply: `PaymentOutcomeUnknownError` with reason
2128
+ `secure_acceptance_reply_late`. The iframe gets the stopped page
2129
+ `sent_answer_not_shown`, naming the authorization, unless the API confirmed
2130
+ the card never left (`stopped_before_send`).
2131
+ - A reply that fails payment-core's rule raises `secure_acceptance_reply_unconfirmed`,
2132
+ and one the API no longer serves (60 seconds after approval)
2133
+ `response_expired`; the iframe gets `sent_answer_not_shown`. A card CyberSource
2134
+ refused raises `ProcessorRefusedError`, the controller reads `declined`, and the
2135
+ iframe gets `sent_answer_not_shown`.
2136
+
2137
+ What a late or unread reply means: CyberSource may have created a token the
2138
+ merchant's page never read, and the merchant may keep it. For a merchant whose
2139
+ reviewed entry names the request its page sends once the receipt answered
2140
+ (Arrow's payment method set), the SDK watches 10 seconds for it after delivery,
2141
+ read-only; when none comes it emits `secure_acceptance_reply_unused` with
2142
+ `{ authorizationId, last4 }`. Reload checkout: if the merchant lists a saved
2143
+ card ending in those four digits, pick it; otherwise ask for a new checkout and
2144
+ a new approval. Never type a card into a spent approval.
2145
+
2146
+ 3-D Secure: the merchant charges the token at place order and may run 3-D
2147
+ Secure in your browser (Arrow uses Cardinal). Only a frictionless result places
2148
+ the order: a challenge stops it before any authorization, so nothing is
2149
+ charged. Report the order as not placed. The merchant keeps the token, as the
2150
+ approval screen told the cardholder, until the cardholder removes the saved card
2151
+ in their account with the merchant.
2152
+
2153
+ Arrow Electronics is the one merchant Agentcard reviewed on the production
2154
+ host. Its entry, `arrow_token_create`, pins the profile id and access key
2155
+ Arrow's checkout at `https://www.arrow.com` sends, so `prepare()` there fills
2156
+ Arrow's profile id, and `merchant` must be `Arrow Electronics`. The API refuses
2157
+ any other profile id, access key or page on the production host
2158
+ (`unsupported_checkout`, with `secure_acceptance_merchant_unreviewed` or
2159
+ `secure_acceptance_access_key_unreviewed`). The sandbox takes your own test
2160
+ profile and CyberSource's test cards. `prepare()` reports the API's refusals by name,
2161
+ with the API's own reason as the error's `detail`: `unsupported_checkout`,
2162
+ `processor_unavailable`, `merchant_profile_mismatch`, the environment refusals,
2163
+ and, until Agentcard turns the relay on,
2164
+ `cybersource_sa_relay_unavailable`. A Save while the relay is off ends the same
2165
+ way: the API refuses the create before any authorization exists, the iframe
2166
+ gets the stopped page (`stopped_before_send`), the preparation is retired, and
2167
+ the controller reads `failed` with reason `cybersource_sa_relay_unavailable`.
2168
+ The SDK writes no CORS header on the iframe's answer; Playwright adds its own to
2169
+ any cross-origin fulfill, which a navigation never reads. Autopilot never pays
2170
+ this lane. The matching API, Vault and relay releases are required.
2171
+
2030
2172
  ### Pay a WSPay Components checkout
2031
2173
 
2032
2174
  Hotel booking pages such as Valamar's take cards in WSPay Components, WSPay's
package/dist/amadeus.d.ts CHANGED
@@ -8,7 +8,18 @@ export declare const AMADEUS_SEND_WINDOW_MS = 20000;
8
8
  * limit. As a network error, the tunnel reports it at once as its own "Error".
9
9
  */
10
10
  export declare function failsAsNetworkError(url: string): boolean;
11
- /** The send window for a request paused under a preparation for `psp`, or null when it has none. */
11
+ /** The processor a send window's events and stop cause name. */
12
+ export type SendWindowProcessor = 'amadeus' | 'secure_acceptance';
13
+ /** Why a send window stopped its attempt: the API confirmed the card never left the device and never can. */
14
+ export type SendWindowClosed = `${SendWindowProcessor}_send_window_closed`;
15
+ /**
16
+ * The send window for a request paused under a preparation for `psp`, or null when it has none: a
17
+ * prepared Amadeus SDK add, and a prepared CyberSource Secure Acceptance token create, each 20 seconds.
18
+ */
19
+ export declare function preparedSendWindowMs(psp: string | undefined, url: string): number | null;
20
+ /** The processor a prepared request's send window names in its events, or null when it has none. */
21
+ export declare function sendWindowProcessor(psp: string | undefined): SendWindowProcessor | null;
22
+ /** The send window for a prepared Amadeus SDK add, or null: preparedSendWindowMs for Amadeus alone. */
12
23
  export declare function amadeusSendWindowMs(psp: string | undefined, url: string): number | null;
13
24
  /** What the window reads from the adapter, re-read when it closes. */
14
25
  export interface SendWindowHooks {
@@ -32,10 +43,11 @@ export interface SendWindowHooks {
32
43
  /** The part of a merchant attempt the window acts on. */
33
44
  export interface SendWindowAttempt {
34
45
  readonly signal: AbortSignal;
35
- stop(cause: 'amadeus_send_window_closed'): void;
46
+ stop(cause: SendWindowClosed): void;
36
47
  }
37
48
  /**
38
49
  * Arm the window on one paused request. Returns the function that disarms it, which the adapter
39
- * calls once the attempt settles, whatever the outcome.
50
+ * calls once the attempt settles, whatever the outcome. `processor` names the window's events and
51
+ * the attempt's stop cause (Amadeus by default).
40
52
  */
41
- export declare function armSendWindow(ms: number, attempt: SendWindowAttempt, hooks: SendWindowHooks): () => void;
53
+ export declare function armSendWindow(ms: number, attempt: SendWindowAttempt, hooks: SendWindowHooks, processor?: SendWindowProcessor): () => void;
package/dist/amadeus.js CHANGED
@@ -1,5 +1,6 @@
1
1
  // Two Amadeus Checkout rules both browser adapters share: how a refused card request fails
2
- // (failsAsNetworkError), and the send window of a prepared SDK add, below.
2
+ // (failsAsNetworkError), and the send window of a prepared SDK add, below, which a prepared
3
+ // CyberSource Secure Acceptance token create shares.
3
4
  //
4
5
  // The Checkout Web SDK's tunnel iframe gives its add XHR 30 seconds (xhr.timeout = 30000 in
5
6
  // tunnel.50fc56c91f295b5bfef0.js), then reports a timeout and Air France's page gives the payment
@@ -8,17 +9,26 @@
8
9
  // SDK cannot see the device start sending, and the API can: a plain runtime cancel is confirmed only
9
10
  // while the card has not moved, and once it is, the device's send is refused.
10
11
  //
12
+ // A merchant's page that posts CyberSource Secure Acceptance's token create into a hidden frame asks
13
+ // that frame for the reply 30 times, a second apart, from Save (Arrow Electronics), then gives up
14
+ // without a word. A token CyberSource creates after that point is one the page never reads, so a
15
+ // paused token create gets the same 20 second window (cybersource-secure-acceptance.ts).
16
+ //
11
17
  // So when the window closes and no answer is being handed to the page yet, the adapter asks for that
12
18
  // cancel. Confirmed: nothing was sent and nothing can be, so the attempt stops, the page's request
13
- // fails, and the checkout reads failed. Not confirmed: the device may already be sending, so the
14
- // attempt keeps waiting for its answer, which can still reach the page, and the page's own 30 second
15
- // deadline still ends it. A create that has not answered yet may already have committed its row and
16
- // bound the preparation the cardholder approved, and that device may be sending too. So the window
17
- // first retires the preparation: once the API confirms no request can bind it, nothing can be sent;
18
- // when a request already holds it, that authorization gets the same cancel and the same rule, and
19
- // when the API confirms neither, the attempt keeps waiting. The Data Collection Page's store has no
20
- // client timeout and gets no window.
19
+ // fails (a Secure Acceptance frame gets the stopped page), and the checkout reads failed. Not
20
+ // confirmed: the device may already be sending, so the attempt keeps waiting for its answer, which can
21
+ // still reach the page, and the page's own deadline still ends it. A create that has not answered yet
22
+ // may already have committed its row and bound the preparation the cardholder approved, and that
23
+ // device may be sending too. So the window first retires the preparation: once the API confirms no
24
+ // request can bind it, nothing can be sent; when a request already holds it, that authorization gets
25
+ // the same cancel and the same rule, and when the API confirms neither, the attempt keeps waiting. The
26
+ // Data Collection Page's store has no client timeout and gets no window. The window's events name the
27
+ // processor: `amadeus_send_window_closed` and `amadeus_send_window_unconfirmed`, or
28
+ // `secure_acceptance_send_window_closed` and `secure_acceptance_send_window_unconfirmed`.
21
29
  import { amadeusCardUrl } from './amadeus-checkout.generated.js';
30
+ import { isSecureAcceptanceTokenRequest } from './cybersource-secure-acceptance.generated.js';
31
+ import { SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_SEND_WINDOW_MS } from './cybersource-secure-acceptance.js';
22
32
  /** How long after the pause a paused Amadeus SDK add may still be sent. */
23
33
  export const AMADEUS_SEND_WINDOW_MS = 20_000;
24
34
  /**
@@ -31,15 +41,39 @@ export const AMADEUS_SEND_WINDOW_MS = 20_000;
31
41
  export function failsAsNetworkError(url) {
32
42
  return amadeusCardUrl(url) !== null;
33
43
  }
34
- /** The send window for a request paused under a preparation for `psp`, or null when it has none. */
44
+ /**
45
+ * The send window for a request paused under a preparation for `psp`, or null when it has none: a
46
+ * prepared Amadeus SDK add, and a prepared CyberSource Secure Acceptance token create, each 20 seconds.
47
+ */
48
+ export function preparedSendWindowMs(psp, url) {
49
+ if (psp === 'amadeus_checkout')
50
+ return amadeusCardUrl(url)?.path === 'sdk' ? AMADEUS_SEND_WINDOW_MS : null;
51
+ if (psp === SECURE_ACCEPTANCE_PSP) {
52
+ let tokenCreate = false;
53
+ try {
54
+ tokenCreate = isSecureAcceptanceTokenRequest(url, 'POST') === true;
55
+ }
56
+ catch {
57
+ tokenCreate = false;
58
+ }
59
+ return tokenCreate ? SECURE_ACCEPTANCE_SEND_WINDOW_MS : null;
60
+ }
61
+ return null;
62
+ }
63
+ /** The processor a prepared request's send window names in its events, or null when it has none. */
64
+ export function sendWindowProcessor(psp) {
65
+ return psp === 'amadeus_checkout' ? 'amadeus' : psp === SECURE_ACCEPTANCE_PSP ? 'secure_acceptance' : null;
66
+ }
67
+ /** The send window for a prepared Amadeus SDK add, or null: preparedSendWindowMs for Amadeus alone. */
35
68
  export function amadeusSendWindowMs(psp, url) {
36
- return psp === 'amadeus_checkout' && amadeusCardUrl(url)?.path === 'sdk' ? AMADEUS_SEND_WINDOW_MS : null;
69
+ return psp === 'amadeus_checkout' ? preparedSendWindowMs(psp, url) : null;
37
70
  }
38
71
  /**
39
72
  * Arm the window on one paused request. Returns the function that disarms it, which the adapter
40
- * calls once the attempt settles, whatever the outcome.
73
+ * calls once the attempt settles, whatever the outcome. `processor` names the window's events and
74
+ * the attempt's stop cause (Amadeus by default).
41
75
  */
42
- export function armSendWindow(ms, attempt, hooks) {
76
+ export function armSendWindow(ms, attempt, hooks, processor = 'amadeus') {
43
77
  let disarmed = false;
44
78
  const settled = () => disarmed || attempt.signal.aborted || hooks.delivering();
45
79
  // Resolves only once the API confirms the card never left the device and never can; see the top
@@ -69,11 +103,12 @@ export function armSendWindow(ms, attempt, hooks) {
69
103
  if (settled())
70
104
  return;
71
105
  if (!confirmed) {
72
- hooks.onEvent?.({ type: 'blocked', detail: 'amadeus_send_window_unconfirmed' });
106
+ hooks.onEvent?.({ type: 'blocked', detail: `${processor}_send_window_unconfirmed` });
73
107
  return;
74
108
  }
75
- hooks.onEvent?.({ type: 'blocked', detail: 'amadeus_send_window_closed' });
76
- attempt.stop('amadeus_send_window_closed');
109
+ const closed = `${processor}_send_window_closed`;
110
+ hooks.onEvent?.({ type: 'blocked', detail: closed });
111
+ attempt.stop(closed);
77
112
  };
78
113
  const timer = setTimeout(() => { void close(); }, ms);
79
114
  timer.unref?.();