@agent-cards/sdk 0.23.19 → 0.23.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/PREFLIGHT.md +2 -0
- package/README.md +34 -4
- package/dist/amadeus.d.ts +4 -2
- package/dist/amadeus.js +16 -3
- package/dist/buy.d.ts +84 -2
- package/dist/buy.js +16 -1
- package/dist/card-endpoint-facts.d.ts +2 -1
- package/dist/card-endpoint-facts.js +18 -9
- package/dist/card-endpoint.d.ts +20 -4
- package/dist/card-endpoint.js +38 -4
- package/dist/card-fields.generated.js +483 -17
- package/dist/cdp.d.ts +29 -0
- package/dist/cdp.js +302 -18
- package/dist/client.d.ts +30 -4
- package/dist/client.js +128 -13
- package/dist/hipay.generated.d.ts +56 -0
- package/dist/hipay.generated.js +389 -0
- package/dist/hold.d.ts +180 -0
- package/dist/hold.js +264 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -0
- package/dist/lifecycle.d.ts +10 -0
- package/dist/lifecycle.js +21 -0
- package/dist/merchant-card-endpoints.generated.js +102 -11
- package/dist/paddle-vgs.generated.d.ts +3 -2
- package/dist/paddle-vgs.generated.js +9 -5
- package/dist/pre-claim.d.ts +14 -0
- package/dist/pre-claim.js +76 -3
- package/dist/prepared-processor.d.ts +6 -2
- package/dist/prepared-processor.js +18 -2
- package/dist/primer.generated.js +510 -44
- package/dist/quickpay.d.ts +44 -0
- package/dist/quickpay.js +65 -0
- package/dist/stripe-checkout.generated.js +124 -2
- package/dist/twoc2p.d.ts +54 -0
- package/dist/twoc2p.generated.d.ts +33 -0
- package/dist/twoc2p.generated.js +1145 -0
- package/dist/twoc2p.js +85 -0
- package/dist/voice.d.ts +16 -13
- package/dist/voice.js +13 -10
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Continue a browser checkout from typed fields. `buy.ask` and `buy.confirm` now return `checkout_handoff`, the step your own agent takes when the purchase agent hands it a matched offer's checkout: the `buy_url` to open in the browser that checks out, the store and product, the listed price, the checkout authorization to create and the time to create it by. It is null on every other turn, and on a kind of step this version does not know. `buy.conversation` now returns `open_offers`, each offer handed to your agent and how far it got, and `browser_checkouts`, each checkout Agentcard ran in its own browser with its status, total, cart hash, approval link and order id. Before, the SDK dropped all three, so a caller read the step from `reply` and could not recover a browser checkout's approval link or order id from the conversation. Both lists are empty when the API sends none. TypeScript: `BuyTurn` gains `checkout_handoff` and `BuyConversation` gains `open_offers` and `browser_checkouts`; the new types `BuyCheckoutHandoff`, `BuyOpenOffer`, `BuyBrowserCheckout` and `BuyApiAmount` are exported. The listed price and the authorization hint arrive as the API writes them (`BuyApiAmount`: their `currency` typed, their amount fields passed through). An API that sends none of them reads as null and empty lists, so this release works before and after the API release that adds the browser path.
|
|
6
|
+
|
|
7
|
+
- Hold a card request with nothing created until the person confirms the total. `attachToCdp(…, { hold: true })` holds the first card request; `controller.release({ amount, currency, pageTotal })` creates the authorization at that total only when the page still shows it (`upTo: true` for a ceiling), and `controller.hold.cancel()` fails it. A held request is cancelled before its processor's own wait, and every other card request fails at once. Hold mode also holds Bolt's tokenizer (cancel only) and, with `placeholderNumbers`, any request carrying the placeholder card. New `serviceWorkers: 'bypass'` sends every armed frame's requests past site service workers instead of refusing the page, and `sharedWorkers: 'accept'` accepts a shared worker unwatched instead of refusing the page (both the default in hold mode; `'observe'` attaches and watches it where the browser allows). Exported: `CardRequestHold`, `HeldCardRequest`, `HoldOptions`, `HoldReleaseInput`, `HoldReleaseOutcome`, `HoldState`, `HoldEndedError`, `HoldAnsweredError`, `BOLT_HOLD_RULE`, `placeholderHoldRule`, `matchHoldOnlyRule`, `HOLD_DEADLINES_MS`.
|
|
8
|
+
|
|
9
|
+
- Hold a repeat confirm for the person's decision with `buy.confirm({ ..., wait: true })`. On a browser cart already waiting for the person's approval, the API holds the confirm up to 25 seconds and answers the moment they decide (`approved_not_charged` in sandbox, `order_placed` live, or `declined`), so your agent no longer polls. The first confirm never holds: it answers with the `approval_url` at once. Without `wait`, a confirm behaves as before. Needs an API release that reads `wait`; an older API ignores it and answers at once.
|
|
10
|
+
|
|
11
|
+
- Read `approved_not_charged` as a turn's status. A sandbox browser checkout the person approved now answers `status: 'approved_not_charged'` instead of `declined`: the card was never sent to the store, so nothing was ordered or charged, and `decline_code` still reads `sandbox_mode`. Earlier releases read the new status as `needs_input`. Every other sandbox answer, such as a sandbox order through a store integration, still answers `declined` with `sandbox_mode`. TypeScript: `BuyStatus` gains `approved_not_charged`; code that switches exhaustively over it needs the new case.
|
|
12
|
+
|
|
13
|
+
- Approve on Agentcard's page when a client names no approval posture. A client built without `voice`, or with `voice` and no `approval`, now runs every `voice.*` verb as `approval: 'sheet'` (the person approves on Agentcard's page) instead of throwing `missing_approval_posture`, so a checkout client that pauses a card request and asks for approval works without extra options. `approval: 'in_app'` is unchanged, and a value that is neither `'sheet'` nor `'in_app'` still throws `missing_approval_posture` at construction. A behaviour change for clients that relied on the error: release it as a minor version.
|
|
14
|
+
|
|
15
|
+
- Continue 2C2P's hosted payment page (`pgw-ui.2c2p.com`, builds 4.1 to 4.5) with the card field the cardholder's device writes, for Agentcard's proof company only (dark launch). 2C2P's page encrypts the card into one field, `payment.data.securePayToken`, and posts it to `pgwv4.2c2p.com/payment/<build>/Payment` (`sandbox-pgwv4.2c2p.com` for test payments). This build declares `twoc2p_secure_pay` on every sync, and the API serves the 2C2P entry only to Agentcard's proof company, whatever its processor lists name, so every other company's 2C2P payments go through untouched, as for an unsupported processor. The Payment names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total 2C2P's page shows. Before it creates anything, the API reads the merchant and the total from 2C2P itself and refuses another total (`amount_mismatch`), a saved card, an instalment plan, a currency conversion or a points redemption (`invalid_twoc2p_request`), and a live merchant Agentcard has not reviewed (`unsupported_checkout`). After the approval the SDK writes the device's field over the one your agent's page wrote, in that Payment alone, moves no other byte, and continues the page's own request; 3-D Secure then runs in the agent's browser, where a challenge needs a person at that browser. A request that is not byte for byte the one the cardholder approved is refused, and the SDK retires that approval as `merchant_never_retried`. The API serves the device's field only in the 10 minutes after the approval and then deletes it. Your events and error messages show nothing after the page's build folder on 2C2P's page hosts: `https://pgw-ui.2c2p.com/payment/4.1/{token}`. A 2C2P payment also needs the matching API and Vault releases.
|
|
16
|
+
|
|
5
17
|
- Answer a Datatrans form's second card post from a token answer that carries no `cardInfo`, or a `cardInfo` with the `accountType` and `binLength` fields Datatrans documents. Datatrans's card frame reads both answers as a success, and the API records them once it runs this release's checkout rules. An earlier SDK version still hands the first answer to the page, because it passes on whatever the API stored, but it sends the page's next post of the same form to the cardholder as a new approval.
|
|
6
18
|
|
|
7
19
|
- Finish a Worldpay eProtect checkout that your code resumes itself. With `attachToCdp(…, { pauseOnly: true })`, you create the authorization, read it, and call `controller.resume(read)`. The read carries the card registration as Agentcard stored it, bare JSON, and earlier releases handed exactly that to the merchant's paused registration script. Worldpay's script reads its answer only through the callback the request named, so it never received the registration: the page waited until the merchant's timeout, and the checkout read `outcome_unknown` with reason `merchant_request_aborted` after the cardholder had approved. The SDK now answers the resumed registration as it answers one it created itself, with the registration inside the request's own callback under Worldpay's script type. The SDK never hands the page a read that is not this request's registration, such as one already inside a callback or another request's answer: `resume` rejects with `PaymentOutcomeUnknownError`, reason `eprotect_registration_unconfirmed`, and the paused request fails. A checkout whose authorization the SDK creates was already answered this way and does not change. No API or Vault release is needed.
|
|
@@ -32,6 +44,8 @@
|
|
|
32
44
|
|
|
33
45
|
- Pay a store on Paysafe Checkout's current build, 1.16.1. The loader Paysafe tells merchants to include, `checkout/v1/latest`, now runs version 1.16.1, and that build sends the card to `api.paysafe.com/checkout/api/v1/tokenize` (`api.test.paysafe.com` in its test environment), where 1.8.0 sent it to `hosted.paysafe.com`. Earlier releases did not recognize that request, so it was never paused and the agent's placeholder card reached Paysafe. The SDK now pauses it and keeps the three headers the build sends beside its credential: `X-Internal-Correlation-Id`, `X-App-Version` and `X-TransactionSource`. Paysafe.js latest sends the same three, and its replay keeps them too. `prepare({ psp: 'paysafe' })` binds a request only to a Checkout build Agentcard read, by the version the request names: 1.8.0 on `hosted.paysafe.com`, or 1.16.1 on `api.paysafe.com`, each with its own headers. A request from any other build stops with `checkout_changed`, and nothing is sent. An SDK that syncs its registry pauses the 1.16.1 request once the API release deploys; a prepared 1.16.1 checkout needs this release. Both need a Vault release after 0.6.2.
|
|
34
46
|
|
|
47
|
+
- Read an Authorize.Net refusal as a refusal. Authorize.Net's Accept.js endpoint answers HTTP 200 whether or not it took the card and names the outcome in the answer's `resultCode`, and the cardholder's device recorded an `Error` answer as approved: `authorize()` resolved and the paused request continued with Authorize.Net's refusal in its body. The device now records it as Authorize.Net's refusal, so `authorize()` throws `ProcessorRefusedError` with Authorize.Net's code, such as `E00007`, and the authorization reads `declined` with reason `processor_refused`. Only an answer that carries Accept.js's nonce under `resultCode` `Ok` is approved; any other answer leaves the outcome unknown. No SDK upgrade is needed: the device changes with the next Vault release, and the API judges the answer again at approve once it deploys.
|
|
48
|
+
|
|
35
49
|
- Keep a Gravy checkout session out of your events and error messages. Gravy's Secure Fields send the card to `/checkout/sessions/<session id>/fields` on the merchant's Gravy host with no auth header, so the session id is the only credential that request needs: anyone who holds it can write card fields into the session until it expires. Earlier releases named the full URL in the `card_request_paused` event and in the error `authorize()` throws for a request in the wrong method, so the session id reached your `onEvent` handler and every log that recorded the event or the error. `redactUrl`, every event and every error message now show it as `{session}`, as in `https://api.<merchant>.gr4vy.app/checkout/sessions/{session}/fields`. No API or Vault release is needed.
|
|
36
50
|
|
|
37
51
|
- Pay FlixBus's checkout on Payrails web SDK 6.7.0. FlixBus's page now loads web SDK 6.7.0 in place of 5.50.0, and 6.7.0's Pay now button sends the card-form payment without the `enrollInstrumentToNetworkOffers` member that 5.50.0 sent as `false`. 0.23.11 required that member and aborted every such payment as `unsupported_payrails_card_payment` before creating anything, so no FlixBus payment reached an approval. A payment that names no `enrollInstrumentToNetworkOffers` now pauses as 5.50.0's does: Payrails reads a missing flag as `false`, and 6.7.0 sends no other member in its place. A payment that sets the flag `true` or keeps the card (`storeInstrument: true`, or `meta.order.processingType` `CardOnFile` or `UnscheduledCardOnFile`) still aborts as `payrails_card_on_file_consent_required`, and the flag in another case, in another place or as anything but a boolean aborts as `unsupported_payrails_card_payment`. A payment also needs the matching API and Vault releases.
|
|
@@ -127,6 +141,8 @@
|
|
|
127
141
|
|
|
128
142
|
- Pay Cinemark and Dick's Sporting Goods' own storefront, two reviewed Adyen merchants that take the card on their own server. Both profiles, `cinemark_initiate` and `dsg_paygate_v2`, ship `enabled`, and the API serves them `enabled` only to a client of a company Agentcard turned them on for. For every other client the adapters keep pausing and aborting their card requests as `merchant_profile_observed`, as before. A payment there needs a preparation for the profile and the cardholder's approval in the Vault, and Autopilot never pays one. The create carries the merchant's own checkout total as your agent's browser read it, and the API refuses a payment whose amount is not that total. Dick's paygate at Golf Galaxy, Public Lands and Going Going Gone moves to its own profile, `dsg_other_stores_paygate_v2`, which ships in `observe`, so the adapters abort those card requests as before. Matching API and Vault releases are required.
|
|
129
143
|
|
|
144
|
+
- Read the booking D-EDGE's payment hub holds for a card post from D-EDGE's card frame, where HUMA Kotor Bay and other D-EDGE hotels take the card. The adapters record the hub's own `POST /load/<session>` answer, read-only, and send it with the create, cut to what the card pays now, the room's total and the part of it left for the stay, the rate's deposit and scheduled-payment marks and the booking's status. A deposit other than your integer `amount` throws `AmountMismatchError` with `amountSource: 'merchant_total'`. A rate that only guarantees the booking with the card, a charge on a later date, or part of the room left to pay at the stay throws `CardEndpointRefusedError` with reason `facts_refused`, because the hotel would charge the card after a single-use card may have closed; a stay tax the hotel collects at the desk is not part of the room. A voucher, points or a bank chosen in the hub after the booking loaded throws it with reason `facts_unavailable`. D-EDGE stays in `observe`, so its card post still ends on the stopped page with reason `not_enabled`. The API answers a D-EDGE create that carries no hub record with `409 merchant_facts_required`, and one at a rate the hotel charges later with `409 merchant_facts_refused` and reason `deferred`, before it checks whether D-EDGE takes payments, so an earlier SDK cannot pay D-EDGE once Agentcard enables it. A matching API release is required.
|
|
145
|
+
|
|
130
146
|
- Stop the card fields adyen-web 5 encrypts at a reviewed Adyen merchant profile. adyen-web 5 seals each field as an RSA-OAEP / A256CBC-HS512 JWE, where adyen-web 6 writes RSA-OAEP-256 / A256GCM, and the SDK read only adyen-web 6's form. So the card requests of Cathay Pacific's payment page and Dunelm's legacy checkout, and Cathay's charge request after its signing step, reached the merchant with the agent's dummy card. The adapters now read both forms as a card. Both profiles ship in `observe`, so the adapters abort each of these requests before it leaves the browser and report it as `merchant_profile_observed`. The Vault writes only adyen-web 6's form, so the API refuses a create for a card request in adyen-web 5's form with `cse_template_unsupported`, and nothing is charged.
|
|
131
147
|
|
|
132
148
|
- Abort the requests in which IHG's pay page sends the typed card outside its reviewed card request, in every status, as `card_endpoint_refused` with reason `card_resend`. The page posts the same booking to IHG's enrollment service while its pre-ticked "Enroll for free" box stays ticked, posts it on other paths for more than one room, a travel agent or Business Rewards, and at a hotel that asks for 3-D Secure first posts the number to two 3-D Secure calls. The SDK let each of these reach IHG with the agent's dummy card. A booking with the box unticked reaches IHG's reviewed card request, which the adapters refuse as before while IHG is in `observe`.
|
|
@@ -151,6 +167,8 @@
|
|
|
151
167
|
|
|
152
168
|
- Pay a Wix checkout whose card goes to Wix's own tokenizer, from Agentcard's own account only for now (dark launch): Agentcard turns it on for another company once it checks that the site instance Wix's card frame sends belongs to the reviewed store. Every Wix checkout mounts Wix Cashier's card frame, and for Wix Payments and classic Stripe on Wix that frame posts the card to `https://cashier-pci.wixapps.net/_api/payment-gateway-web/tokenize/v2/tokens`. Wix's payment widget fails the payment 30 seconds after it asks the frame for a token, too soon for an approval after Pay, so the SDK pays a Wix checkout only through `prepare({ psp: 'wix_payments', environment: 'production' })`, at a store Agentcard reviewed (ParaHouse Magazine, Psalms & Potions and Alexandra Hagel today), with `amount` and `currency`, the total the cardholder approves: the request names none. Wix has no test environment, so a test client's `prepare()` fails with `live_host_on_sandbox_row`. The sync now also asks for `capabilities=wix_payments_tokenize`. Once the cardholder approves, the frame's request pauses, the cardholder's device sends the card through the checkout relay, and the frame receives Wix's token. The frame sends its card request once more under a fresh site instance after Wix answers 401 or 403: the SDK refuses that second request in the browser and never answers it from the first approval. The SDK fails a paused request the cardholder's device has not sent 20 seconds after the pause, of the 30 seconds Wix waits, with reason `wix_payments_send_window_closed`, as it fails a prepared Secure Acceptance token create, and the device sends nothing past its own earlier boundary.
|
|
153
169
|
|
|
170
|
+
- Pay a HiPay checkout whose card goes to HiPay's own vault, for a company Agentcard turned HiPay on for (dark launch). HiPay's hosted fields, which HiPay's WooCommerce, PrestaShop and Magento modules use by default, send the card from a hidden frame on `libs.hipay.com` to `https://secure2-vault.hipay-tpp.com/rest/v2/token` under the merchant's public credential, and the merchant's page gives that request 20 seconds after Pay, too soon for an approval after Pay. So the SDK pays a HiPay checkout only through `prepare({ psp: 'hipay', environment: 'production' })` (`'sandbox'` for HiPay's stage vault, with a test client), with `amount` and `currency` above zero, the total the cardholder approves: the request names none. On the live vault only a merchant Agentcard reviewed is paid, at its own checkout page and under its own public credential; none is reviewed yet, so a live `prepare()` fails with `unsupported_checkout`. The sync now also asks for `capabilities=hipay_vault_token`. The frame posts the card number alone while it is typed, and posts `/rest/v2/token/update` once it holds a token: the SDK fails both in the browser, and any post to the token URL that carries no security code with them, with a `blocked` event (`hipay_number_only`, `hipay_update_refused`) and spends no ready preparation, so the frame keeps the number and Pay sends one complete request. Once the cardholder approves, that request pauses, the cardholder's device sends the card to HiPay itself, and the frame receives HiPay's token: the SDK reads the approval every 500 ms for the 20 seconds the page waits. The SDK fails a paused request the device has not sent 12 seconds after the pause, of the 20 seconds the page waits, with reason `hipay_send_window_closed`. A payment also needs the matching API and Vault releases, and a company listed for HiPay.
|
|
171
|
+
|
|
154
172
|
- Keep a GoPay card form's token out of your events and error messages. GoPay's card frame sends the card to its own URL, `https://secure.gopay.com/gp-card-comm/h/<token>?lang=..&ver=..&SG=<signature>`, and that token, with the signature in the query, opens that payment's card form to anyone who holds it. `redactUrl`, every event and every error message now show it as `{token}` on GoPay's card frame hosts, as in `https://secure.gopay.com/gp-card-comm/h/{token}`, and drop the query as before. The SDK does not pause a GoPay payment yet. No API or Vault release is needed.
|
|
155
173
|
|
|
156
174
|
- Pay PayMongo's hosted checkout through the checkout relay, for a company Agentcard turns PayMongo on for (dark launch). Every PayMongo payment link, Storefront page and Checkout Session opens one page, `https://checkout.paymongo.com/<session>`, whose Pay button posts the card to that page's own URL as a Next.js Server Action. This build declares `paymongo_checkout_pay` on every sync, and the API serves the PayMongo entry only to a company Agentcard turned PayMongo on for, so every other company's PayMongo payments go through untouched, as for an unsupported processor. For an enabled company the SDK pauses only the checkout page's own card attach. The attach names no price, so `authorize()` refuses one without `amount` above zero and `currency` (`AmountRequiredError`): send the total PayMongo's checkout shows, in Philippine pesos. The API reads the checkout session from PayMongo and refuses a total that is not the session's to the centavo (`amount_mismatch`), a test-mode session on live credentials or the reverse (`paymongo_environment_mismatch`), and a session that has expired, is already being paid, takes no cards or adds PayMongo's fee on top (`paymongo_checkout_unpayable`), and it names PayMongo's merchant, whatever `merchant` you send. The cardholder's device sends the card through the checkout relay, and the page gets PayMongo's own answer: the paid payment, or the step toward PayMongo's 3-D Secure page, which the page then runs in the agent's browser, where a challenge needs a person at that browser. A PayMongo payment also needs the matching API, Vault and checkout relay releases.
|
|
@@ -163,6 +181,8 @@
|
|
|
163
181
|
|
|
164
182
|
- Keep a Paytrail payment's id and a Payment Highway card page's session out of your events and error messages. Paytrail's card method posts to `/payments/<transaction>/click-to-pay/loading-and-redirect` on `services.paytrail.com`, and that id opens Paytrail's own payment wall for the payment (`pay.paytrail.com/pay/<transaction>`), its amount, its merchant and every method's form, to anyone who holds it. Payment Highway, Paytrail's card platform, names its card page's session in the same way (`/form/<session>/pay_with_click_to_pay` on `v1.api.paymenthighway.io`). `redactUrl`, every event and every error message now show them as `{transaction}` and `{session}`, as in `https://services.paytrail.com/payments/{transaction}/click-to-pay/loading-and-redirect`. The SDK does not pause a Paytrail payment yet. No API or Vault release is needed.
|
|
165
183
|
|
|
184
|
+
- Pause QuickPay's hosted payment window (where most Danish and Nordic webshops send the buyer) for the cardholder's device. The sync now also asks for `capabilities=quickpay_hosted_payment_page`, and the API serves QuickPay's recognizer only for a company Agentcard turned QuickPay on for. The SDK pauses the merchant's signed entry form to `https://payment.quickpay.net/`, which carries no card, as a `hosted_form` authorization, and answers the paused navigation with the `submitted_on_device` page once the cardholder's device has paid it. Every other post your browser makes to QuickPay, the window's card post, its fee lookup, its wallet token post and its 3-D Secure step, carries the card your agent typed or follows it, and so does the window's lookup of the card's first digits: the SDK fails each one and reads `unsupported` with reason `processor_card_page`. Your events and error messages name the window's session, the 32-hex first segment of its paths, as `{session}`. Matching API and Vault releases are required. QuickPay stays off for every company Agentcard has not turned it on for.
|
|
185
|
+
|
|
166
186
|
## 0.23.0
|
|
167
187
|
|
|
168
188
|
### Upgrading from 0.22.0
|
package/PREFLIGHT.md
CHANGED
|
@@ -205,6 +205,8 @@ Shiji STS (`shiji_sts`), the card leg of MyCheck Wallet V3 at La Quinta by Wyndh
|
|
|
205
205
|
|
|
206
206
|
Wix Payments (`wix_payments`) has no discovery rule in this table. Wix Cashier's card frame on `cashier-pci.wixapps.net` is the same for every provider a Wix site connects, and for Square and Wix's Stripe Gateway it sends the card to the processor's own SDK rather than to Wix's tokenizer, so the frame on a page is no evidence of this lane, and a Wix checkout stays `unknown`. The direct SDK pauses the card request the frame sends to Wix's tokenizer only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled. Read the Wix row of the [README](./README.md) processor table.
|
|
207
207
|
|
|
208
|
+
HiPay (`hipay`) has no discovery rule in this table. HiPay's JavaScript SDK (`libs.hipay.com/js/sdkjs.js`) loads on every HiPay checkout, hosted fields or HiPay's own hosted page, so the script on a page is no evidence that a payment can be approved, and a HiPay checkout stays `unknown`. The direct SDK pauses the token create HiPay's hosted fields send only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled. Read the HiPay row of the [README](./README.md) processor table.
|
|
209
|
+
|
|
208
210
|
Gravy Secure Fields (`gravy`) is identified from the loader its merchants load, `/secure-fields/latest/secure-fields.js` on `cdn.<merchant>.gr4vy.app` (or `cdn.sandbox.<merchant>.gr4vy.app`), but it is not in this table. It launches dark: the direct SDK pauses Gravy's card request, a `PUT` from Gravy's controller frame, only after `syncRegistry()` and only for an org Agentcard has enabled, so the bundled catalog leaves it out and a Gravy checkout stays `unknown`.
|
|
209
211
|
|
|
210
212
|
Paddle (`paddle_vgs`) has no discovery rule in this table. Paddle's checkout frame on `buy.paddle.com` names the tokenizer it sends the card through only after the buyer's first step, and Paddle can name another one for each checkout, so nothing on the page before that step says which one a checkout uses, and a Paddle checkout stays `unknown`. The direct SDK pauses the card request a VGS checkout sends to `vault.paddle.com/cards` only after `syncRegistry()`, only through a prepared checkout, and only for an org Agentcard has enabled. Read the Paddle row of the [README](./README.md) processor table for what the SDK covers.
|
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ const bundle = await agentcard.voice.challengeForHost(purchase.id, { lang: 'es'
|
|
|
57
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
58
|
```
|
|
59
59
|
|
|
60
|
-
One choice per business, not per purchase
|
|
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
61
|
|
|
62
62
|
### The card that pays
|
|
63
63
|
|
|
@@ -180,6 +180,12 @@ Merchant timeouts still apply to the request itself: Square's observed tokenizat
|
|
|
180
180
|
|
|
181
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
182
|
|
|
183
|
+
### Hold the card request until the person confirms the total
|
|
184
|
+
|
|
185
|
+
`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.
|
|
186
|
+
|
|
187
|
+
Hold mode also catches Bolt's tokenizer, which no device replay can answer (held, then only cancelled), and, with `hold: { placeholderNumbers }`, any request whose body carries the placeholder card, wherever it goes. In sandbox, `release({ …, continuePlaceholder: true })` sends a store's own card post on unchanged, since it carries the documented test number only. Site service workers are bypassed (`serviceWorkers: 'bypass'`) instead of refusing the page, and a shared worker is accepted unwatched (`sharedWorkers: 'accept'`): Fetch reaches no shared worker, so nothing it sends can be approved, and the browser only ever holds the placeholder card. `sharedWorkers: 'observe'` attaches to it and ends the checkout on a card request it sends, on a browser that allows that attach (Kernel does not).
|
|
188
|
+
|
|
183
189
|
### Recognisers and request builders
|
|
184
190
|
|
|
185
191
|
Both are registries the SDK ships and you extend at runtime, so a processor or a store Agentcard does not know yet needs no release.
|
|
@@ -280,6 +286,7 @@ Coverage is specific to the processor request format, merchant setup, browser tr
|
|
|
280
286
|
| WSPay Components (WSPay's build of Monri Components) | dark launch: the card frame's confirm of a new card, after `vault.syncRegistry()`, with `amount` and `currency`, and only for an org Agentcard has enabled; for any other org the SDK leaves WSPay's requests alone. The cardholder's device sends the card to WSPay and the frame receives WSPay's answer. A confirm that asks WSPay to keep the card on file, as Valamar's does on every booking, is paid only as a hotel's EUR 1.00 card guarantee with a card the cardholder stored. Validated in local Chromium against a model of the card frame; no WSPay sandbox run or live merchant purchase yet. See [Pay a WSPay Components checkout](#pay-a-wspay-components-checkout) |
|
|
281
287
|
| Shiji STS (MyCheck Wallet V3; Wyndham's booking pages) | dark launch, mode `cse` under a pinned key, and only for an org Agentcard has enabled. The card token a hotel booking mints guarantees the stay, so the cardholder approves with a card they stored, under a line that says the hotel can keep it on file. See [Shiji STS](#shiji-sts-mycheck-wallet-v3) |
|
|
282
288
|
| Wix checkout (Wix Cashier's card frame; Wix Payments and classic Stripe on Wix) | dark launch: only in Agentcard's own account for now, after `vault.syncRegistry()`, and only through `prepare({ psp: 'wix_payments', environment: 'production' })` at a store Agentcard reviewed, with `amount` and `currency`. The frame's post to `cashier-pci.wixapps.net/_api/payment-gateway-web/tokenize/v2/tokens` pauses, the cardholder's device sends the card through the checkout relay, and the frame receives Wix's token. Wix has no test environment. The SDK refuses the frame's second card request after a 401 or 403 in the browser, and fails a request still unsent 20 seconds after the pause, before the card leaves (`wix_payments_send_window_closed`) |
|
|
289
|
+
| HiPay Enterprise (HiPay's hosted fields; France, Portugal, Italy, Spain) | dark launch: after `vault.syncRegistry()`, only for an org Agentcard has enabled, and only through `prepare({ psp: 'hipay', environment: 'production' })` (`'sandbox'` for HiPay's stage vault) at a merchant Agentcard reviewed, with `amount` and `currency` above zero. The hosted fields' post to `secure2-vault.hipay-tpp.com/rest/v2/token` pauses, the cardholder's device sends the card to HiPay directly, and the frame receives HiPay's token. The frame's number-only post and its `/update` fail in the browser (`hipay_number_only`, `hipay_update_refused`), and a request still unsent 12 seconds after the pause fails before the card leaves (`hipay_send_window_closed`). No merchant is reviewed yet, so no live HiPay payment can be approved |
|
|
283
290
|
| 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" |
|
|
284
291
|
| Paddle checkout (Paddle's card vault; Rectangle Pro) | dark launch: only in Agentcard's own account for now, after `vault.syncRegistry()`, and only through `prepare({ psp: 'paddle_vgs', environment: 'production' })` (`'sandbox'` for Paddle's sandbox) on an attachment whose `cardId` names the paying card; without one, `prepare()` fails with `card_required`. On a Paddle checkout whose card method names VGS, the frame's post to `vault.paddle.com/cards` pauses, the cardholder's device sends the card itself, and the frame receives the stored card's id; its second request after its 10-second timeout gets the same answer. Type `placeholderCard()`'s facts for that card into Paddle's form, and pass `amount` and `currency`, the total the cardholder approves. Only Visa, Mastercard and American Express cards are paid (`paddle_vgs_card_unsupported` otherwise), and a typed expiry or placeholder that is not the card's fails the create with `paddle_vgs_card_mismatch`. On the live vault only a reviewed seller at its reviewed page is paid (Rectangle Pro at `https://rectangleapp.com`), and the approval says that seller can keep the card on file. Paddle's `/pay` 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. A Paddle checkout that sends the card through TokenEx or Spreedly is not supported, and no live payment has gone through this route yet |
|
|
285
292
|
|
|
@@ -289,7 +296,7 @@ so new processors work without you shipping a release. `attachToCdp` derives the
|
|
|
289
296
|
`Fetch.enable` url patterns from that same list rather than a constant, which is
|
|
290
297
|
why the sync call belongs before the attach. `agentcard.checkout.cardUrlPatterns()` returns
|
|
291
298
|
those patterns if you arm a CDP connection yourself. Call
|
|
292
|
-
`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,redsys_hosted_payment_page,paymongo_checkout_pay`
|
|
299
|
+
`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,quickpay_hosted_payment_page`
|
|
293
300
|
for the list this build syncs. The API serves Fiserv's card capture only to a
|
|
294
301
|
build that lists `fiserv_card_capture`, and only for a company Agentcard turned
|
|
295
302
|
Fiserv on for. An older SDK never pauses a Fiserv request it could not finish,
|
|
@@ -317,6 +324,11 @@ PayMongo's hosted checkout (`paymongo_checkout_pay`) is served on the same terms
|
|
|
317
324
|
this build pauses the checkout page's own card attach on `checkout.paymongo.com`,
|
|
318
325
|
the cardholder's device sends it through the checkout relay, and the page gets
|
|
319
326
|
PayMongo's answer.
|
|
327
|
+
QuickPay's hosted payment window (`quickpay_hosted_payment_page`) is served the
|
|
328
|
+
same way: this build pauses the merchant's signed entry form to
|
|
329
|
+
`payment.quickpay.net` for the cardholder's device and fails every other post
|
|
330
|
+
your browser makes to QuickPay, and the window's lookup of the typed card's
|
|
331
|
+
first digits.
|
|
320
332
|
CyberSource Secure Acceptance's token
|
|
321
333
|
create (`cybersource_sa_token_create`) is served on the same terms too; every
|
|
322
334
|
other company keeps the stop every build applies to a card posted to Secure
|
|
@@ -565,6 +577,23 @@ mode; the difference matters if you drive `authorize()` yourself.
|
|
|
565
577
|
reads `stopped_before_send`: the `PaymentOutcomeUnknownError` then carries
|
|
566
578
|
`processorRequestStarted: false`.
|
|
567
579
|
|
|
580
|
+
D-EDGE's card frame, where HUMA Kotor Bay and other D-EDGE hotels take the
|
|
581
|
+
card, posts it as a page load of the frame, and the payment hub around the
|
|
582
|
+
frame loads the booking from its own host (`POST /load/<session>`). The SDK
|
|
583
|
+
records that answer, read-only, and sends it with the create, cut to what
|
|
584
|
+
the card pays now, the room's total and the part of it left for the stay,
|
|
585
|
+
the rate's deposit and scheduled-payment marks and the booking's status. A
|
|
586
|
+
deposit other than your integer `amount` is an `AmountMismatchError` against
|
|
587
|
+
the merchant's total. A rate that only guarantees the booking with the
|
|
588
|
+
card, a charge on a later date, or part of the room left to pay at the stay
|
|
589
|
+
is refused as `facts_refused` before any authorization: the hotel would
|
|
590
|
+
charge the card after a single-use card may have closed. A stay tax the
|
|
591
|
+
hotel collects at the desk is not part of the room, so a room paid in full
|
|
592
|
+
goes on. A voucher, points or a bank chosen in the hub after the booking
|
|
593
|
+
loaded leaves the booking unknown, and the payment is refused as
|
|
594
|
+
`facts_unavailable`. D-EDGE is in observe, so its card post ends on the
|
|
595
|
+
stopped page with reason `not_enabled` until Agentcard enables it.
|
|
596
|
+
|
|
568
597
|
`syncRegistry()` asks the API for `SUPPORTED_MODES` only
|
|
569
598
|
(`token,cse,hosted_form,device_encrypt`), so a processor whose flow this build
|
|
570
599
|
cannot finish is never paused; the API serves `hosted_form` and `device_encrypt`
|
|
@@ -887,8 +916,9 @@ is gone resolves as `PaymentOutcomeUnknownError` with reason
|
|
|
887
916
|
paused request is not the card request Agentcard reviewed; `detail` names
|
|
888
917
|
fields, never values), `facts_unavailable` (the merchant keeps its own
|
|
889
918
|
record of the order and this runtime read none for this checkout) or
|
|
890
|
-
`facts_refused` (that record says the order renews,
|
|
891
|
-
|
|
919
|
+
`facts_refused` (that record says the order renews, that the merchant
|
|
920
|
+
charges the card later, as a hotel rate that only guarantees the booking
|
|
921
|
+
does, or that the checkout is in test mode). Every reason but `request_unsupported`, `facts_unavailable`
|
|
892
922
|
and `facts_refused` holds the attachment. The adapters also emit `card_endpoint_refused` with the
|
|
893
923
|
profile, the endpoint origin and the reason.
|
|
894
924
|
- `PageAmountRequiredError` (`code: 'page_amount_required'`, `psp`): the paused
|
package/dist/amadeus.d.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
export declare const AMADEUS_SEND_WINDOW_MS = 20000;
|
|
3
3
|
/** How long after the pause a paused Wix tokenize request may still be sent, of the 30 seconds Wix's widget waits. */
|
|
4
4
|
export declare const WIX_PAYMENTS_SEND_WINDOW_MS = 20000;
|
|
5
|
+
/** How long after the pause a paused HiPay token create may still be sent, of the 20 seconds the merchant's page waits. */
|
|
6
|
+
export declare const HIPAY_SEND_WINDOW_MS = 12000;
|
|
5
7
|
/**
|
|
6
8
|
* True when a request the adapters refuse must fail as a network error (ERR_FAILED) rather than as
|
|
7
9
|
* the cancellation (ERR_ABORTED) they use elsewhere. An XMLHttpRequest reports a cancellation only
|
|
@@ -11,13 +13,13 @@ export declare const WIX_PAYMENTS_SEND_WINDOW_MS = 20000;
|
|
|
11
13
|
*/
|
|
12
14
|
export declare function failsAsNetworkError(url: string): boolean;
|
|
13
15
|
/** The processor a send window's events and stop cause name. */
|
|
14
|
-
export type SendWindowProcessor = 'amadeus' | 'secure_acceptance' | 'wix_payments';
|
|
16
|
+
export type SendWindowProcessor = 'amadeus' | 'secure_acceptance' | 'wix_payments' | 'hipay';
|
|
15
17
|
/** Why a send window stopped its attempt: the API confirmed the card never left the device and never can. */
|
|
16
18
|
export type SendWindowClosed = `${SendWindowProcessor}_send_window_closed`;
|
|
17
19
|
/**
|
|
18
20
|
* The send window for a request paused under a preparation for `psp`, or null when it has none: a
|
|
19
21
|
* prepared Amadeus SDK add, a prepared CyberSource Secure Acceptance token create and a prepared Wix
|
|
20
|
-
* tokenize request, each 20 seconds.
|
|
22
|
+
* tokenize request, each 20 seconds, and a prepared HiPay token create, 12 seconds.
|
|
21
23
|
*/
|
|
22
24
|
export declare function preparedSendWindowMs(psp: string | undefined, url: string): number | null;
|
|
23
25
|
/** The processor a prepared request's send window names in its events, or null when it has none. */
|
package/dist/amadeus.js
CHANGED
|
@@ -19,6 +19,11 @@
|
|
|
19
19
|
// handed back in time. A token Wix mints after that point is one no charge ever names, so a paused
|
|
20
20
|
// tokenize request gets the same 20 second window.
|
|
21
21
|
//
|
|
22
|
+
// HiPay's merchant page gives its hosted fields 20 seconds after Pay (getPaymentData answers TIMEOUT_ERROR), and
|
|
23
|
+
// the merchant's server charges only a token the page read in time. A token HiPay mints after that point stays in
|
|
24
|
+
// the frame unused (the frame's next Pay would send /update, which the SDK refuses), so a paused token create gets a
|
|
25
|
+
// 12 second window: HiPay answers a create in about a second, so a card sent inside it reaches the page in time.
|
|
26
|
+
//
|
|
22
27
|
// So when the window closes and no answer is being handed to the page yet, the adapter asks for that
|
|
23
28
|
// cancel. Confirmed: nothing was sent and nothing can be, so the attempt stops, the page's request
|
|
24
29
|
// fails (a Secure Acceptance frame gets the stopped page), and the checkout reads failed. Not
|
|
@@ -30,16 +35,20 @@
|
|
|
30
35
|
// the same cancel and the same rule, and when the API confirms neither, the attempt keeps waiting. The
|
|
31
36
|
// Data Collection Page's store has no client timeout and gets no window. The window's events name the
|
|
32
37
|
// processor: `amadeus_send_window_closed` and `amadeus_send_window_unconfirmed`,
|
|
33
|
-
// `secure_acceptance_send_window_closed` and `secure_acceptance_send_window_unconfirmed`,
|
|
34
|
-
// `wix_payments_send_window_closed` and `wix_payments_send_window_unconfirmed
|
|
38
|
+
// `secure_acceptance_send_window_closed` and `secure_acceptance_send_window_unconfirmed`,
|
|
39
|
+
// `wix_payments_send_window_closed` and `wix_payments_send_window_unconfirmed`, or
|
|
40
|
+
// `hipay_send_window_closed` and `hipay_send_window_unconfirmed`.
|
|
35
41
|
import { amadeusCardUrl } from './amadeus-checkout.generated.js';
|
|
36
42
|
import { isSecureAcceptanceTokenRequest } from './cybersource-secure-acceptance.generated.js';
|
|
37
43
|
import { SECURE_ACCEPTANCE_PSP, SECURE_ACCEPTANCE_SEND_WINDOW_MS } from './cybersource-secure-acceptance.js';
|
|
38
44
|
import { WIX_PAYMENTS_PSP, isWixPaymentsTokenizeUrl } from './wix-payments.generated.js';
|
|
45
|
+
import { HIPAY_PSP, isHipayTokenUrl } from './hipay.generated.js';
|
|
39
46
|
/** How long after the pause a paused Amadeus SDK add may still be sent. */
|
|
40
47
|
export const AMADEUS_SEND_WINDOW_MS = 20_000;
|
|
41
48
|
/** How long after the pause a paused Wix tokenize request may still be sent, of the 30 seconds Wix's widget waits. */
|
|
42
49
|
export const WIX_PAYMENTS_SEND_WINDOW_MS = 20_000;
|
|
50
|
+
/** How long after the pause a paused HiPay token create may still be sent, of the 20 seconds the merchant's page waits. */
|
|
51
|
+
export const HIPAY_SEND_WINDOW_MS = 12_000;
|
|
43
52
|
/**
|
|
44
53
|
* True when a request the adapters refuse must fail as a network error (ERR_FAILED) rather than as
|
|
45
54
|
* the cancellation (ERR_ABORTED) they use elsewhere. An XMLHttpRequest reports a cancellation only
|
|
@@ -53,7 +62,7 @@ export function failsAsNetworkError(url) {
|
|
|
53
62
|
/**
|
|
54
63
|
* The send window for a request paused under a preparation for `psp`, or null when it has none: a
|
|
55
64
|
* prepared Amadeus SDK add, a prepared CyberSource Secure Acceptance token create and a prepared Wix
|
|
56
|
-
* tokenize request, each 20 seconds.
|
|
65
|
+
* tokenize request, each 20 seconds, and a prepared HiPay token create, 12 seconds.
|
|
57
66
|
*/
|
|
58
67
|
export function preparedSendWindowMs(psp, url) {
|
|
59
68
|
if (psp === 'amadeus_checkout')
|
|
@@ -70,12 +79,16 @@ export function preparedSendWindowMs(psp, url) {
|
|
|
70
79
|
}
|
|
71
80
|
if (psp === WIX_PAYMENTS_PSP)
|
|
72
81
|
return isWixPaymentsTokenizeUrl(url) ? WIX_PAYMENTS_SEND_WINDOW_MS : null;
|
|
82
|
+
if (psp === HIPAY_PSP)
|
|
83
|
+
return isHipayTokenUrl(url) ? HIPAY_SEND_WINDOW_MS : null;
|
|
73
84
|
return null;
|
|
74
85
|
}
|
|
75
86
|
/** The processor a prepared request's send window names in its events, or null when it has none. */
|
|
76
87
|
export function sendWindowProcessor(psp) {
|
|
77
88
|
if (psp === WIX_PAYMENTS_PSP)
|
|
78
89
|
return 'wix_payments';
|
|
90
|
+
if (psp === HIPAY_PSP)
|
|
91
|
+
return 'hipay';
|
|
79
92
|
return psp === 'amadeus_checkout' ? 'amadeus' : psp === SECURE_ACCEPTANCE_PSP ? 'secure_acceptance' : null;
|
|
80
93
|
}
|
|
81
94
|
/** The send window for a prepared Amadeus SDK add, or null: preparedSendWindowMs for Amadeus alone. */
|
package/dist/buy.d.ts
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { ApiModule, type ApiTransport } from './api-module.js';
|
|
18
18
|
import { AgentcardError } from './voice-types.js';
|
|
19
|
-
/** Where a turn stands. `needs_input` is the common case: a question, or a cart waiting for its confirm; `needs_approval` is a confirm under `approval: 'voice'` waiting for the person's verdict on `authorization_id
|
|
20
|
-
export type BuyStatus = 'needs_input' | 'needs_approval' | 'order_placed' | 'partially_placed' | 'declined';
|
|
19
|
+
/** Where a turn stands. `needs_input` is the common case: a question, or a cart waiting for its confirm; `needs_approval` is a confirm under `approval: 'voice'` waiting for the person's verdict on `authorization_id`; `approved_not_charged` is a sandbox browser checkout the person approved, where the card was never sent to the store. */
|
|
20
|
+
export type BuyStatus = 'needs_input' | 'needs_approval' | 'order_placed' | 'partially_placed' | 'declined' | 'approved_not_charged';
|
|
21
21
|
/** How a confirm that pays with the person's own card asks them: `page`, Agentcard's approval page on `approval_url` (the default); `voice`, the key word inside your app on the purchase's own `authorization_id`. */
|
|
22
22
|
export type BuyApproval = 'voice' | 'page';
|
|
23
23
|
/** The two ways a purchase approved by voice ends without an order: the person declined it, or the approval ran out before a confirm placed against it. */
|
|
@@ -129,6 +129,75 @@ export interface BuyUnmatched {
|
|
|
129
129
|
detail: string | null;
|
|
130
130
|
at: string;
|
|
131
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* The step your own agent takes next when the purchase agent hands it a matched offer's checkout
|
|
134
|
+
* (your client's browser mode is `agent`): open `buy_url` itself, in the browser that will check out,
|
|
135
|
+
* attach this SDK before the payment form, and create the checkout authorization the hint describes
|
|
136
|
+
* before `expires_at`. `reply` says the same in words.
|
|
137
|
+
*/
|
|
138
|
+
export interface BuyCheckoutHandoff {
|
|
139
|
+
type: 'open_offer';
|
|
140
|
+
buy_url: string;
|
|
141
|
+
store: {
|
|
142
|
+
domain: string;
|
|
143
|
+
name?: string;
|
|
144
|
+
};
|
|
145
|
+
product: {
|
|
146
|
+
id: string;
|
|
147
|
+
title: string;
|
|
148
|
+
};
|
|
149
|
+
quantity: number;
|
|
150
|
+
/** The catalog's price for the product and its `currency`, as the API sends it (see the Purchase API reference). */
|
|
151
|
+
listed_price: BuyApiAmount;
|
|
152
|
+
checkout: {
|
|
153
|
+
/** The `merchant`, `currency` and `checkout_origin` the checkout authorization names, and the most it may authorize, as the API sends them. */
|
|
154
|
+
authorization_hint: BuyApiAmount & {
|
|
155
|
+
merchant: string;
|
|
156
|
+
checkout_origin: string;
|
|
157
|
+
};
|
|
158
|
+
sdk: string;
|
|
159
|
+
};
|
|
160
|
+
rules: string[];
|
|
161
|
+
expires_at: string;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* An amount object as the Purchase API sends it inside a browser checkout step: its `currency`, and its
|
|
165
|
+
* amount fields under the API's own names, passed through untyped. The SDK's one word for an amount is
|
|
166
|
+
* `amount`, and these objects reach a caller exactly as the API wrote them.
|
|
167
|
+
*/
|
|
168
|
+
export type BuyApiAmount = {
|
|
169
|
+
currency: string;
|
|
170
|
+
} & Record<string, unknown>;
|
|
171
|
+
/** An offer handed to your own agent, and how far it got (`link`, `product`, `cart`, `payment`, `approved`), read from the checkout authorization your SDK created. */
|
|
172
|
+
export interface BuyOpenOffer {
|
|
173
|
+
type: 'open_offer';
|
|
174
|
+
buy_url: string;
|
|
175
|
+
store_domain: string;
|
|
176
|
+
product_id: string;
|
|
177
|
+
/** The catalog's price for the product, as the API sends it. */
|
|
178
|
+
listed_price: BuyApiAmount;
|
|
179
|
+
expires_at: string;
|
|
180
|
+
stage: string;
|
|
181
|
+
authorization_id: string | null;
|
|
182
|
+
authorization_status: string | null;
|
|
183
|
+
/** The store's charge on that authorization: `authorized` (a hold), `captured`, or null. */
|
|
184
|
+
charged_kind: string | null;
|
|
185
|
+
}
|
|
186
|
+
/** A checkout Agentcard ran in its own browser at a store, and where it stands (`at_payment`, `awaiting_approval`, `approved_not_charged`, `placed`, `declined`, `expired`, `failed`, `released`). */
|
|
187
|
+
export interface BuyBrowserCheckout {
|
|
188
|
+
checkout_id: string;
|
|
189
|
+
status: string;
|
|
190
|
+
store: string;
|
|
191
|
+
store_url: string | null;
|
|
192
|
+
total_cents: number | null;
|
|
193
|
+
currency: string;
|
|
194
|
+
cart_hash: string;
|
|
195
|
+
approval_url: string | null;
|
|
196
|
+
authorization_id: string | null;
|
|
197
|
+
order_id: string | null;
|
|
198
|
+
failure_reason: string | null;
|
|
199
|
+
updated_at: string;
|
|
200
|
+
}
|
|
132
201
|
/**
|
|
133
202
|
* One turn's answer. Branch on `status`; read `cart.hash` for the confirm
|
|
134
203
|
* that follows; never parse `reply`. `error_code: 'turn_timeout'` means the
|
|
@@ -149,6 +218,8 @@ export interface BuyTurn extends BuyOutcome {
|
|
|
149
218
|
catalog: BuyCatalog | null;
|
|
150
219
|
error_code: string | null;
|
|
151
220
|
unmatched: BuyUnmatched[];
|
|
221
|
+
/** The step your own agent takes next, when the purchase agent hands it the checkout; null otherwise. */
|
|
222
|
+
checkout_handoff: BuyCheckoutHandoff | null;
|
|
152
223
|
}
|
|
153
224
|
/** A turn the API declined: `status: 'declined'` with the code that says why. `isBuyDeclined` narrows to it. */
|
|
154
225
|
export type BuyDeclined = BuyTurn & {
|
|
@@ -218,6 +289,10 @@ export interface BuyConversation {
|
|
|
218
289
|
orders: BuyOrder[];
|
|
219
290
|
carts: BuyCart[];
|
|
220
291
|
unmatched: BuyUnmatched[];
|
|
292
|
+
/** The offers handed to your own agent on this conversation, and how far each got. */
|
|
293
|
+
open_offers: BuyOpenOffer[];
|
|
294
|
+
/** Every checkout Agentcard ran in its own browser on this conversation, and where each stands. */
|
|
295
|
+
browser_checkouts: BuyBrowserCheckout[];
|
|
221
296
|
created_at: string;
|
|
222
297
|
updated_at: string;
|
|
223
298
|
}
|
|
@@ -263,6 +338,13 @@ export interface BuyConfirmInput {
|
|
|
263
338
|
* `voice_declined` or `approval_expired`. Sent only when named.
|
|
264
339
|
*/
|
|
265
340
|
approval?: BuyApproval;
|
|
341
|
+
/**
|
|
342
|
+
* On a repeat confirm of a browser cart waiting for the person's approval,
|
|
343
|
+
* hold up to 25 seconds and answer the moment they decide, instead of
|
|
344
|
+
* answering "still waiting" at once. The first confirm never holds: it
|
|
345
|
+
* answers with the `approval_url` straight away. Sent only when true.
|
|
346
|
+
*/
|
|
347
|
+
wait?: boolean;
|
|
266
348
|
/** How long this call waits for the turn. Default 130 seconds. */
|
|
267
349
|
timeout_ms?: number;
|
|
268
350
|
}
|
package/dist/buy.js
CHANGED
|
@@ -89,6 +89,17 @@ function cartsOf(v) {
|
|
|
89
89
|
}
|
|
90
90
|
const str = (v) => (typeof v === 'string' ? v : null);
|
|
91
91
|
const num = (v) => (typeof v === 'number' ? v : null);
|
|
92
|
+
const isRecord = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
93
|
+
/** The step this version knows, `open_offer`; an answer with none, or with a kind of step a later API adds, reads as null. */
|
|
94
|
+
function checkoutHandoffOf(v) {
|
|
95
|
+
return isRecord(v) && v.type === 'open_offer' && typeof v.buy_url === 'string' ? v : null;
|
|
96
|
+
}
|
|
97
|
+
function openOffersOf(v) {
|
|
98
|
+
return Array.isArray(v) ? v.filter((o) => isRecord(o) && o.type === 'open_offer' && typeof o.buy_url === 'string') : [];
|
|
99
|
+
}
|
|
100
|
+
function browserCheckoutsOf(v) {
|
|
101
|
+
return Array.isArray(v) ? v.filter((b) => isRecord(b) && typeof b.checkout_id === 'string') : [];
|
|
102
|
+
}
|
|
92
103
|
function paymentSourceOf(v) {
|
|
93
104
|
if (!v || typeof v !== 'object')
|
|
94
105
|
return null;
|
|
@@ -102,7 +113,7 @@ function outcomeOf(a) {
|
|
|
102
113
|
authorization_id: str(a.authorization_id),
|
|
103
114
|
};
|
|
104
115
|
}
|
|
105
|
-
const STATUSES = new Set(['needs_input', 'needs_approval', 'order_placed', 'partially_placed', 'declined']);
|
|
116
|
+
const STATUSES = new Set(['needs_input', 'needs_approval', 'order_placed', 'partially_placed', 'declined', 'approved_not_charged']);
|
|
106
117
|
function statusOf(v) { return typeof v === 'string' && STATUSES.has(v) ? v : 'needs_input'; }
|
|
107
118
|
function placementsOf(v) {
|
|
108
119
|
if (!Array.isArray(v))
|
|
@@ -125,6 +136,7 @@ export function turnOf(body) {
|
|
|
125
136
|
catalog: body.catalog && typeof body.catalog === 'object' ? body.catalog : null,
|
|
126
137
|
error_code: str(body.error_code),
|
|
127
138
|
unmatched: Array.isArray(body.unmatched) ? body.unmatched : [],
|
|
139
|
+
checkout_handoff: checkoutHandoffOf(body.checkout_handoff),
|
|
128
140
|
...outcomeOf(body),
|
|
129
141
|
};
|
|
130
142
|
}
|
|
@@ -138,6 +150,8 @@ function conversationOf(body) {
|
|
|
138
150
|
orders: Array.isArray(body.orders) ? body.orders : [],
|
|
139
151
|
carts: cartsOf(body.carts),
|
|
140
152
|
unmatched: Array.isArray(body.unmatched) ? body.unmatched : [],
|
|
153
|
+
open_offers: openOffersOf(body.open_offers),
|
|
154
|
+
browser_checkouts: browserCheckoutsOf(body.browser_checkouts),
|
|
141
155
|
created_at: str(body.created_at) ?? '',
|
|
142
156
|
updated_at: str(body.updated_at) ?? '',
|
|
143
157
|
};
|
|
@@ -231,6 +245,7 @@ export class BuyModule extends ApiModule {
|
|
|
231
245
|
...(input.delivery_address ? { delivery_address: input.delivery_address } : {}),
|
|
232
246
|
...(input.payment_source ? { payment_source: input.payment_source } : {}),
|
|
233
247
|
...(input.approval ? { approval: input.approval } : {}),
|
|
248
|
+
...(input.wait === true ? { wait: true } : {}),
|
|
234
249
|
};
|
|
235
250
|
return this.turn(body, input.timeout_ms);
|
|
236
251
|
}
|
|
@@ -41,7 +41,8 @@ export declare class CardEndpointFactsWatch {
|
|
|
41
41
|
tracks(key: string): boolean;
|
|
42
42
|
/**
|
|
43
43
|
* A request left. Recorded only when it is a document, an XHR or a fetch (or of a type
|
|
44
|
-
* the adapter could not name) on the
|
|
44
|
+
* the adapter could not name) on the origin a profile's record comes from: its facts
|
|
45
|
+
* origin (D-EDGE's payment hub), else its endpoint origin (FastSpring's popup).
|
|
45
46
|
*/
|
|
46
47
|
sent(key: string, request: {
|
|
47
48
|
method: string;
|
|
@@ -6,14 +6,22 @@
|
|
|
6
6
|
// the popup document (script#viewdata) and replaces it with every JSON answer it reads
|
|
7
7
|
// from the session URL, tax included. From attach on, the adapters tell this watch about
|
|
8
8
|
// every request that leaves, every answer and every failure; it keeps, read-only, the
|
|
9
|
-
// documents, XHRs and fetches on the
|
|
10
|
-
// else, a bounded number of them, the busiest
|
|
11
|
-
// checkout lost to that bound can still refuse
|
|
12
|
-
// pauses, capture() waits briefly for any
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// and the API read it with the same
|
|
9
|
+
// documents, XHRs and fetches on the origin a profile's record comes from (its endpoint's,
|
|
10
|
+
// or the one its facts name), and nothing else, a bounded number of them, the busiest
|
|
11
|
+
// checkout session's oldest going first; what a checkout lost to that bound can still refuse
|
|
12
|
+
// its capture. When that profile's card request pauses, capture() waits briefly for any
|
|
13
|
+
// request on the same checkout session that left before the pause, then takes the order
|
|
14
|
+
// record whose answer finished last, the one the page shows, cut by payment-core to the
|
|
15
|
+
// values its reader reads (the total with tax, the currency, test mode and the renewal
|
|
16
|
+
// marks). The create carries that cut record; the client and the API read it with the same
|
|
17
|
+
// rules.
|
|
18
|
+
//
|
|
19
|
+
// D-EDGE's card frame posts the card from card.secure-hotel-booking.com, and the payment hub
|
|
20
|
+
// around it loads the booking from pay.secure-hotel-booking.com (POST /load/<session id>):
|
|
21
|
+
// that answer is the record, and the frame's card post and the hub's own calls are one
|
|
22
|
+
// checkout session by the session id they both carry (cardEndpointFactsSession). Its 3-D
|
|
23
|
+
// Secure browser data (/setDevice) is an inert call; a voucher, points or a bank chosen in
|
|
24
|
+
// the hub after /load leaves the booking unknown, and capture() refuses.
|
|
17
25
|
//
|
|
18
26
|
// A record holds the order only if the page changed nothing after it. payment-core's
|
|
19
27
|
// merchantCardEndpointFactsRole names what each request is to the card request: a call
|
|
@@ -64,7 +72,8 @@ export class CardEndpointFactsWatch {
|
|
|
64
72
|
tracks(key) { return this.exchanges.has(key); }
|
|
65
73
|
/**
|
|
66
74
|
* A request left. Recorded only when it is a document, an XHR or a fetch (or of a type
|
|
67
|
-
* the adapter could not name) on the
|
|
75
|
+
* the adapter could not name) on the origin a profile's record comes from: its facts
|
|
76
|
+
* origin (D-EDGE's payment hub), else its endpoint origin (FastSpring's popup).
|
|
68
77
|
*/
|
|
69
78
|
sent(key, request) {
|
|
70
79
|
if (this.origins.length === 0 || typeof request.url !== 'string' || typeof request.method !== 'string')
|
package/dist/card-endpoint.d.ts
CHANGED
|
@@ -49,6 +49,11 @@ export interface CardEndpointProfile {
|
|
|
49
49
|
*/
|
|
50
50
|
readonly facts?: {
|
|
51
51
|
readonly reader: string;
|
|
52
|
+
/**
|
|
53
|
+
* Null when the record's answers come from the endpoint origin; else the origin they come from, another host of
|
|
54
|
+
* the endpoint's site (D-EDGE's payment hub, beside the card frame that posts the card).
|
|
55
|
+
*/
|
|
56
|
+
readonly origin: string | null;
|
|
52
57
|
readonly responses: readonly {
|
|
53
58
|
readonly method: string;
|
|
54
59
|
readonly path: string;
|
|
@@ -101,9 +106,11 @@ export declare const CARD_ENDPOINT_MODE: "card_endpoint";
|
|
|
101
106
|
* (`detail` says what, naming fields, never values).
|
|
102
107
|
* - `facts_unavailable`: the merchant keeps its own record of the order (profile.facts),
|
|
103
108
|
* and this runtime read none for this checkout, or one it cannot place.
|
|
104
|
-
* - `facts_refused`: the merchant's own record says the order renews on a schedule,
|
|
105
|
-
*
|
|
106
|
-
*
|
|
109
|
+
* - `facts_refused`: the merchant's own record says the order renews on a schedule, that
|
|
110
|
+
* the merchant charges the card later (a hotel rate that only guarantees the booking, a
|
|
111
|
+
* charge on a later date, part of the room left for the stay), or that its checkout is
|
|
112
|
+
* in test mode. A total or currency that differs from the agent's is an
|
|
113
|
+
* AmountMismatchError instead.
|
|
107
114
|
*
|
|
108
115
|
* `not_enabled`, `amount_required`, `approval_required` and `preparation_unsupported`
|
|
109
116
|
* answer the same way for every request on the attachment, so the adapters stop asking;
|
|
@@ -138,7 +145,10 @@ export declare function cardEndpointResendFor(url: string, method: string): Card
|
|
|
138
145
|
* CORS preflight to the same URL defers), so routing a URL is never a decision to pause it.
|
|
139
146
|
*/
|
|
140
147
|
export declare function cardEndpointRouted(url: string): boolean;
|
|
141
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* The origins the record of each profile whose merchant keeps one comes from (its facts.origin, else its
|
|
150
|
+
* endpoint): the only responses card-endpoint-facts.ts records.
|
|
151
|
+
*/
|
|
142
152
|
export declare function cardEndpointFactsOrigins(): readonly string[];
|
|
143
153
|
/**
|
|
144
154
|
* The checkout session a request on the endpoint of a profile with facts belongs to: the
|
|
@@ -148,6 +158,12 @@ export declare function cardEndpointFactsOrigins(): readonly string[];
|
|
|
148
158
|
* request. card-endpoint-facts.ts counts what it keeps, and what a checkout lost, by it.
|
|
149
159
|
* When the paths of two profiles on one origin both fit, the shorter session is the one:
|
|
150
160
|
* every request on a card request's session then gets the card request's own.
|
|
161
|
+
*
|
|
162
|
+
* For a profile whose record comes from its own origin (D-EDGE's payment hub beside the card
|
|
163
|
+
* frame), a request on that origin belongs to the session its last path segment names, the
|
|
164
|
+
* session id every hub URL ends in, and the profile's card request to the session its bind
|
|
165
|
+
* value names, both written on that origin ('https://pay.secure-hotel-booking.com/<id>'): the
|
|
166
|
+
* card post and the hub's own calls are one checkout, as payment-core's role rules read them.
|
|
151
167
|
*/
|
|
152
168
|
export declare function cardEndpointFactsSession(url: string): string | null;
|
|
153
169
|
/** Whether this build's copy of the profile takes payments (enabled, nothing pending, a reviewed HAR, not recurring). */
|