@agent-cards/sdk 0.23.27 → 0.24.0
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 +66 -0
- package/PREFLIGHT.md +7 -1
- package/README.md +137 -99
- package/dist/aci.d.ts +52 -22
- package/dist/aci.js +79 -15
- package/dist/adyen-merchant-hosted.generated.d.ts +1 -1
- package/dist/adyen-merchant-hosted.generated.js +459 -21
- package/dist/agent-amount.generated.js +1 -1
- package/dist/agentcard.d.ts +4 -5
- package/dist/agentcard.js +4 -5
- package/dist/amadeus.d.ts +9 -3
- package/dist/amadeus.js +33 -4
- package/dist/api-module.d.ts +1 -7
- package/dist/api-module.js +3 -5
- package/dist/{voice-retry.js → api-retry.js} +1 -1
- package/dist/api-types.d.ts +53 -0
- package/dist/{voice-types.js → api-types.js} +7 -2
- package/dist/approvals.d.ts +166 -0
- package/dist/approvals.js +213 -0
- package/dist/buy.d.ts +20 -101
- package/dist/buy.js +19 -74
- package/dist/capabilities.generated.js +1 -1
- package/dist/card-fields.generated.js +1398 -106
- package/dist/cdp.d.ts +3 -13
- package/dist/cdp.js +231 -175
- package/dist/client.d.ts +104 -22
- package/dist/client.js +215 -40
- package/dist/creditguard.generated.d.ts +4 -4
- package/dist/datatrans.generated.js +22 -1
- package/dist/elavon-converge.generated.js +5 -3
- package/dist/hold.d.ts +8 -0
- package/dist/hold.js +43 -2
- package/dist/hosted-entry.js +2 -0
- package/dist/index.d.ts +9 -15
- package/dist/index.js +6 -10
- package/dist/juspay-gate.js +2 -2
- package/dist/juspay.generated.d.ts +2 -2
- package/dist/juspay.generated.js +174 -4
- package/dist/lifecycle.d.ts +9 -61
- package/dist/lifecycle.js +24 -10
- package/dist/merchant-card-endpoints.generated.d.ts +6 -1
- package/dist/merchant-card-endpoints.generated.js +344 -19
- package/dist/merchant-hosted.d.ts +8 -1
- package/dist/nike-order.d.ts +51 -0
- package/dist/nike-order.js +81 -0
- package/dist/oceanpayment.d.ts +38 -0
- package/dist/oceanpayment.js +70 -0
- package/dist/paddle-tokenex.generated.js +105 -10
- package/dist/payplug.d.ts +41 -0
- package/dist/payplug.js +71 -0
- package/dist/payrails.generated.js +27 -7
- package/dist/payrexx.generated.js +176 -2
- package/dist/pre-claim.d.ts +9 -0
- package/dist/pre-claim.js +86 -7
- package/dist/preflight-capabilities.generated.d.ts +0 -4
- package/dist/preflight-capabilities.generated.js +0 -5
- package/dist/preflight-catalog.json +46 -5
- package/dist/preflight-playwright.d.ts +2 -2
- package/dist/preflight-playwright.js +63 -14
- package/dist/preflight-schemas.json +4 -0
- package/dist/preflight.generated.d.ts +0 -4
- package/dist/preflight.generated.js +24 -1
- package/dist/prepared-processor.d.ts +18 -5
- package/dist/prepared-processor.js +48 -8
- package/dist/primer.generated.js +1418 -120
- package/dist/processout.generated.d.ts +61 -0
- package/dist/processout.generated.js +577 -0
- package/dist/requests.js +1 -1
- package/dist/shiji-sts.generated.js +105 -10
- package/dist/stripe-checkout.generated.js +1405 -234
- package/dist/substitutions.generated.js +460 -26
- package/dist/tap-payments.generated.js +105 -10
- package/dist/twoc2p.generated.js +105 -10
- package/dist/url-masks/bluesnap.d.ts +2 -0
- package/dist/url-masks/bluesnap.js +15 -0
- package/dist/url-masks/oceanpayment.d.ts +2 -0
- package/dist/url-masks/oceanpayment.js +13 -0
- package/dist/url-masks/payplug.d.ts +2 -0
- package/dist/url-masks/payplug.js +13 -0
- package/dist/url-masks/vtex_payments.d.ts +2 -0
- package/dist/url-masks/vtex_payments.js +12 -0
- package/dist/url-masks.generated.d.ts +1 -1
- package/dist/url-masks.generated.js +6 -2
- package/dist/verifone.generated.js +42 -2
- package/dist/vtex-payments.generated.d.ts +127 -0
- package/dist/vtex-payments.generated.js +544 -0
- package/dist/webhooks.d.ts +2 -2
- package/dist/webhooks.js +2 -2
- package/dist/wspay.generated.d.ts +1 -1
- package/dist/wspay.generated.js +175 -3
- package/examples/preflight/kernel-native/inventory.json +1 -1
- package/package.json +6 -8
- package/dist/languages.d.ts +0 -23
- package/dist/languages.js +0 -76
- package/dist/voice-pkce.d.ts +0 -49
- package/dist/voice-pkce.js +0 -147
- package/dist/voice-stream.generated.d.ts +0 -118
- package/dist/voice-stream.generated.js +0 -170
- package/dist/voice-types.d.ts +0 -735
- package/dist/voice.d.ts +0 -373
- package/dist/voice.js +0 -815
- package/readme.test.mjs +0 -53
- /package/dist/{voice-retry.d.ts → api-retry.d.ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,72 @@ An unreleased change waits as a file of its own in `changes/` (`changes/README.m
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.24.0
|
|
8
|
+
|
|
9
|
+
### Upgrading from 0.23.28
|
|
10
|
+
|
|
11
|
+
- **Voice approval leaves this package, and page approval moves to `agentcard.approvals`.** `agentcard.voice` is gone, and so is the `voice` option on `new Agentcard()`. The four verbs a purchase approved on Agentcard's page needs keep their inputs and answers under `agentcard.approvals`: `voice.authorizations.create` is `approvals.create`, `request: 'sandbox'` included, `voice.authorizations.get` is `approvals.get`, `voice.authorizations.cancel` is `approvals.cancel`, and `voice.awaitResult` is `approvals.awaitResult`. Every approval runs on Agentcard's page, so a client names no approval posture, and the return link comes from the create's `return_url` alone. `awaitResult` waits 60 seconds unless `timeout_ms` names another deadline.
|
|
12
|
+
|
|
13
|
+
- These `voice` verbs go with no replacement: `connect`, `completeConnect`, `exchange`, `awaitConnected`, `disconnect`, `sessions.get`, `people.get`, `authorizations.setCard`, `approvals.approvalUrl`, `challenge`, `challengeForHost`, `stream`, `appKeys.register`, `appKeys.revoke`, `reset`, `actions.propose`, `actions.get`, `actions.awaitDone` and `languages()`. The approval link is the `approval_url` the create and every open read carry. A read no longer types `intent`, `intent_hash`, `interaction_evidence`, `delivery_channel`, or the processor's answer that `controller.resume` replayed. `buy.confirm` takes no `approval`, a turn never answers `needs_approval`, and `buy.approveByVoice` is gone; `authorization_id` on `buy.confirm` works as before. `attachToCdp` takes neither `pauseOnly` nor `onPaused`, and the controller has no `resume`. `CHECKOUT_AUTHORIZATION_EVENT_TYPES` no longer names `checkout_authorization.voice_dispatched`. `ApiTransport` loses `languages` and `pollTimeoutMs`.
|
|
14
|
+
|
|
15
|
+
- Removed exports: `VoiceModule`, `checkoutOriginOf`, `bundleOf`, `streamAddress`, `Languages`, `readReturnLink`, `openStream`, `streamBuffer`, `isBuyNeedsApproval`, `isBuyVoiceUnsupported`, `VOICE_EXECUTION_UNSUPPORTED`, and the types `VoiceOptions`, `VoiceTransport`, `Language`, `LanguageInfo`, `PkceCrypto`, `Lang`, `VoiceAsk`, `ApprovalPosture`, `Action`, `ConnectInput`, `ConnectResult`, `Connected`, `Person`, `Proposed`, `Done`, `ActionState`, `VoiceResetInput`, `VoiceChallenge`, `Verdict`, `AuthorizationDetail`, `Exchanged`, `ExchangeInput`, `Revoked`, `DeliveryChannel`, `InteractionEvidence`, `Intent`, `ApprovalRequestFields`, `SessionState`, `ReturnLink`, `VoiceResetOutcome`, `VaultSessionStep`, `HostApprovalBundle`, `AppVoiceKeyDescription`, `HardwareAttestation`, `RegisteredAppVoiceKey`, `RevokedAppVoiceKey`, `Hearing`, `StreamFormat`, `StreamHandle`, `StreamInput`, `StreamVerdict`, `ChannelKind`, `BuyApproval`, `BuyVoiceDeclineCode`, `BuyNeedsApproval`, `BuyVoiceUnsupported`, `BuyVoiceTurn`, `BuyApproveByVoiceInput`, `PausedCheckout` and `ResumeResult`. `SANDBOX_REQUEST`, `CANCEL_REASONS` and the types `CreateAuthorizationInput`, `Authorization`, `AuthorizationResult`, `Cancelled`, `PayingCard` and `ChoosableCard` stay, beside the new `ApprovalsModule` and `AuthorizationFields`. The checkout module, `buy.ask`, `buy.confirm`, `buy.conversation`, the CDP and Playwright adapters, the registries and the webhooks work as before.
|
|
16
|
+
|
|
17
|
+
- TypeScript: `MerchantHostedSubstitutions.path` is now `ProfilePathStep[]`. A step is a `JsonPathStep` or `{ text: 'json' }`, the step into a string that holds JSON text, and both new types are exported, `JsonTextStep` and `ProfilePathStep`. Code that reads each step as a string or a number needs the new case. `CardRequestHold` gains `covers`.
|
|
18
|
+
|
|
19
|
+
- Nike's card frame profile, `nike_paymentcc_store`, takes a Mastercard only: the frame's store post must name `MASTERCARD`, the one code Nike's recorded purchase shows. Type a Mastercard-range placeholder into Nike's frame. A placeholder the frame names `VISA` or any other code is no longer Nike's reviewed request, so the adapters refuse it with reason `unrecognized_payment_endpoint`. The profile stays in observe.
|
|
20
|
+
|
|
21
|
+
- Keep a cart's unquoted fees unquoted. `BuyCart.serviceFeesCents` is now `null` while the store has not quoted its fees, and the new `serviceFeesStatus` says `pending`. Before, the client read a missing amount as `0`, so an app could not tell fees still to come from no fees at all. A quoted amount reads `known`, and anything else reads `unknown` with a `null` amount. `ask()`, `conversation()` and the carts that come back with an error read fees the same way.
|
|
22
|
+
|
|
23
|
+
### New
|
|
24
|
+
|
|
25
|
+
- Pause iyzico's Checkout Form card payment for the cardholder's device, for a company Agentcard turned iyzico on for. iyzico's hosted Checkout Form (on `cpp.iyzipay.com`, where iyzico's Shopify app sends the buyer, and the form iyzico's WooCommerce plugin renders) posts the card as JSON to `https://api.iyzipay.com/payment/iyzipos/checkoutform/auth/ecom`, or to `.../initialize3ds/ecom` when 3-D Secure runs, under the form's `X-IYZI-TOKEN`. The sync now also asks for `capabilities=iyzico_checkout_form`, and the API serves iyzico's recognizer only for a company Agentcard turned iyzico on for. The SDK pauses only the form's own payment with a new card at those exact URLs: a saved card, a card the form would keep on file, an installment plan and a currency conversion fail in your browser before anything is created. A payment that names no `buyerProtected` pauses too, the way the form sends it for a guest on a page without iyzico's card storage script (`window.iyziUcsInit`), an iyzi.link payment link among them; one whose `buyerProtected` is anything but `false` fails in your browser, since buyer protection keeps the card with iyzico. The cardholder's device sends the payment to iyzico itself with the real card, and the form gets iyzico's answer: the payment done, or the bank's 3-D Secure page, which the form then runs in the agent's browser, where a challenge needs a person at that browser. The payment names its price but not its currency, so pass `amount` and `currency`, above zero: without them `authorize()` throws `AmountRequiredError` before anything is created, and the API refuses a pair whose amount is not the payment's price. An iyzico payment also needs the matching API and Vault releases. iyzico stays off for every company Agentcard has not turned it on for.
|
|
26
|
+
|
|
27
|
+
- Pause Paymentwall's card request for the cardholder's device, for a company Agentcard turned Paymentwall on for. Paymentwall's card form, on Paymentwall's hosted checkout or on a merchant's own page, sends the card as a form post to `https://pwgateway.com/api/token` under the merchant's public key, or to `https://api.paymentwall.com/api/brick/token` under a test key, and Paymentwall answers with a one-time token the checkout charges afterwards. The sync now also asks for `capabilities=paymentwall_card_token`, and the API serves Paymentwall's recognizer only for a company Agentcard turned Paymentwall on for. The SDK pauses only the card form's own request at those exact URLs: a stored card's security code, a Google Pay token, a card brand picked from the page's own list and any other body there fail in your browser before anything is created. The cardholder's device sends the request to Paymentwall itself with the real card, and the page gets Paymentwall's token. Paymentwall's request names no price, so pass `amount` and `currency`, above zero: without them `authorize()` throws `AmountRequiredError` before anything is created. The API pays only a Paymentwall merchant Agentcard reviewed for the checkout page the request comes from. Matching API and Vault releases are required. Paymentwall stays off for every company Agentcard has not turned it on for.
|
|
28
|
+
|
|
29
|
+
- Pause BlueSnap's Hosted Payment Fields for the cardholder's device, for a company Agentcard turned BlueSnap on for. The card number, expiry and code frames each send their own field as a JSON `PUT` to the merchant's payment-fields token, `https://ws1.bluesnap.com/services/2/payment-fields-tokens/<token>` (`ws2` for a token ending `_2`, `sandpay.bluesnap.com` in BlueSnap's sandbox). The sync now also asks for `capabilities=bluesnap_hosted_payment_fields`, and the API serves BlueSnap's recognizer only for a company Agentcard turned BlueSnap on for. The SDK pauses the card number frame's `PUT`, the cardholder's device sends the whole card in that one request, and the page gets BlueSnap's card data. The SDK holds the expiry and code frames' own `PUT`s and answers them with that answer, so the placeholder values never reach BlueSnap; they fail when the card number request fails, or when none begins within five seconds. A second card number request on the same token (`split_card_reused`), a saved card's request (`bluesnap_saved_card`) and anything else on the token's URL fail in your browser, and the page's own retry of the card number request while the cardholder is still deciding gets the approval's answer. The store's server charges the token afterwards and can ask BlueSnap to keep the card, so a live payment takes only a card the person stored (`card_not_stored`), and the approval says the store can keep it. The request names no price, so pass `amount` and `currency`, above zero: without them `authorize()` throws `AmountRequiredError` before anything is created. The token is the request's only credential, so `redactUrl` reads it as `{token}` in every event, error and URL the SDK reports. Matching API and Vault releases are required. BlueSnap stays off for every company Agentcard has not turned it on for.
|
|
30
|
+
|
|
31
|
+
- Pause the card post of Oceanpayment's hosted payment page for the cardholder's device, for a company Agentcard turned Oceanpayment on for (dark launch). The page, where Oceanpayment's Shopify redirect app sends the buyer after Shopify's Pay now, submits its card form natively to `https://secure.oceanpayment.com/gateway/sendPay;jsessionid=<session>` with the card in clear. The sync now also asks for `capabilities=oceanpayment_hosted_payment_page`, and the API serves Oceanpayment's recognizer only for a company Agentcard turned Oceanpayment on for. The SDK pauses only that post, exactly as the page's form action writes it, and aborts every other post to `secure.oceanpayment.com` and `test-secure.oceanpayment.com` as `unsupported_checkout` with reason `processor_card_page`, Cardinal's device-data call among them, so the agent's placeholder never reaches Oceanpayment; the page's own GETs continue. The cardholder's device writes the real card into the paused post and sends it through Agentcard's checkout relay. The post names no price, so pass `amount` and `currency`, above zero: without them `authorize()` throws `AmountRequiredError` before anything is created. The API takes only a test payment on Oceanpayment's sandbox today, since the post names neither the merchant nor the amount, and no answer is recorded as paid yet: a 4xx is a refusal, and every other answer waits as unknown for a person to settle. The session in the path names the payment the page holds, so `redactUrl` reads it as `{session}` in every event, error and URL the SDK reports, as in `https://secure.oceanpayment.com/gateway/sendPay;jsessionid={session}`. Matching API, relay and Vault releases are required. Oceanpayment stays off for every company Agentcard has not turned it on for.
|
|
32
|
+
|
|
33
|
+
- Pause VTEX Payments' card post for the cardholder's device, for a company Agentcard turned VTEX on for, only through a checkout the cardholder approved before Pay. Ollie's headless checkout, which some VTEX stores run, posts the card as JSON to the store's own VTEX vault, `https://<account>.vtexvault.com/api/payments/transactions/<id>/payments`, and gives the post 12 seconds. The sync now also asks for `capabilities=vtex_payments_card_payment`, and `prepare({ psp: 'vtex_payments', environment: 'production' })` names VTEX's one environment. The claim binds the first post by its shape; Ollie sends the same post again on a non-OK answer or a network error, then once more to `api.vtexvault.com`, and each of those fails in your browser on the spent approval, so one approval sends the card once. A post with no preparation fails in your browser before anything is created (`PreparationRequiredError`). The post names its price, which the cardholder approves, and VTEX answers it with a 201 and no body, so the payment's result comes from the checkout's next step, not from that answer. VTEX's payments endpoint takes no authentication, so the 32 hex digits after `/transactions/` on `<account>.vtexvault.com` are all a card post to that payment needs: `redactUrl` reads them as `{transaction}` in every event, error and URL the SDK reports. A payment also needs the matching API and Vault releases and a store Agentcard reviewed. VTEX Payments stays off for every company Agentcard has not turned it on for.
|
|
34
|
+
|
|
35
|
+
- Answer Ollie's VTEX card post inside the 12 seconds Ollie's checkout gives it. A prepared VTEX authorization is read every 500 ms for those 12 seconds, so VTEX's answer reaches the checkout within half a second of the cardholder's device recording it. The create names how long ago the post paused on your machine's clock (`paused_ms`), and the API counts 8 of the 12 seconds from that pause on its own clock: a post the cardholder's device has not sent by then is refused with `vtex_payments_post_timed_out` and nothing sent, at the create before any row or as a decline once the row exists, and a create refused that way retires its preparation. A paused post the device has not sent 8 seconds after the pause fails in your browser with nothing sent once the API confirms the card never left (`vtex_payments_send_window_closed`); while the device may be sending, the post keeps waiting for its answer. `authorize()` throws `CheckoutPreparationError` with either reason, never `CheckoutApiError` or `ApprovalDeclinedError`. A direct `authorize()` of a VTEX preparation must name `pausedAt`, which the adapters fill: without it the call throws before any create, and the preparation stays unspent for a call that names it. A post Ollie's checkout aborts while it waits ends the attempt with no second create, as an unknown outcome when the card may have left. `prepare({ psp: 'vtex_payments' })` passes on by name the API's refusals before any preparation exists: `processor_unavailable`, `live_host_on_sandbox_row`, `merchant_profile_mismatch`, `unsupported_currency` and `amount_required`.
|
|
36
|
+
|
|
37
|
+
- Pause USAePay's Pay.js card frame request for the cardholder's device, for a company Agentcard turned USAePay on for. USAePay's card frame (`https://www.usaepay.com/js/v2/card.html`, which a merchant's checkout page mounts) posts the card as JSON to `https://www.usaepay.com/api/v2/pub/payment_keys` (`secure.usaepay.com` alike, `sandbox.usaepay.com` in test) under the merchant's public key in its `Authorization`. The sync now also asks for `capabilities=usaepay_payment_key`, and the API serves USAePay's recognizer only for a company Agentcard turned USAePay on for. The SDK pauses only the frame's own card body at that exact URL: a Google Pay or Apple Pay token and a bank account sent to the same URL fail in your browser before anything is created. The cardholder's device sends the card through Agentcard's checkout relay with the real card, and the frame gets USAePay's single-use payment key, which the merchant's server charges afterwards. The request names no amount, so pass `amount` and `currency`, above zero: without them `authorize()` throws `AmountRequiredError` before anything is created. A USAePay payment also needs the matching API and Vault releases. USAePay stays off for every company Agentcard has not turned it on for.
|
|
38
|
+
|
|
39
|
+
- Pay a ProcessOut checkout, for the company Agentcard turned ProcessOut on for (dark launch). The store's page hands its card to ProcessOut.js, whose card frame on `js.processout.com` posts it to `https://api.processout.com/cards` under the store's project id and calls the page's error callback 20 seconds after the page asks it for a token, too soon for an approval after that press. So the SDK sends the card only through `prepare({ psp: 'processout', environment: 'production' })` (`'sandbox'` for a `test-proj_` project), before the press; without one, the SDK refuses the request before it asks the API, and sends nothing. The sync now also asks for `capabilities=processout_card_token`. Live and sandbox projects share one host, so the SDK binds the press's request to the approval by its project's environment and its shape, never by the URL `prepare()` names. Once the cardholder approves, the request pauses, the cardholder's device sends the card to ProcessOut itself, and the frame receives ProcessOut's card token, rebuilt from the card facts the page reads, and nothing else. A refusal ProcessOut answers inside a 2xx never reaches the frame as a token. One approval sends one card request, and a request the cardholder's device has not sent 12 seconds after the pause fails before the card leaves, with `processout_send_window_closed`. ProcessOut's request names no amount, so the cardholder approves the `amount` and `currency` you pass, above zero; the API refuses a zero `amount` with `amount_required` and an environment other than your key's mode with `processout_environment_mismatch`, and `prepare()` names both. A live project pays only a store Agentcard has reviewed, at its reviewed page and under its reviewed name and project id. Agentcard has reviewed none yet, so `prepare()` on a live project fails with `unsupported_checkout` before anyone is asked. The API holds the reviewed stores, so a store Agentcard reviews later needs no new SDK. No ProcessOut payment has gone through this route yet. A payment also needs the matching API and Vault releases.
|
|
40
|
+
|
|
41
|
+
- Pause PayPlug's payment page link for the cardholder's device, for a company Agentcard turned PayPlug on for, Agentcard's own for now. The sync now also asks for `capabilities=payplug_hosted_payment_page`. The SDK pauses the merchant's redirect to `https://secure.payplug.com/pay/<payment id>` (or `/pay/test/<payment id>` for a test payment), creates a `hosted_form` authorization for it, and fails every post a page it watches sends to PayPlug's page host, so PayPlug never receives the card your agent typed. The link names no amount: `authorize()` throws `AmountRequiredError` without your `amount` and `currency`, above zero, and the API answers `amount_required` for any currency but euros. One PayPlug payment takes one authorization across your company: a second one while the first awaits answers `duplicate_submission`, and one for a payment a card may already have gone to answers `payplug_payment_already_sent`. The link opens that payment's page and pays it, so `redactUrl` reads its id as `{payment}` in every event, error and URL the SDK reports, as in `https://secure.payplug.com/pay/{payment}`. No PayPlug payment completes yet: the API refuses every PayPlug link with `seller_unreviewed` until Agentcard reviews the name and the PayPlug account a merchant's PayPlug page shows, so that refusal is expected, not a setup error. Matching API and Vault releases are required. PayPlug stays off for every company Agentcard has not turned it on for.
|
|
42
|
+
|
|
43
|
+
- Pause the card request teenage engineering's checkout sends to its own server. Its Centra checkout on `teenage.engineering` runs Adyen's Drop-in and posts adyen-web's encrypted card, as `paymentMethodSpecificFields`, to `https://teenage.engineering/_api/mw/selections/<selection>/payment2`. The adapters now pause that request and refuse it as `unsupported_checkout` while the new profile, `teenage_engineering_centra_payment`, is in `observe`, so the agent's dummy card never reaches the store. The order form's own post to the same path, which carries no card, passes as before. A body that keeps the card for later, names a stored card or subscription contracts, or carries the card on the initiate call is aborted.
|
|
44
|
+
|
|
45
|
+
- Pause the card request SCHEELS' and Buff's checkouts send to their own servers. SCHEELS' page posts adyen-web's state as JSON text inside its own body, `state`, to `https://apihub.scheels.com/frontastic/action/cart/setAdyenCreditCard`; Buff's Magewire checkout posts it as the `placeOrder` argument to `https://www.buff.com/us/magewire/post/livewire/message/checkout.payment.methods.adyen_cc`. The adapters now pause each request and refuse it as `unsupported_checkout` while the new profiles, `scheels_frontastic_adyen_card` and `buff_magewire_adyen_cc`, are in `observe`, so the agent's dummy card never reaches either store. A profile's rules now read inside JSON text a body carries, and only text written exactly as `JSON.stringify` or PHP's `json_encode` (default flags) writes it, as Buff's server does: anything else is aborted.
|
|
46
|
+
|
|
47
|
+
- Pause the card request Philipp Plein's checkout sends to its own server. Plein's SiteGenesis page writes adyen-web's state as JSON text into one field of its billing form and posts the form to `https://www.plein.com/us/billing/`. The adapters now pause that form post and refuse it as `unsupported_checkout` while the new profile, `plein_sfcc_billing_form`, is in `observe`, so the agent's dummy card never reaches the store. A profile's rules now read a form body written exactly as a browser writes it, and an observe event reports a form's shape by its field names. Plein's page names its total only in an analytics block, so the profile cannot be paid until a reviewed rule reads it.
|
|
48
|
+
|
|
49
|
+
- Hold Nike's order to the amount the cardholder approved. Once an adapter asks the cardholder to pay through Nike's card frame profile, `nike_paymentcc_store`, it holds every request to `api.nike.com`. `attachToCdp` holds them in every frame it arms from the attach on, so a page already loaded when the cardholder is asked is held too; at the ask it also arms every other page and frame of the browser context then open, and the browser target, which reaches a tab or shared worker the checkout did not open once it loads after the ask. `attachToPlaywright` holds them with a route on the page's browser context, which reaches no shared or service worker. One order, `PUT /buy/checkouts/v3/<checkoutId>`, goes on unchanged, only when its `request.totals.total` and `request.currency` are the `approved` amount and the checkout still names that approval as under way, and after it only a byte-identical retry; a write whose path a server may read as under `/buy/checkouts/`, however it is spelled, is judged the same way. Every other request to `api.nike.com`, and any sent before the ask, continues untouched. An order that fails the amount rule, or comes while no approved amount was read, fails before it leaves, nothing is charged, a checkout still under way ends `failed` with reason `merchant_order_<why>`, and every later order under that approval fails too. A second order, or one after the checkout was cancelled, reconciled or retried, fails and leaves the state as it is. The events `merchant_order_released` and `merchant_order_refused` name the authorization and never the order. A browser, context, page or frame that cannot take the hold refuses the payment to Nike before anything is created. At the browser target, in the pages armed at the ask and on Playwright's context route, only the attachment that asked through Nike's profile last in that browser context, for any attempt, judges Nike's order; in the frames it arms, each `attachToCdp` attachment judges every order itself. The profile stays in observe.
|
|
50
|
+
|
|
51
|
+
- A card endpoint answer from `authorize()` (`CardEndpointReplay`) carries `approved`: the amount the cardholder approved, in the currency's smallest unit, with its currency, as the API's read names it. It is absent when the read names none.
|
|
52
|
+
|
|
53
|
+
- The preflight catalog names two more card forms. The card fields of every Bambora Custom Checkout 1 release on `libs.na.bambora.com` (rule `bambora-na-custom-checkout-fields`) name `bambora`, so a store that serves its own copy of Bambora's library, as Kent does, reads as Bambora and `supported` instead of naming no processor. The SDK pauses the same Bambora tokenization request as before. Worldpay's hosted payment pages under `/app/hpp/` and `/app/hpp-iframe/` on `payments.worldpay.com` (rule `worldpay-wpg-hosted-payment-page`), which Razer's checkout opens in a frame, name `worldpay` and read `unknown` with `processor_variant_unverified`, because the SDK takes over only Worldpay Access Checkout's card session. Both rule ids join the rule lists in `preflight-schemas.json`. BlueSnap leaves the catalog's exclusions, where it read `multi_request_tokenization_not_supported` with `human_checkout`, since the SDK now pauses its card fields. The catalog's `catalog_sha256` changes, as does the hash the Kernel kit's `inventory.json` carries; `catalog_version` stays `2026-09-15.1`.
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Hold a card a processor encrypted in the page when the store sends it inside its own request. With `hold: { placeholderNumbers }`, the hold now also holds any request whose body carries Adyen's `encryptedCardNumber` (or the legacy `adyenjs_` ciphertext), Braintree's legacy client-side encryption or Worldline Connect's `encryptedCustomerInput`, wherever it goes. Adyen's card component in an Advanced (non-Sessions) integration, such as Salesforce Commerce's, sends the encrypted card to the store's own payment or order step, which no recogniser matched and which never carries the placeholder's digits, so Pay went through with nothing held. Such a request is held as `encrypted_card` (`store_card_post`) and only cancelled: no release sends it on, in sandbox or live (`encrypted_card_post`). `CardRequestHold.covers` lists the hold-only rules armed (`bolt`, `placeholder_card`, `encrypted_card`). Exported: `encryptedCardHoldRule`. No API or Vault release is needed.
|
|
58
|
+
|
|
59
|
+
- Attach on a checkout that runs a worklet. `attachToCdp` refused any page whose browser context held a CDP `worklet` target (an audio, paint, animation or layout worklet) with `CheckoutAttachmentError` reason `unavailable`, and a hold armed earlier failed with `browser_interception_unavailable` once one started. Shopify's checkout starts one in its web-pixel sandbox frames, so every attach on those checkouts failed. A worklet has no fetch, XHR, WebSocket or beacon, so it can send no card request, and the module it loads is fetched by its owning frame, through that frame's interception. The SDK now accepts it and releases it from its debugger wait without owning it. Auction and shared storage worklets still refuse the page. No API or Vault release is needed.
|
|
60
|
+
|
|
61
|
+
- Read a checkout that loads hundreds of its own scripts through to its card frames. `collectCheckoutSignals` and `inspectCheckout` charged every script, iframe and form location, and every frame's own address, against the signal and snapshot budgets in document order, top frame first. A store whose checkout loads a few hundred of its own scripts, as Magento's RequireJS checkouts do with one script per module, spent the whole snapshot budget before the read reached the card frame, so the result read `unknown` with `snapshot_limit` and named no processor. A location that no reviewed rule can match, such as a store's own script or an ad frame's address, is now skipped without counting against either budget, and the read reaches the card frames. A read the budgets still cut, such as a page that floods a processor's own host, still reads `unknown`, and one frame's read examines at most 16,384 elements. No API or Vault release is needed.
|
|
62
|
+
|
|
63
|
+
- Hand the page no bank or card name from a Payrails or Primer answer that could finish the card number. Both processors fill names from their own BIN tables, and with the BIN and the last four already on the page, a name holding the card's hidden middle digits completes the number. The Payrails answer the SDK rebuilds for the page now leaves out the issuer's name when it carries more than three digits, in any script, and the network, the card type and the country's name when they carry any; the token still reaches the page. A Primer token whose network and BIN table names carry more than three digits together never reaches the page: `authorize()` throws `PaymentOutcomeUnknownError` with reason `primer_token_unconfirmed`. Agentcard's API judges the answer the same way before it stores it, whatever SDK version asks, so this is a second check behind it.
|
|
64
|
+
|
|
65
|
+
## 0.23.28
|
|
66
|
+
|
|
67
|
+
- Hand the page no Juspay `/txns` answer whose bank redirect could carry the card. The page receives only Juspay's order id, transaction id, status and bank redirect, and the SDK now hands over an approved answer only when the redirect's pay/start link and its parameters name no 3-D Secure PaReq and hold no compressed value. A PaReq carries the card deflated, and any compressed value can, where a check that reads text never finds it. For any other answer, `authorize()` throws `PaymentOutcomeUnknownError` with reason `juspay_response_unconfirmed`, and the page never gets it. Agentcard's API checks the answer the same way before it stores it, whatever SDK version asks, so this is a second check behind it.
|
|
68
|
+
|
|
69
|
+
- Read Juspay's transaction id the same way. The page gets the transaction id beside the bank redirect, so a `/txns` answer whose transaction id names a 3-D Secure PaReq or holds a compressed value now ends the same way, with `PaymentOutcomeUnknownError` and reason `juspay_response_unconfirmed`.
|
|
70
|
+
|
|
71
|
+
- Stop Payrexx Pay's 3-D Secure posts after an answer whose 3-D Secure step could carry the card. When Payrexx answers the card request by starting 3-D Secure, the frame posts the card again for the authentication and the post-authentication, and the cardholder's device sends each post with the real card. An answer whose method or ACS address, or any of its members, names a 3-D Secure PaReq or holds a compressed value no longer opens those posts: each one fails in the browser, and the checkout reads `unsupported` with reason `unsupported_payrexx_card_resend`. Agentcard's API judges the answer the same way before it hands one over, so this is a second check behind it.
|
|
72
|
+
|
|
7
73
|
## 0.23.27
|
|
8
74
|
|
|
9
75
|
- Pause Pin Payments' card request for the cardholder's device, for a company Agentcard turned Pin Payments on for. Pin's Hosted Fields (its control frame on `cdn.pinpayments.com`) and Pin.js on a merchant's own page send the card as JSON to `https://api.pinpayments.com/1/cards` under the merchant's publishable key, and Pin answers with a card token the merchant's server charges afterwards. The sync now also asks for `capabilities=pin_payments_card_token`, and the API serves Pin's recognizer only for a company Agentcard turned Pin on for. The SDK pauses only the card fields' own request at that exact URL: a secret key, an amount or any other body there fails in your browser before anything is created, and Pin's JSONP form and a card's own record are never paused. The cardholder's device sends the request to Pin itself with the real card, and the page gets Pin's card token. Pin's request names no price, so pass `amount` and `currency`, above zero: without them `authorize()` throws `AmountRequiredError` before anything is created. The API pays only a Pin account Agentcard reviewed for the checkout page the request comes from. Matching API and Vault releases are required. Pin Payments stays off for every company Agentcard has not turned it on for.
|
package/PREFLIGHT.md
CHANGED
|
@@ -219,9 +219,13 @@ Paygent (`paygent`) has no discovery rule in this table yet. Every Paygent card
|
|
|
219
219
|
|
|
220
220
|
Openpay (`openpay_bbva`) has no discovery rule in this table yet. Openpay merchants load `openpay.js` on pages that take no card, some on every page, so the script on a page is no evidence that a payment can be approved, and an Openpay checkout stays `unknown`. The direct SDK pauses the token request `openpay.js` sends to `api.openpay.mx/v1/<merchant id>/tokens` only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled. Read the Openpay row of the [README](./README.md) processor table for what the SDK covers.
|
|
221
221
|
|
|
222
|
+
VTEX Payments (`vtex_payments`) has no discovery rule in this table yet. One class of vault hosts serves every VTEX store, so a rule would name every VTEX checkout while no VTEX payment can be approved yet. The direct SDK pauses the card post Ollie's checkout sends to `<account>.vtexvault.com/api/payments/transactions/<id>/payments` only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled.
|
|
223
|
+
|
|
224
|
+
ProcessOut (`processout`) has no discovery rule in this table yet. ProcessOut.js loads the same card frame from `js.processout.com` for every project, live or sandbox, so the frame on a page names no store Agentcard reviewed, and a ProcessOut checkout stays `unknown`. The direct SDK pauses the card request the frame sends to `api.processout.com/cards` only after `syncRegistry()`, only through a prepared checkout, and only for the company Agentcard turned ProcessOut on for. Read the ProcessOut row of the [README](./README.md) processor table for what the SDK covers.
|
|
225
|
+
|
|
222
226
|
The bundled direct SDK profile declares processor support for `one_time` purchases. The catalog carries each processor's request limitations and known exclusions. A detection rule does not cover every product or regional variant sold under the processor's name. For example, Moneris Hosted Tokenization and Moneris Checkout use different payment paths; the implemented adapter covers Hosted Tokenization.
|
|
223
227
|
|
|
224
|
-
The catalog also carries reviewed hosted, regional, and versioned variants observed on live checkouts: Adyen secured fields on the regional live hosts and any release, the Square card element frame, dLocal Smart Fields releases, Mercado Pago secure fields and the guest Checkout Pro card form, and the Rapyd hosted checkout page. A variant the implemented adapter does not admit, such as the Tranzila terminal-root page, the Authorize.Net hosted payment form, or a Worldpay hosted payment page
|
|
228
|
+
The catalog also carries reviewed hosted, regional, and versioned variants observed on live checkouts: Adyen secured fields on the regional live hosts and any release, the Square card element frame, dLocal Smart Fields releases, the card fields of every Bambora Custom Checkout 1 release on `libs.na.bambora.com`, Mercado Pago secure fields and the guest Checkout Pro card form, and the Rapyd hosted checkout page. A variant the implemented adapter does not admit, such as the Tranzila terminal-root page, the Authorize.Net hosted payment form, or a Worldpay hosted payment page on `secure.worldpay.com` or `payments.worldpay.com`, identifies its processor and returns `unknown` with `processor_variant_unverified`. A new fingerprint never declares payment support by itself.
|
|
225
229
|
|
|
226
230
|
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).
|
|
227
231
|
|
|
@@ -361,6 +365,8 @@ Collect only the documented asset observations when using your own browser tools
|
|
|
361
365
|
|
|
362
366
|
The default collection budget is `1500` milliseconds, with at most `64` frames, `2048` signals, and `262144` serialized snapshot bytes. Set lower bounds through `collection.timeoutMs`, `collection.maxFrames`, `collection.maxSignals`, or `collection.maxSnapshotBytes` on `inspectCheckout`. The maximum time budget is `10000` milliseconds; the remaining limits cannot exceed their defaults. The minimum snapshot budget is `512` bytes.
|
|
363
367
|
|
|
368
|
+
A script, iframe or form location, or a frame's own address, that no reviewed rule can match, such as a store's own scripts, counts against neither the signal limit nor the snapshot budget. A checkout that loads a thousand of its own scripts is still read through to its card frames. The collector examines at most `16384` script, iframe and form elements in one frame; a frame with more reads as `unknown` with `signal_limit`.
|
|
369
|
+
|
|
364
370
|
Frames are read one nesting level at a time with up to eight frames in flight, and each frame handle is released while its document read proceeds. A remote CDP connection therefore pays about three round trips per nesting level plus one per eight frames, rather than four round trips per frame. Over a remote connection, allow `3000` to `5000` milliseconds through `collection.timeoutMs`, inspect after the payment controls have loaded, and read `observation.reasons` before retrying. A longer budget cannot repair a scan that started before the checkout settled.
|
|
365
371
|
|
|
366
372
|
## Validate without a purchase
|
package/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
# @agent-cards/sdk
|
|
2
2
|
|
|
3
|
-
Agentcard's SDK: one client, `Agentcard`, with
|
|
3
|
+
Agentcard's SDK: one client, `Agentcard`, with three modules. The directory in the monorepo is still `packages/checkout`; the package on npm is `@agent-cards/sdk`. `@agent-cards/checkout` is its older name and re-exports this package, entry for entry.
|
|
4
4
|
|
|
5
5
|
| Module | What it does |
|
|
6
6
|
|---|---|
|
|
7
7
|
| `agentcard.checkout` | Pauses a browser purchase's card request, has the cardholder approve it, and replays the processor's answer; creates a purchase directly; the recognisers and the request builders |
|
|
8
|
-
| `agentcard.
|
|
8
|
+
| `agentcard.buy` | Asks the Purchase API for a cart for a person you connected, places it by its hash, and reads the conversation |
|
|
9
|
+
| `agentcard.approvals` | Creates a purchase for a person you connected, which they approve on Agentcard's page, and reads or waits for their decision |
|
|
9
10
|
|
|
10
11
|
Card data never enters your process, your logs or your network: your server holds a placeholder card and a token, and the person's device supplies the real card.
|
|
11
12
|
|
|
@@ -15,73 +16,9 @@ Card data never enters your process, your logs or your network: your server hold
|
|
|
15
16
|
npm i @agent-cards/sdk
|
|
16
17
|
```
|
|
17
18
|
|
|
18
|
-
Node 22 or any runtime with `fetch
|
|
19
|
+
Node 22 or any runtime with `fetch` and WebCrypto. Zero runtime dependencies. One base URL, `https://api.agentcard.sh`; sandbox and live are decided by your credentials.
|
|
19
20
|
|
|
20
|
-
##
|
|
21
|
-
|
|
22
|
-
A person connects their card to your app once, then approves a purchase by voice. Six lines, against a sandbox client.
|
|
23
|
-
|
|
24
|
-
```js
|
|
25
|
-
import { Agentcard } from '@agent-cards/sdk';
|
|
26
|
-
|
|
27
|
-
const agentcard = new Agentcard({ clientId: process.env.AGENTCARD_CLIENT_ID, clientSecret: process.env.AGENTCARD_CLIENT_SECRET, voice: { approval: 'sheet', returnUrl: 'yourapp://connected' } });
|
|
28
|
-
|
|
29
|
-
const link = await agentcard.voice.connect(); // the app opens link.url in its sheet
|
|
30
|
-
const person = await agentcard.voice.completeConnect(returnLink); // the link the sheet came back with
|
|
31
|
-
const purchase = await agentcard.voice.authorizations.create({ user_id: person.user_id, merchant: 'Coffee', amount: 1250, currency: 'usd', request: 'sandbox' });
|
|
32
|
-
const { card, cards } = purchase; // the card that pays and why; cards are the others, for authorizations.setCard(purchase.id, id)
|
|
33
|
-
const result = await agentcard.voice.awaitResult(purchase.id); // { status: 'approved', approved_with: 'voice' }
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
The app opens `purchase.approval_url` in its sheet for the approval (see `@agent-cards/voice`). Sandbox and live are decided by the credentials: the token exchange names the client's mode and the client reads it (`agentcard.sandbox`), so nothing here says which one this is. `request: 'sandbox'` is the first-run form: it names no processor, the sandbox completes it as a sandbox charge, and only a sandbox client may send it. At go-live the credentials change and the request becomes the processor's own card request: one a browser purchase paused on, or one `agentcard.checkout.requests.build(...)` made for your order. `voice.returnUrl` is exactly one of the redirect links registered on your client. `readme.test.mjs` runs this block against a stub on every test run.
|
|
37
|
-
|
|
38
|
-
### A returning person
|
|
39
|
-
|
|
40
|
-
Store `person.user_id`. On a later launch ask what they already have, and open the Vault only for what is missing.
|
|
41
|
-
|
|
42
|
-
```js
|
|
43
|
-
const who = await agentcard.voice.people.get(storedUserId); // { user_id, card, voice: ['en'], rule, passkey }
|
|
44
|
-
if (!who.card || !who.voice.includes('en')) {
|
|
45
|
-
const link = await agentcard.voice.connect({ voice: 'required' }); // the Vault shows only the missing step
|
|
46
|
-
}
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
`connection_not_found` means the person never connected through your client (or disconnected): treat them as new.
|
|
50
|
-
|
|
51
|
-
### Approving inside your app
|
|
52
|
-
|
|
53
|
-
`voice: { approval: 'sheet' }`: the person approves on Agentcard's page, opened in your app for one moment; `purchase.approval_url` (or `voice.approvals.approvalUrl(id)`) is the link. `voice: { approval: 'in_app' }`: hold-to-talk in your app, judged on the phone and by Agentcard, signed by a key in your app. The server then mints the key word as one sealed bundle the app forwards unread:
|
|
54
|
-
|
|
55
|
-
```js
|
|
56
|
-
const bundle = await agentcard.voice.challengeForHost(purchase.id, { lang: 'es' }); // to the app's VoiceHost.approve, unread
|
|
57
|
-
const reg = await agentcard.voice.appKeys.register(key, { user_id: person.user_id }); // the key the app's frame minted; reg.credential goes back to the phone
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
One choice per business, not per purchase. Leave it out and the client approves on Agentcard's page, as with `approval: 'sheet'`; a value that is neither `'sheet'` nor `'in_app'` throws `missing_approval_posture` at construction.
|
|
61
|
-
|
|
62
|
-
### The card that pays
|
|
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`. 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
|
-
|
|
66
|
-
### Where the key word goes
|
|
67
|
-
|
|
68
|
-
`challengeForHost(id, { delivery })` names where the key word goes, and the bundle carries both of Agentcard's lines either way: `say` with the word in it, `say_private` pointing at the screen. `delivery: 'screen'`: the app shows `word`, speaks `say_private`, and the word never leaves the phone as sound. `delivery: 'spoken'`: the app speaks `say`, or plays `audio_url` (`audio_delivery` says which form it carries), so a purchase completes with no look at the screen; the word stays on the screen as well. `voice.challenge` takes the same option.
|
|
69
|
-
|
|
70
|
-
The choice is yours, and it has a cost. Spoken through the loudspeaker, a nearby recorder hears the fresh word before the person has said it. Over headphones or the earpiece it stays with them. The app half reads the audio route (this package cannot) and names it on the streamed answer as `channel`; the `@agent-cards/voice` README says how the reference app reads it.
|
|
71
|
-
|
|
72
|
-
### A bot with no screen
|
|
73
|
-
|
|
74
|
-
A messaging bot or an agent with no screen in front of the person sends `link.url` or `purchase.approval_url` as a text itself; the person opens it on their phone. Leave `return_url` out on such a connect: the Vault ends on "Connected", the link polls (`grant: 'poll'`), and `voice.awaitConnected(link.session_id)` reads the result.
|
|
75
|
-
|
|
76
|
-
### Languages
|
|
77
|
-
|
|
78
|
-
A language is a BCP 47 tag, and the list Agentcard speaks is data: `await agentcard.voice.languages()` reads it (`[{ tag: 'en', name: 'English' }, { tag: 'es', name: 'Español' }]`), and every call that names a `lang` checks the tag against it first, refusing one not on the list with `unsupported_language` before anything is sent. A language added on Agentcard's side needs no new release of this package.
|
|
79
|
-
|
|
80
|
-
### Reading, without waiting
|
|
81
|
-
|
|
82
|
-
`voice.awaitResult`, `voice.awaitConnected` and `voice.actions.awaitDone` wait. For one read with no wait, from a handler that answers a poll: `voice.authorizations.get(id)`, `voice.sessions.get(session_id)`, `voice.actions.get(session_id)`. Every read waits out the API's `slow_down` for you. Passing `timeout_ms: 0` to an await verb is not a read; use the read verbs.
|
|
83
|
-
|
|
84
|
-
### Webhooks
|
|
21
|
+
## Webhooks
|
|
85
22
|
|
|
86
23
|
Agentcard signs every delivery. Verify against the raw bytes and branch on `type`:
|
|
87
24
|
|
|
@@ -94,35 +31,40 @@ if (isCheckoutAuthorizationEvent(event) && event.type === 'checkout_authorizatio
|
|
|
94
31
|
|
|
95
32
|
More than one secret is accepted, so a secret rotates without a missed event; a stale timestamp, a malformed header and a wrong signature each throw an `AgentcardError` with its own code. The event unions are `CheckoutAuthorizationEvent` (`checkout_authorization.*`) and `VaultEvent` (`vault.*`); an event of a type this release does not name arrives as `OtherEvent`, so branch on `type` and read `data` yourself.
|
|
96
33
|
|
|
97
|
-
##
|
|
34
|
+
## Connect a person
|
|
35
|
+
|
|
36
|
+
Every purchase names the person who pays: `user_id` on `agentcard.buy`, `user` on `agentcard.checkout`. A person gets that id when they store a card in the Vault from a link your server mints:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
curl -X POST https://api.agentcard.sh/api/v2/vault_sessions -H "Authorization: Bearer $ORG_TOKEN" -H "Content-Type: application/json" -d '{}'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Send the person the answer's `url`. When they finish, the `vault.session_linked` webhook carries their `user_id`, and `GET /api/v2/vault_sessions/{id}` returns it once `status` is `linked`. Store it, because every call below names it. `$ORG_TOKEN` is your client's token from `POST /api/v2/oauth/token`. The [Vault quickstart](https://docs.agentcard.sh/vault/quickstart) covers the whole flow, test cards included.
|
|
98
43
|
|
|
99
|
-
|
|
44
|
+
## Buy through the Purchase API
|
|
45
|
+
|
|
46
|
+
`agentcard.buy` is Agentcard's Purchase API from the same client: it finds the merchant, builds the cart and places the order for a person you connected. `user_id` is the id from [Connect a person](#connect-a-person).
|
|
100
47
|
|
|
101
48
|
```js
|
|
102
|
-
const purchase = await agentcard.voice.authorizations.create({ user_id, merchant: 'Coffee', amount: 650, currency: 'usd', request: 'sandbox' });
|
|
103
|
-
const result = await agentcard.voice.awaitResult(purchase.id); // { status: 'approved', approved_with: 'voice' }
|
|
104
49
|
const turn = await agentcard.buy.ask({ user_id, ask: 'a flat white from Coffee to my desk' }); // { status: 'needs_input', cart: { hash, totalCents, items } }
|
|
105
|
-
const placed = await agentcard.buy.confirm({ user_id, conversation_id: turn.conversation_id, confirm: turn.cart.hash
|
|
50
|
+
const placed = await agentcard.buy.confirm({ user_id, conversation_id: turn.conversation_id, confirm: turn.cart.hash });
|
|
106
51
|
const state = await agentcard.buy.conversation({ user_id, conversation_id: turn.conversation_id }); // { turn_in_progress: false, orders: [{ order_id }] }
|
|
107
52
|
if (placed.status !== 'order_placed') console.log(placed.status, placed.decline_code); // 'declined' names why; 'needs_input' is a question, not a failure
|
|
108
53
|
```
|
|
109
54
|
|
|
110
55
|
Every call names `user_id`; a person not connected to your client is refused with `user_not_connected`. `buy.ask` answers one turn: `status` is `needs_input` (a question, or a cart waiting for its confirm), `order_placed`, `partially_placed` (several carts, some placed; each one's outcome is in `placements`) or `declined` (with `decline_code`). Read `turn.cart.hash` and never the prose; a cart that changed since its hash was issued refuses the confirm with `cart_changed`, and the fresh carts ride on the error. `buy.confirm` with `authorization_id` places against the approval the person gave: one that is not approved, is for another purchase, or already paid an order is refused with `authorization_not_approved`, `authorization_mismatch` or `authorization_spent`, and nothing is charged; the answer carries `authorization_id` back. Without it the confirm runs as before, with `payment_source: 'vault'` for the person's own card. A turn runs against live merchants, so this call waits up to 130 seconds and is sent once, never retried; a turn still running past the API's own budget answers `error_code: 'turn_timeout'` with the carts it had shown, and `buy.conversation` reads whether it is done (`turn_in_progress`) and every order it placed. Refusals are a `BuyError`, an `AgentcardError` with the API's code; `isBuyDeclined(turn)` narrows a declined answer. `buy.test.mjs` runs this block against a stub on every test run.
|
|
111
56
|
|
|
112
|
-
## Approve a purchase
|
|
57
|
+
## Approve a purchase on Agentcard's page
|
|
113
58
|
|
|
114
|
-
|
|
59
|
+
`agentcard.approvals` creates a purchase for a person you connected and has them approve it on Agentcard's page. `user_id` is the id from [Connect a person](#connect-a-person); `request` is the processor's card request the purchase pays, or `'sandbox'` under a sandbox client.
|
|
115
60
|
|
|
116
61
|
```js
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}); // 'order_placed', 'declined', or 'voice_execution_unsupported'
|
|
121
|
-
if (placed.status === 'declined') console.log(placed.decline_code); // 'voice_declined' | 'approval_expired'
|
|
122
|
-
if (placed.status === 'voice_execution_unsupported') console.log(placed.error.message); // take your own path; nothing was charged
|
|
62
|
+
const purchase = await agentcard.approvals.create({ user_id, merchant: 'Coffee', amount: 1250, currency: 'usd', request: 'sandbox' });
|
|
63
|
+
// Send the person purchase.approval_url: they approve with Face ID, Touch ID or their master password.
|
|
64
|
+
const result = await agentcard.approvals.awaitResult(purchase.id); // { status: 'approved', approved_with: 'passkey' }
|
|
123
65
|
```
|
|
124
66
|
|
|
125
|
-
`
|
|
67
|
+
`approvals.get(id)` reads where a purchase stands without waiting, and `approvals.cancel(id, { reason })` withdraws one the person has not decided, with `person_closed_sheet`, `person_changed_mind` or `business_cancelled`. `awaitResult` polls no faster than the interval the purchase names, waits out `slow_down`, and answers `{ status: 'timeout' }` after 60 seconds unless `timeout_ms` names another deadline. A live client refuses `request: 'sandbox'` with `sandbox_request_in_live` before any call.
|
|
126
68
|
|
|
127
69
|
## Browser purchases
|
|
128
70
|
|
|
@@ -176,10 +118,6 @@ await agentcard.checkout.attachToCdp(cdp, pageSessionId, { user: 'usr_123', merc
|
|
|
176
118
|
|
|
177
119
|
Merchant timeouts still apply to the request itself: Square's observed tokenization deadline is about 10 seconds, Braintree's native request timeout is 60 seconds, and Adyen Web's own request timeout abandons its Sessions payment call 60 seconds after Pay. These are the processors' limits; the SDK's own authorization wait stays 15 minutes. The approval outlives the page's own request: when the page's script gives up on its paused card request while the cardholder is still deciding, the SDK keeps the approval pending (state `awaiting_approval`, reason `merchant_request_lost`) and answers the page's next request for the same purchase from it; when the approval lands first, the controller reads `ready_to_submit` with reason `awaiting_merchant_retry`, and one more Pay click completes the purchase. The wait for that request is bounded (`merchantRetryWaitMs`, two minutes by default); an approval the page never asks for again is retired as `merchant_never_retried`. For a short-deadline processor, `controller.prepare()` before the first Pay finishes the request inside its own deadline. A Shiji STS getTokenId is the exception: its approval answers only the request it was created from, and the SDK cancels it as `merchant_request_aborted` when the page abandons that request.
|
|
178
120
|
|
|
179
|
-
### Pause only, and let your server create
|
|
180
|
-
|
|
181
|
-
`attachToCdp(…, { pauseOnly: true, onPaused })` pauses the card request without creating the purchase and hands you `{ request, recognizer, page_origin, controller }`. Create it with `voice.authorizations.create` as on any other purchase, read the result, and `controller.resume(result)` replays the processor's answer into the page. `recognizer` names the processor that claimed the request (`stripe`), and so does the `card_request_paused` event's detail, so nothing has to re-match the URL.
|
|
182
|
-
|
|
183
121
|
### Hold the card request until the person confirms the total
|
|
184
122
|
|
|
185
123
|
`attachToCdp(…, { hold: true })` pauses the first card request and holds it with nothing created. `controller.hold.whenHeld()` resolves with what was held (the processor and the endpoint, never a body). Once the person confirmed the total and the page still shows it, `controller.release({ amount, currency, pageTotal })` creates the authorization at that amount: a live client answers the page with the device's replay, a sandbox client never sends the request on and resolves `approved_not_charged`. A page total that moved, or one you could not read (`pageTotal: null`), cancels the request instead. `upTo: true` releases at a ceiling while the page total stays at or under it. `controller.hold.cancel()` fails the request. A held request is cancelled before its processor's own wait runs out (Braintree 60 s, Bolt 20 s, 30 s for the rest), and every other card request, and every one after a decision, fails at once.
|
|
@@ -204,13 +142,7 @@ The three shipped builders are the Stripe shapes Agentcard completes from a dire
|
|
|
204
142
|
|
|
205
143
|
### Moving from `@agent-cards/checkout` 0.x
|
|
206
144
|
|
|
207
|
-
`@agent-cards/checkout` is this package's older name and re-exports it for one release.
|
|
208
|
-
|
|
209
|
-
## The paused request
|
|
210
|
-
|
|
211
|
-
A purchase's `status` is one of `awaiting_approval`, `approved`, `declined` (the person said no, a rule or the processor refused: `terminal_reason` says which), `cancelled` (withdrawn before a decision, by you or by the checkout runtime), `expired`, or, from `awaitResult` alone, `timeout` at your deadline. `voice.authorizations.cancel(id, { reason })` withdraws a purchase the person has not decided yet, in your words (`person_closed_sheet`, `person_changed_mind`, `business_cancelled`). `checkout_origin` on the create (or the paused request's `origin` header) is what an auto-approval rule needs to take the purchase; without it the create answers `execution_reason: 'unverified_origin'`. On a finished purchase `awaitResult` and `authorizations.get` carry `settlement`: whether the charge settled, as Agentcard read it back from the processor (`status: 'settled' | 'not_settled' | 'unknown'`, `final`, `settled_amount`, `message`).
|
|
212
|
-
|
|
213
|
-
The connect link is an OAuth authorization grant: `voice.connect` mints `state` and a PKCE challenge, the sheet's return link carries `code` and `state`, and `voice.completeConnect(url)` exchanges it once, within 60 seconds, refusing a `state` it did not mint before any call. When another server instance minted the link, pass the `code_verifier` and `expected_state` that `connect` returned there. A mint with no return link polls (`grant: 'poll'`, then `awaitConnected`). `readReturnLink(url)` reads a return link's `code`, `state` and `voice_reset`. `voice.connect({ landing: 'voice_options' })` mints a link that lands the signed-in person on the Vault's Voice options page (the voice key, its reset, the language, the passkey it rests on) for an app's own settings row; it collects nothing and polls.
|
|
145
|
+
`@agent-cards/checkout` is this package's older name and re-exports it for one release. `VaultClient` constructs the same client as `Agentcard`; every top-level checkout verb (`syncRegistry`, `authorize`, `isCardRequest`, …) is also under `agentcard.checkout`.
|
|
214
146
|
|
|
215
147
|
## Credentials
|
|
216
148
|
|
|
@@ -294,6 +226,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
|
|
|
294
226
|
| 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" |
|
|
295
227
|
| Paddle checkout (Paddle's card vault or TokenEx; any Paddle seller) | dark launch: after `vault.syncRegistry()`, and only for an org Agentcard has enabled. On a Paddle checkout whose card method names VGS, pay only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the paying card; without one, `prepare()` fails with `card_required`. The frame's post to `vault.paddle.com/cards` pauses, the cardholder's device sends the card itself, and the frame receives the stored card's id; its second request after its 10-second timeout gets the same answer. Type `placeholderCard()`'s facts for that card into Paddle's form, and pass `amount` and `currency`, the total the cardholder approves. Only Visa, Mastercard and American Express cards are paid (`paddle_vgs_card_unsupported` otherwise), and a typed expiry or placeholder that is not the card's fails the create with `paddle_vgs_card_mismatch`. On the live vault, Agentcard pays a checkout at any Paddle seller's own https page; from any other page, such as `http://localhost:4000`, `prepare()` fails with `unsupported_checkout`. Paddle's vault keeps the card, so Paddle can charge it again without another approval, at a subscription's renewal for example. Paddle's `/pay` that follows names the approved card's first six and last four digits, expiry and brand: the SDK writes them over the ones Paddle's form read from the placeholder, and aborts a `/pay` that names another stored card or comes more than a minute later. On a Paddle checkout whose card method names TokenEx, call no `prepare()`: the SDK pauses Paddle's `/pay`, and once the cardholder approves it, writes the card the cardholder's device encrypted over the placeholder's. With a `paddle_vgs` preparation on the attachment, the SDK refuses that `/pay` with `checkout_changed`. A Paddle checkout that sends the card through Spreedly is not supported |
|
|
296
228
|
| Paygent (`PaygentToken.js`; Fundex donation forms) | dark launch: only for an org Agentcard enabled, after `vault.syncRegistry()`, and only through `prepare({ psp: 'paygent', environment: 'production' })` (`'sandbox'` for Paygent's test host) before the press that sends the card: the script gives up on its token request 35 seconds after sending it. The request to `token.paygent.co.jp/n/token/request` pauses, the cardholder's device sends the card itself, and the page receives Paygent's single-use token. One approval sends one card request, so the SDK refuses a second press, and a request for a stored card's security code too. Pass `amount` and `currency`, the total the cardholder approves. On the live host the API accepts only a reviewed merchant at its reviewed page, under its reviewed name, merchant id and token key, and in its currency (Tokyo Chinese School at `https://fundexapp.jp`, in yen), and refuses any other before anyone is asked. The merchant runs 3-D Secure after the token in the agent's browser. No live payment has gone through this route yet |
|
|
229
|
+
| ProcessOut (ProcessOut.js's card frame on `js.processout.com`) | dark launch: only for the company Agentcard turned ProcessOut on for, after `vault.syncRegistry()`, and only through `prepare({ psp: 'processout', environment: 'production' })` (`'sandbox'` for a `test-proj_` project) before the press that asks the frame for a token: the frame calls the page's error callback 20 seconds after that press. The frame's post to `api.processout.com/cards` pauses, the cardholder's device sends the card to ProcessOut itself, and the frame receives ProcessOut's card token, rebuilt from the card facts the page reads. A refusal inside a 2xx answer never reaches the frame as a token, and a request still unsent 12 seconds after the pause fails before the card leaves (`processout_send_window_closed`). Pass `amount` and `currency` above zero, the total the cardholder approves. A live project pays only a store Agentcard reviewed, at its reviewed page and under its reviewed name and project id, and Agentcard has reviewed none yet, so a live `prepare()` fails with `unsupported_checkout`. No ProcessOut payment has gone through this route yet |
|
|
297
230
|
|
|
298
231
|
The recognizer list, and the status of each reviewed Adyen merchant profile on
|
|
299
232
|
your client, are fetched from the API at runtime (`agentcard.checkout.syncRegistry()`),
|
|
@@ -301,7 +234,7 @@ so new processors work without you shipping a release. `attachToCdp` derives the
|
|
|
301
234
|
`Fetch.enable` url patterns from that same list rather than a constant, which is
|
|
302
235
|
why the sync call belongs before the attach. `agentcard.checkout.cardUrlPatterns()` returns
|
|
303
236
|
those patterns if you arm a CDP connection yourself. Call
|
|
304
|
-
`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,moneris_hosted_tokenization,paddle_vgs_cards,paddle_tokenex_pay,redsys_hosted_payment_page,lyra_krypton_payment,payfast_card_pay,wix_payments_tokenize,paymongo_checkout_pay,paygent_token,pay_nl_card_token,hipay_vault_token,twoc2p_secure_pay,quickpay_hosted_payment_page,revolut_card_field,paypal_card_fields,gopay_card_form,payrexx_card_frame,comgate_hosted_payment_page,przelewy24_hosted_payment_page,makecommerce_card_frame,poynt_collect_nonce,tokenex_iframe_tokenize,transbank_webpay_hosted_payment_page,sumup_checkout_process,clover_card_token,eximbay_hosted_payment_page,toast_pinned_key,xendit_card_token,omise_card_token,paytrail_hosted_payment_page,fastspring_card_payment,openpay_tokenizer,barion_hosted_payment_page,paymob_checkout_pay,payu_latam_webcheckout_card,nexi_checkout_pay,pin_payments_card_token`
|
|
237
|
+
`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,moneris_hosted_tokenization,paddle_vgs_cards,paddle_tokenex_pay,redsys_hosted_payment_page,lyra_krypton_payment,payfast_card_pay,wix_payments_tokenize,paymongo_checkout_pay,paygent_token,pay_nl_card_token,hipay_vault_token,twoc2p_secure_pay,quickpay_hosted_payment_page,revolut_card_field,paypal_card_fields,gopay_card_form,payrexx_card_frame,comgate_hosted_payment_page,przelewy24_hosted_payment_page,makecommerce_card_frame,poynt_collect_nonce,tokenex_iframe_tokenize,transbank_webpay_hosted_payment_page,sumup_checkout_process,clover_card_token,eximbay_hosted_payment_page,toast_pinned_key,xendit_card_token,omise_card_token,paytrail_hosted_payment_page,fastspring_card_payment,openpay_tokenizer,barion_hosted_payment_page,paymob_checkout_pay,payu_latam_webcheckout_card,nexi_checkout_pay,pin_payments_card_token,paymentwall_card_token,oceanpayment_hosted_payment_page,payplug_hosted_payment_page,processout_card_token,aps_custom_merchant_page,bluesnap_hosted_payment_fields,iyzico_checkout_form,midtrans_card_token,sbps_token_jsonp,tap_card_sdk_token,usaepay_payment_key,vtex_payments_card_payment`
|
|
305
238
|
for the list this build syncs. The API serves Fiserv's card capture only to a
|
|
306
239
|
build that lists `fiserv_card_capture`, and only for a company Agentcard turned
|
|
307
240
|
Fiserv on for. An older SDK never pauses a Fiserv request it could not finish,
|
|
@@ -380,6 +313,19 @@ this build pauses the gateway link `secure.barion.com/Pay?Id=<payment id>` for
|
|
|
380
313
|
the cardholder's device and fails every post a page it watches makes to the
|
|
381
314
|
gateway. A merchant's server often sends the buyer to that link with a
|
|
382
315
|
redirect, and only `attachToCdp` pauses a redirect.
|
|
316
|
+
PayPlug's payment page (`payplug_hosted_payment_page`) is served the same way:
|
|
317
|
+
this build pauses the page link `secure.payplug.com/pay/<payment id>` (or
|
|
318
|
+
`/pay/test/<payment id>`) for the cardholder's device and fails every post a
|
|
319
|
+
page it watches makes to PayPlug's page host. The link names no amount, so
|
|
320
|
+
`authorize()` needs your `amount`, above zero, and the API takes it in euros
|
|
321
|
+
alone (`currency: 'eur'`), the one currency PayPlug's page charges. One PayPlug
|
|
322
|
+
payment takes one authorization across your company: a second one while the
|
|
323
|
+
first awaits answers `duplicate_submission`, and one for a payment a card may
|
|
324
|
+
already have gone to answers `payplug_payment_already_sent`. The API serves
|
|
325
|
+
PayPlug to Agentcard's own company alone for now, and takes no PayPlug payment
|
|
326
|
+
until Agentcard has reviewed the name and the PayPlug account a merchant's
|
|
327
|
+
PayPlug page shows: until then it refuses every PayPlug link with
|
|
328
|
+
`seller_unreviewed`, which is expected and not a setup error.
|
|
383
329
|
Revolut Business's card field (`revolut_card_field`) is served on the same
|
|
384
330
|
terms: this build pauses the frame's card payment on `merchant.revolut.com`,
|
|
385
331
|
the cardholder's device sends it to Revolut itself, and the frame gets
|
|
@@ -429,6 +375,22 @@ page, the cardholder's device sends it to Omise itself with the real card, and
|
|
|
429
375
|
the page gets Omise's token. The API pays only a merchant Agentcard reviewed
|
|
430
376
|
for the page the request comes from. Omise's request names no price, so pass
|
|
431
377
|
`amount` and `currency`, above zero: that is the amount the cardholder approves.
|
|
378
|
+
VTEX Payments' card post (`vtex_payments_card_payment`) is served on the same
|
|
379
|
+
terms, and only through `prepare({ psp: 'vtex_payments', environment: 'production' })`
|
|
380
|
+
before Pay: Ollie's headless checkout, which some VTEX stores run, posts the
|
|
381
|
+
card to the store's own VTEX vault, `<account>.vtexvault.com`, and gives the
|
|
382
|
+
post 12 seconds. This build pauses that post, binds it to the approval, and
|
|
383
|
+
refuses each later post on the same approval, Ollie's resends and its post to
|
|
384
|
+
`api.vtexvault.com` included, so one approval sends the card once. The post
|
|
385
|
+
names its price, which the cardholder approves; VTEX answers it with 201 and no
|
|
386
|
+
body, so the payment's result comes from the checkout's next step, not from
|
|
387
|
+
this answer. The API pays only a store Agentcard reviewed for the page. A paused
|
|
388
|
+
post is read every 500 ms while Ollie waits, and one the cardholder's device has
|
|
389
|
+
not sent 8 seconds after the pause fails in your browser with nothing sent
|
|
390
|
+
(`CheckoutPreparationError`, `vtex_payments_send_window_closed`). A direct
|
|
391
|
+
`authorize()` of that post names `pausedAt`, the moment it paused in epoch
|
|
392
|
+
milliseconds. The adapters fill it, and a call without it throws before
|
|
393
|
+
anything is created.
|
|
432
394
|
Pin Payments' card request (`pin_payments_card_token`) is served on the same
|
|
433
395
|
terms: this build pauses the card request Pin's Hosted Fields or Pin.js sends to
|
|
434
396
|
`https://api.pinpayments.com/1/cards`, the cardholder's device sends it to Pin
|
|
@@ -437,6 +399,24 @@ merchant's server charges afterwards. The API pays only a Pin account Agentcard
|
|
|
437
399
|
reviewed for the page the request comes from. Pin's request names no price, so
|
|
438
400
|
pass `amount` and `currency`, above zero: that is the amount the cardholder
|
|
439
401
|
approves.
|
|
402
|
+
Paymentwall's card request (`paymentwall_card_token`) is served on the same
|
|
403
|
+
terms: this build pauses the card request Paymentwall's card form sends to
|
|
404
|
+
`https://pwgateway.com/api/token` (`https://api.paymentwall.com/api/brick/token`
|
|
405
|
+
under a test key), from Paymentwall's hosted checkout or a merchant's own page,
|
|
406
|
+
the cardholder's device sends it to Paymentwall itself with
|
|
407
|
+
the real card, and the page gets Paymentwall's one-time token, which the
|
|
408
|
+
checkout charges afterwards. The API pays only a Paymentwall merchant Agentcard
|
|
409
|
+
reviewed for the page the request comes from. Paymentwall's request names no
|
|
410
|
+
price, so pass `amount` and `currency`, above zero: that is the amount the
|
|
411
|
+
cardholder approves.
|
|
412
|
+
ProcessOut's card request (`processout_card_token`) is served on the same
|
|
413
|
+
terms, through a prepared checkout only: this build pauses the card request
|
|
414
|
+
ProcessOut.js's card frame sends to `https://api.processout.com/cards`, the
|
|
415
|
+
cardholder's device sends it to ProcessOut itself with the real card, and the
|
|
416
|
+
frame gets ProcessOut's card token, which the store's server charges
|
|
417
|
+
afterwards. The API pays only a store Agentcard reviewed for the page the
|
|
418
|
+
request comes from. ProcessOut's request names no price, so pass `amount` and
|
|
419
|
+
`currency`, above zero: that is the amount the cardholder approves.
|
|
440
420
|
FastSpring's checkout (`fastspring_card_payment`) is served on the same terms:
|
|
441
421
|
this build pauses the card payment the checkout posts to
|
|
442
422
|
`<store>.onfastspring.com/.../session/<id>/payment`, the cardholder's device
|
|
@@ -472,6 +452,14 @@ checkout shows, which the API holds to the payment's own amount and the device
|
|
|
472
452
|
holds to Nexi's record. The SDK sends the checkout page's origin as
|
|
473
453
|
`checkout_origin`, which for Nexi's hosted page is `https://checkout.dibspayment.eu`,
|
|
474
454
|
and the API pays only a merchant reviewed for that page.
|
|
455
|
+
Oceanpayment's hosted payment page (`oceanpayment_hosted_payment_page`) is served
|
|
456
|
+
on the same terms: this build pauses the card post the page submits to
|
|
457
|
+
`https://secure.oceanpayment.com/gateway/sendPay;jsessionid=<session>`, aborts every
|
|
458
|
+
other post to Oceanpayment's hosts, and the cardholder's device writes the real card
|
|
459
|
+
into the paused post and sends it through Agentcard's checkout relay. The post
|
|
460
|
+
names no amount, so pass `amount` and `currency`, above zero: the total the page
|
|
461
|
+
shows. The API takes only a test payment on Oceanpayment's sandbox today, because
|
|
462
|
+
the post names neither the merchant nor the amount.
|
|
475
463
|
CyberSource Secure Acceptance's token
|
|
476
464
|
create (`cybersource_sa_token_create`) is served on the same terms too; every
|
|
477
465
|
other company keeps the stop every build applies to a card posted to Secure
|
|
@@ -636,7 +624,8 @@ mode; the difference matters if you drive `authorize()` yourself.
|
|
|
636
624
|
sends the card request to the merchant itself over Agentcard's checkout
|
|
637
625
|
relay and reports the merchant's answer; `authorize()` resolves with
|
|
638
626
|
`{ mode: 'card_endpoint', profile, status, headers, setCookies, body,
|
|
639
|
-
withheld }
|
|
627
|
+
withheld, approved }`, where `approved` is the amount the cardholder approved,
|
|
628
|
+
in the currency's smallest unit. The adapters write each `Set-Cookie` into your browser
|
|
640
629
|
host-only on the endpoint host and Secure, so none of them travels over
|
|
641
630
|
plain http. A `Set-Cookie` whose `Domain` names a parent of the endpoint
|
|
642
631
|
host that your browser already holds a cookie of that name on is written
|
|
@@ -689,6 +678,44 @@ mode; the difference matters if you drive `authorize()` yourself.
|
|
|
689
678
|
`profile` and the `endpoint` origin, never the request's URL, body or
|
|
690
679
|
session: the URL carries the merchant's cart and booking ids.
|
|
691
680
|
|
|
681
|
+
Nike's card frame stores the card under the checkout's id, and Nike charges
|
|
682
|
+
nothing until its page sends the order, `PUT
|
|
683
|
+
https://api.nike.com/buy/checkouts/v3/<checkoutId>`. So once an adapter asks
|
|
684
|
+
the cardholder to pay through Nike's profile, it holds every request to
|
|
685
|
+
`api.nike.com`. `attachToCdp` pauses it in every frame the attachment arms,
|
|
686
|
+
from the attach on, so a document already loaded when the cardholder is
|
|
687
|
+
asked is held too. At the ask it also arms every other page of the browser
|
|
688
|
+
context then open, and every frame in one, and the browser target, which
|
|
689
|
+
reaches a tab, frame, worker or shared worker this checkout did not open
|
|
690
|
+
once it loads after the ask: Chrome applies the browser-level pause only to
|
|
691
|
+
what loads after it. `attachToPlaywright` routes the page's browser context,
|
|
692
|
+
which reaches its pages, those already loaded included, but no shared or
|
|
693
|
+
service worker. A write there whose path a server may read as
|
|
694
|
+
under `/buy/checkouts/`, however it is spelled, is the order. One order goes
|
|
695
|
+
unchanged, only when its `request.totals.total`, a JSON number, and its
|
|
696
|
+
`request.currency` are the `approved` amount and the checkout still names
|
|
697
|
+
that approval as under way, and after it only a byte-identical retry. An
|
|
698
|
+
order that fails the amount rule, or comes while no approved amount was read,
|
|
699
|
+
fails in the browser, so Nike never charges the card: a checkout still under
|
|
700
|
+
way reads `failed` with a `merchant_order_` reason such as
|
|
701
|
+
`merchant_order_amount_mismatch`, and every later order under that approval
|
|
702
|
+
fails too. A second order, or one after the checkout was cancelled,
|
|
703
|
+
reconciled or retried, fails and leaves the state as it is. The events
|
|
704
|
+
`merchant_order_released` and `merchant_order_refused` name the
|
|
705
|
+
authorization, never the order. Every other request, and the order on an
|
|
706
|
+
attachment that never asked to pay through Nike's profile, continues
|
|
707
|
+
untouched. A browser that refuses the browser-level pause, a page or frame
|
|
708
|
+
`attachToCdp` cannot arm at the ask, or a context that refuses the route,
|
|
709
|
+
refuses the payment to Nike before anything is created. At the browser
|
|
710
|
+
target, in the pages armed at the ask and on Playwright's context route,
|
|
711
|
+
only the attachment that asked through Nike's profile last in that browser
|
|
712
|
+
context, for any attempt, judges Nike's order; in the frames it arms from
|
|
713
|
+
the attach on, each `attachToCdp` attachment judges every order itself. A
|
|
714
|
+
shared or service worker that started before the ask, which `attachToCdp`
|
|
715
|
+
lets run only when its `sharedWorkers` or `serviceWorkers` option allows
|
|
716
|
+
one, is not held. An attachment that has not asked holds nothing, and an
|
|
717
|
+
order sent from outside the browser never reaches the SDK.
|
|
718
|
+
|
|
692
719
|
Some of these merchants' pages submit the card form as a page load rather
|
|
693
720
|
than a fetch: the profile's `navigation` is `top`, or `frame` for a form
|
|
694
721
|
inside an iframe, which is then judged by the iframe's own document. The
|
|
@@ -742,7 +769,7 @@ mode; the difference matters if you drive `authorize()` yourself.
|
|
|
742
769
|
cannot finish is never paused; the API serves `hosted_form` and `device_encrypt`
|
|
743
770
|
entries only to callers that ask.
|
|
744
771
|
`syncRegistry()` also sends `SUPPORTED_CAPABILITIES`
|
|
745
|
-
(`capabilities
|
|
772
|
+
(the `capabilities=` list under "Supported processors"),
|
|
746
773
|
the dark-launch half of the same negotiation: the API serves a processor gated
|
|
747
774
|
behind a capability only to a build that declares it, for a company Agentcard
|
|
748
775
|
turned that processor on for, and the built-in list never holds one, so the SDK
|
|
@@ -1085,8 +1112,7 @@ It declares **no dependencies**, so the workspace
|
|
|
1085
1112
|
lockfile needs no importer entry for it (a new package with its own deps cannot
|
|
1086
1113
|
be installed here without regenerating the lockfile, and a full regen drifts
|
|
1087
1114
|
unrelated transitive versions). The build writes the SDK's copies of
|
|
1088
|
-
payment-core's rules (`src/*.generated.ts`)
|
|
1089
|
-
from `packages/voice/src/core/stream.ts`, and the Kernel kit's
|
|
1115
|
+
payment-core's rules (`src/*.generated.ts`) and the Kernel kit's
|
|
1090
1116
|
`examples/preflight/kernel-native/inventory.json`. Git ignores those files, so
|
|
1091
1117
|
build before you test, with the workspace TypeScript:
|
|
1092
1118
|
|
|
@@ -1280,7 +1306,7 @@ with the Card Element after `createPaymentMethod`) has it sent with every
|
|
|
1280
1306
|
`payment_method_data` field removed and the approved `payment_method` in their
|
|
1281
1307
|
place: the SDK asks the API with exactly that body and continues the page's
|
|
1282
1308
|
request with it. The API allows it only for an approval the cardholder gave on
|
|
1283
|
-
the Vault page,
|
|
1309
|
+
the Vault page, with no `amount` (a SetupIntent charges
|
|
1284
1310
|
nothing), paid with exactly the approved payment method on the same Stripe
|
|
1285
1311
|
account, and a SetupIntent Stripe reports still confirmable. Allowed, the SDK
|
|
1286
1312
|
emits `stripe_setup_continued` with
|
|
@@ -1462,6 +1488,18 @@ An attachment keeps no memory of an earlier one. The API reads each checkout fro
|
|
|
1462
1488
|
|
|
1463
1489
|
Once the card-number request is answered, ACI holds the real card on that checkout for up to 30 minutes, and the checkout id is what lets anyone post `/payment` against it, so event URLs show it as `{checkoutId}`. The `/payment` check runs in your browser: it holds an honest agent to the approved card, and the merchant's order state is the outcome to confirm. Autopilot is unavailable for ACI.
|
|
1464
1490
|
|
|
1491
|
+
### Pay a BlueSnap Hosted Payment Fields checkout
|
|
1492
|
+
|
|
1493
|
+
BlueSnap's Hosted Payment Fields send the card in three requests too: the card number frame, the expiry frame and the code frame each send their own field as a JSON `PUT` to the merchant's token, `https://ws1.bluesnap.com/services/2/payment-fields-tokens/<token>` (`ws2` for a token ending `_2`, `sandpay` for BlueSnap's sandbox). BlueSnap launches dark: `syncRegistry()` declares `bluesnap_hosted_payment_fields`, the API serves BlueSnap only to an SDK that declares it, and only for Agentcard's own company.
|
|
1494
|
+
|
|
1495
|
+
Pass `amount` and `currency`, above zero: the request names no price, so that is the amount the cardholder approves. Type a placeholder card of the paying card's network. The SDK:
|
|
1496
|
+
|
|
1497
|
+
- pauses the card number request, which the number frame sends when its field loses focus. The cardholder's device sends it to BlueSnap with the real number, expiry and code, and the page receives BlueSnap's card data.
|
|
1498
|
+
- holds the page's own expiry and code requests, which the frames send when the page submits, and answers them with that answer, so the placeholder values never reach BlueSnap (`split_card_leg_absorbed`). They fail when the card number request fails, or when none begins within 5 seconds (`split_card_leg_unanswered`).
|
|
1499
|
+
- refuses a second card number request on the same token (`split_card_reused`), a saved card's request (`bluesnap_saved_card`), and anything else on the token's URL. The page's own retry of the card number request, sent again while the cardholder is still deciding, gets the approval's answer.
|
|
1500
|
+
|
|
1501
|
+
The store's server charges the token afterwards, and its charge can ask BlueSnap to keep the card. So a live BlueSnap payment takes only a card the person stored, never one a company added (`card_not_stored`), and the approval says the store can keep it.
|
|
1502
|
+
|
|
1465
1503
|
### Use Checkout Pro in Mexico
|
|
1466
1504
|
|
|
1467
1505
|
Attach the SDK before entering card fields on `www.mercadopago.com.mx`, then prepare the guest card checkout:
|