@agent-cards/checkout 0.18.0 → 0.21.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.
Files changed (88) hide show
  1. package/README.md +6 -669
  2. package/cdp.d.ts +1 -0
  3. package/cdp.js +2 -0
  4. package/index.d.ts +1 -0
  5. package/index.js +2 -0
  6. package/package.json +33 -33
  7. package/playwright.d.ts +1 -0
  8. package/playwright.js +2 -0
  9. package/preflight.d.ts +1 -0
  10. package/preflight.js +2 -0
  11. package/CHANGELOG.md +0 -124
  12. package/PREFLIGHT.md +0 -308
  13. package/dist/adyen.generated.d.ts +0 -24
  14. package/dist/adyen.generated.js +0 -64
  15. package/dist/attachment.d.ts +0 -11
  16. package/dist/attachment.js +0 -50
  17. package/dist/braintree.d.ts +0 -2
  18. package/dist/braintree.generated.d.ts +0 -10
  19. package/dist/braintree.generated.js +0 -302
  20. package/dist/braintree.js +0 -2
  21. package/dist/builtin-registry.generated.d.ts +0 -2
  22. package/dist/builtin-registry.generated.js +0 -1
  23. package/dist/card-fields.generated.d.ts +0 -3
  24. package/dist/card-fields.generated.js +0 -46
  25. package/dist/cdp.d.ts +0 -189
  26. package/dist/cdp.js +0 -2194
  27. package/dist/checkout-com.generated.d.ts +0 -4
  28. package/dist/checkout-com.generated.js +0 -183
  29. package/dist/client.d.ts +0 -618
  30. package/dist/client.js +0 -1251
  31. package/dist/hosted-form.d.ts +0 -44
  32. package/dist/hosted-form.js +0 -78
  33. package/dist/index.d.ts +0 -13
  34. package/dist/index.js +0 -6
  35. package/dist/lifecycle.d.ts +0 -165
  36. package/dist/lifecycle.js +0 -370
  37. package/dist/mercado-checkout.d.ts +0 -20
  38. package/dist/mercado-checkout.generated.d.ts +0 -52
  39. package/dist/mercado-checkout.generated.js +0 -198
  40. package/dist/mercado-checkout.js +0 -108
  41. package/dist/owned-shop.generated.d.ts +0 -24
  42. package/dist/owned-shop.generated.js +0 -108
  43. package/dist/paysafe.generated.d.ts +0 -12
  44. package/dist/paysafe.generated.js +0 -87
  45. package/dist/playwright.d.ts +0 -3
  46. package/dist/playwright.js +0 -3
  47. package/dist/preflight-capabilities.generated.d.ts +0 -1253
  48. package/dist/preflight-capabilities.generated.js +0 -1929
  49. package/dist/preflight-catalog.json +0 -4595
  50. package/dist/preflight-playwright.d.ts +0 -34
  51. package/dist/preflight-playwright.js +0 -355
  52. package/dist/preflight-schemas.json +0 -1110
  53. package/dist/preflight.d.ts +0 -1
  54. package/dist/preflight.generated.d.ts +0 -1965
  55. package/dist/preflight.generated.js +0 -556
  56. package/dist/preflight.js +0 -2
  57. package/dist/preparation.d.ts +0 -31
  58. package/dist/preparation.js +0 -164
  59. package/dist/prepared-processor.d.ts +0 -10
  60. package/dist/prepared-processor.js +0 -122
  61. package/dist/recurly.generated.d.ts +0 -1
  62. package/dist/recurly.generated.js +0 -87
  63. package/dist/registry.d.ts +0 -76
  64. package/dist/registry.js +0 -296
  65. package/dist/spreedly.generated.d.ts +0 -10
  66. package/dist/spreedly.generated.js +0 -332
  67. package/dist/stripe-checkout.d.ts +0 -81
  68. package/dist/stripe-checkout.generated.d.ts +0 -82
  69. package/dist/stripe-checkout.generated.js +0 -1004
  70. package/dist/stripe-checkout.js +0 -140
  71. package/dist/substitute.d.ts +0 -38
  72. package/dist/substitute.js +0 -23
  73. package/dist/substitutions.generated.d.ts +0 -10
  74. package/dist/substitutions.generated.js +0 -66
  75. package/examples/existing-browser.mjs +0 -63
  76. package/examples/preflight/classify-direct.mjs +0 -21
  77. package/examples/preflight/classify-kernel.mjs +0 -30
  78. package/examples/preflight/inspect-browser.mjs +0 -44
  79. package/examples/preflight/kernel-native/README.md +0 -112
  80. package/examples/preflight/kernel-native/documented-adapters.json +0 -113
  81. package/examples/preflight/kernel-native/inventory.json +0 -233
  82. package/examples/preflight/kernel-native/qualification.mjs +0 -182
  83. package/examples/preflight/kernel-profile.empty.json +0 -11
  84. package/examples/preflight/mollie-hosted.observations.json +0 -23
  85. package/examples/preflight/mollie-hosted.result.json +0 -103
  86. package/examples/preflight/stripe-script.direct.result.json +0 -92
  87. package/examples/preflight/stripe-script.observations.json +0 -16
  88. package/examples/preflight/stripe-script.result.json +0 -87
package/README.md CHANGED
@@ -1,675 +1,12 @@
1
1
  # @agent-cards/checkout
2
2
 
3
- Let your browser agents pay with **the user's own card**, without your
4
- infrastructure ever touching card data.
5
-
6
- Your agent drives checkout normally. When the page tries to tokenize a card, we
7
- pause that one request, ask the cardholder to approve on their device, and their
8
- device supplies the card and calls the merchant. You get back the response to
9
- replay. A real card never enters your process, your logs, or your network.
10
-
11
- ```
12
- your agent ──drives──> merchant checkout
13
- │ tokenization request
14
- ▼
15
- [ paused by this SDK ]
16
- │ template only, dummy card
17
- ▼
18
- Agentcard ──notify──> cardholder's device
19
- │ decrypts card locally
20
- ▼
21
- merchant's card vault
22
- ┌────────token────────┘
23
- ▼
24
- [ request resumed ] ──> order completes
25
- ```
26
-
27
- ## Install
28
-
29
- ```bash
30
- npm i @agent-cards/checkout
31
- ```
32
-
33
- Upgrading from 0.2.x? Read the [migration notes](./CHANGELOG.md), especially
34
- the unknown-outcome, cancellation and browser-context requirements.
35
-
36
- To inspect a checkout before card entry with SDK `0.9.0` or later, read [Check checkout support](./PREFLIGHT.md).
37
- The read-only helper detects all 23 registered PSPs and reports processor support separately
38
- from an identified checkout flow. Kernel native coverage requires its own capability profile.
39
-
40
- ## Use it
41
-
42
- Two lines against a CDP session you already have:
43
-
44
- ```ts
45
- import { VaultClient, attachToCdp } from '@agent-cards/checkout';
46
-
47
- const vault = new VaultClient({
48
- clientId: process.env.AGENTCARD_CLIENT_ID!,
49
- clientSecret: process.env.AGENTCARD_CLIENT_SECRET!,
50
- });
51
-
52
- // Pull the current processor list. attachToCdp arms the browser from it, so
53
- // without this you only intercept the processors built into your installed
54
- // version. Safe to call on every run: a failed fetch keeps the built-ins.
55
- await vault.syncRegistry();
56
-
57
- await attachToCdp(cdp, pageSessionId, {
58
- vault,
59
- user: 'usr_123', // whose card should pay
60
- merchant: 'vanman.shop',
61
- amount: 583, // your hint, an integer in the currency's smallest unit (or a decimal string: '5.83')
62
- currency: 'usd', // "$5.83" is derived for the approval screen
63
- onApprovalUrl: (url) => sendToUser(url), // SMS, push, email, iMessage: your call
64
- });
65
- ```
66
-
67
- `amount` is your hint: an integer in the currency's smallest unit (583 for
68
- $5.83), or a decimal string in normal units ('5.83'), with `currency`. The
69
- processor's own amount is the higher authority: Agentcard reads it from the
70
- paused request where the processor puts it there, or from the Stripe intent
71
- the request names, right before the cardholder's device replays, and the
72
- company's caps are judged on it. A hint lets a bad purchase be refused the
73
- moment it opens; a hint more than one smallest unit away from the processor's
74
- amount is refused with nothing charged (`AmountMismatchError`), and after the
75
- replay the charge is reconciled against the approval
76
- (`ReplayResponse.amountVerified`, `chargedAmount`, and `chargedKind`:
77
- `captured` for a succeeded intent's `amount_received`, `authorized` for a
78
- manual-capture intent's `amount_capturable`, `none` when nothing is collected
79
- yet; plus the `checkout_authorization.amount_mismatch` webhook to your server
80
- when the charge disagreed). Every result carries `amountAuthority`:
81
- `processor`, `agent`, `page`, or `none`. A display string is never sent;
82
- Agentcard derives it.
83
-
84
- Then let your agent click "Pay" like it always does. `attachToCdp` pauses the
85
- request for approval. Merchant timeouts still apply to the request itself: Square's observed tokenization deadline
86
- 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 (observed on Adyen Web 6.41 and 6.44), each including approval and handoff. These are the processors' limits, not ones the SDK enforces: the SDK's own authorization wait stays 15 minutes.
87
-
88
- 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`, event `merchant_request_lost`) and answers the page's next request for the same purchase from it: same endpoint, same method, same top-level document, same page total, and a body of the same shape carrying the same card placeholder and the same amount when the request names one. An amount has to be in evidence somewhere (your `amount` hint, a `pageAmount` reader, or the request's own bytes); with none, a retry is a new question and the approval is retired unused. Braintree's client re-issues its mutation on its own within a second; on other processors the agent clicks Pay again. If the approval lands before the page asks again, the controller reads `ready_to_submit` with reason `awaiting_merchant_retry` (event `approval_awaiting_merchant_retry`): click Pay once, and that request is answered with no second prompt on the phone. The wait for that request is bounded (`merchantRetryWaitMs`, two minutes by default, never past the approval window); when it ends with no request, the SDK retires the approval through the API with reason `merchant_never_retried`, the controller reads `declined` with that reason, nothing was charged, and the cardholder's approval page says so. This applies to `token` and `cse` checkouts; a hosted form, a prepared checkout and a native Stripe Checkout step keep the behaviour below. For human approval on a short-deadline processor, `controller.prepare()` before the first Pay action, shown below, still finishes the request inside its own deadline. For Adyen, `prepare()` moves the approval before Pay, so only the device-side encryption runs inside Adyen Web's minute.
89
-
90
- Playwright:
91
-
92
- ```ts
93
- import { attachToPlaywright } from '@agent-cards/checkout/playwright';
94
- await attachToPlaywright(page, { vault, user, merchant, amount });
95
- ```
96
-
97
- ## Credentials
98
-
99
- Use your **OAuth client credentials**, not an `sk_` API key — those are retired,
100
- and the checkout endpoints reject them (`client_credentials_required`) because an
101
- authorization is bound to the confidential client that created it. The SDK does
102
- the `client_credentials` exchange for you, caches the token, and refreshes it
103
- once on a 401. Create a client from the dashboard Credentials page or with
104
- `agent-cards-admin oauth-clients create`.
105
-
106
- ## Why you need the SDK and not just `Fetch.enable`
107
-
108
- Card fields render in **cross-origin iframes**, which are separate CDP targets.
109
- Enabling `Fetch` on the page session never sees the tokenization request. You
110
- need recursive `Target.setAutoAttach({ flatten: true })` on every nested target,
111
- then `Fetch.enable` on each, then `Runtime.runIfWaitingForDebugger` to unpause
112
- them. That, plus which headers a merchant requires you to replay verbatim, is
113
- what this package encapsulates.
114
-
115
- ## What runs where
116
-
117
- | | Sees the real card |
118
- |---|---|
119
- | Your agent / browser | **no** — only a dummy PAN and a token |
120
- | Agentcard servers | **no** — a request template and a token |
121
- | Cardholder's device | yes — decrypts locally, calls the merchant directly |
122
-
123
- Because your process only ever handles a dummy card and an opaque token, this
124
- integration is designed to keep you out of PCI scope. Get your own QSA's read
125
- before you put that in writing.
126
-
127
- ## Supported processors
128
-
129
- Coverage is specific to the processor request format, merchant setup, browser transport and follow-up flow. A recognized endpoint is not proof that every store using that processor completes checkout.
130
-
131
- | Processor | Status |
132
- |---|---|
133
- | Shopify | supported, verified end to end |
134
- | Stripe | tokenization replay and direct card-bearing PaymentIntent confirms are implemented; direct confirms read the intent's amount back from Stripe; a hint sent as `amount` + `currency` must agree with it. Browser token-to-intent continuation is unsupported and held. Validate the exact merchant flow before pilot use |
135
- | Braintree card tokenization | Prepared checkout supported; one live Haymarket Books ebook purchase with SDK `0.5.0` confirmed merchant fulfillment and SDK `completed` using a merchant receipt resolver. Independent processor capture/settlement, live 3DS and PayPal wallet flows remain unverified. |
136
- | Checkout.com | supported |
137
- | Mercado Pago | Card tokenization and prepared checkout are implemented. Guest Checkout Pro in Mexico also corrects the issuer for one native card association when the selected card has the same brand and type. One live MXN 40 Lotería Chida purchase with published SDK 0.10.0 completed automatically through Pay; the merchant confirmed paid status and PDF fulfillment. The historical SDK result remains unknown because the private receipt adapter rejected a relative download URL; a separate read with the corrected adapter confirms that same paid receipt. Independent processor capture/settlement and other country or integration paths remain unverified. |
138
- | VGS Collect (Very Good Security; Wolt) | not supported: VGS's proxy aliases only submissions from its own iframe, so a replay from the cardholder's device is refused by the merchant (verified on Wolt, 2026-09-03). Not recognized, so the agent's browser is not paused there |
139
- | Adyen | supported (mode `cse`): the vault encrypts the card for Adyen on the cardholder's device and your browser sends it. Sessions flow only: the paused request is `/checkoutshopper/v1/sessions/{id}/payments` on Adyen's own hosts; a merchant that posts the encrypted fields to its own server is not recognized, so nothing pauses there. Without a preparation, approval starts at Pay and must land before Adyen Web's own request timeout (observed at 60 seconds on Adyen Web 6.41 and 6.44; not enforced by the SDK); `prepare({ psp: 'adyen' })` moves the approval before Pay |
140
- | Tranzila | supported (mode `hosted_form`): the cardholder finishes on Tranzila's own page; the paused form navigation resolves to a synthetic page, and you poll the merchant's order state |
141
-
142
- The recognizer list is fetched from the API at runtime (`vault.syncRegistry()`),
143
- so new processors work without you shipping a release. `attachToCdp` derives the
144
- `Fetch.enable` url patterns from that same list rather than a constant, which is
145
- why the sync call belongs before the attach. `vault.cardUrlPatterns()` returns
146
- those patterns if you arm a CDP connection yourself. Call
147
- `GET /v2/checkout/recognizers?modes=token,cse,hosted_form` for the list that is
148
- live right now.
149
-
150
- ## Modes
151
-
152
- Each recognizer carries a `mode` (absent means `token`), and every authorization
153
- carries the mode it was handled in. The adapters do the right thing for all
154
- three; the difference matters if you drive `authorize()` yourself.
155
- `ReplayResponse` is a union, so branch on `mode`.
156
-
157
- - **`token`** (every processor but Adyen). The cardholder's device calls the
158
- processor and reports its answer; `authorize()` resolves with `status`,
159
- `headers` and `body` to fulfill the paused request with. The browser checks
160
- a fulfilled answer exactly as it checks a real one, so when the page called
161
- the processor cross-origin (Stripe always does: Checkout on
162
- `checkout.stripe.com` and Elements in the `js.stripe.com` frame both fetch
163
- `api.stripe.com`) the answer must carry `access-control-allow-origin` for
164
- the request's own `Origin`, or the page's fetch rejects and the checkout
165
- reports a connection error even though the cardholder approved. The
166
- adapters add those headers (`corsHeadersFor` + `withCorsHeaders`, exported
167
- for a runtime that fulfills by hand, and `corsDecision` when you also want
168
- the reason) and report the decision on the `authorized` event as `cors`:
169
- `echoed`, `same_origin`, or `none` (no usable Origin on the request, so
170
- the page could not read the answer). Only what a browser serializes is
171
- echoed: one canonical http(s) origin, or the opaque `null`. Shopify's
172
- card iframe posts to its own origin, so it never needed them.
173
- - **`cse`** (Adyen). Adyen's own page SDK encrypts the card before the request
174
- leaves the browser, so the paused body carries ciphertext. The cardholder's
175
- device produces the same ciphertext under the merchant's Adyen public key
176
- (fetched by Agentcard from Adyen's host when the request is parked) and
177
- `authorize()` resolves with `substitutions: { encoding: 'json', at, fields,
178
- remove }`. Write them into the paused body with
179
- `substituteEncryptedFields(body, substitutions)` and CONTINUE the request
180
- from the same browser (`Fetch.continueRequest` with the rewritten
181
- `postData`, or Playwright's `route.continue({ postData })`): its session
182
- data, risk data and cookies must stay its own. Only the four encrypted
183
- fields change, and the siblings named in `remove` are dropped (Adyen's
184
- `brand`, which adyen-web derived from the dummy digits the agent typed:
185
- left in place it names the wrong card and Adyen refuses the mismatch;
186
- absent, Adyen reads the brand off the card it decrypts). A body that lacks
187
- the fields throws `SubstitutionError`, which is not terminal. Adyen answers the browser,
188
- so `charged_kind` is null on the approval and the merchant's order state is
189
- the outcome to poll.
190
- - **`hosted_form`** (Tranzila). The processor's hosted card form submits the
191
- card as a TOP-LEVEL form post, so the paused request is a page navigation
192
- (the adapters arm `Fetch.enable` with no resource-type filter and attach the
193
- processor's iframe, which is how a Document request on `direct.tranzila.com`
194
- gets paused at all). The cardholder's device rebuilds that form with the
195
- real card and submits it itself; the processor answers the device, and
196
- `authorize()` resolves with `{ mode: 'hosted_form', kind:
197
- 'submitted_on_device', outcome: 'unverified', submittedAt }` once the
198
- device reports the form left. **This is not an approved payment.** The
199
- stamp is the cardholder's device attesting that the form left it;
200
- Agentcard holds no processor evidence on this mode and cannot obtain any,
201
- so the API finishes the authorization as `submitted_on_device` (never
202
- `approved`) and sends your server `checkout_authorization.submitted`
203
- (never `.approved`). Treat it as "the person paid, or tried to, on their
204
- own device" and confirm the order with the merchant before you count it.
205
- There is no response to replay: FULFIL the paused navigation with
206
- `hostedFormSubmittedPage({ authorizationId, merchant, submittedAt })` (200,
207
- `text/html`, `x-agentcard-checkout: submitted_on_device`, a `<meta
208
- name="agentcard-checkout">` and an inert JSON block saying "submitted on
209
- the cardholder's device, payment unverified, do not resubmit"), the way
210
- the adapters do. Do not abort it: an aborted navigation renders nothing,
211
- the iframe silently keeps its dummy-card form, and the agent's next move is
212
- to click Pay again. Do not fake the processor's result page either: this
213
- SDK does not know the outcome. The adapters emit `submitted_on_device` (not
214
- `authorized`), refuse a byte-identical re-post of the same form for 15
215
- minutes (`hostedFormRepeatQuietMs`), and the API answers a regenerated one
216
- with `409 duplicate_submission`, which the adapters quiet the way they quiet
217
- a decline (`approvalCooldownMs`): the page's immediate re-posts are refused
218
- without a round trip, and once the prior authorization is declined or
219
- expired the same form is a new question. Confirm the order with the
220
- merchant, which learns the outcome from the processor.
221
-
222
- `syncRegistry()` asks the API for `SUPPORTED_MODES` only
223
- (`token,cse,hosted_form`), so a processor whose flow this build cannot finish
224
- is never paused; the API serves `hosted_form` entries only to callers that ask.
225
- A registry mode this SDK cannot finish throws `UnsupportedModeError` before
226
- creation. An approval returned in an unexpected mode has an unknown outcome
227
- and holds the attachment for reconciliation. The `authorized` event's detail names
228
- the `mode`, the `authorizationId` and, for `cse`, the `fields` that were
229
- substituted; it never carries ciphertext. The `submitted_on_device` event's
230
- detail names the `authorizationId`, `submittedAt` and `outcome:
231
- 'unverified'`; it is not an `authorized` event and must not be counted as
232
- one. `amountAuthority` on every replay is `stripe_payment_intent`,
233
- `hosted_form_sum` (the form's own amount) or `display_only`.
234
-
235
- ## Errors worth handling
236
-
237
- - `ApprovalTimeoutError` — the server confirms the authorization expired without a replay attempt. A local deadline is different: `PaymentOutcomeUnknownError` means the approval link may still be valid, so reconcile the merchant order before another attempt.
238
- - `ApprovalDeclinedError` — the user said no.
239
- - `AmountMismatchError`: the processor's amount did not match the amount the
240
- user was (or would have been) asked to approve. Nothing was charged. An
241
- `ApprovalDeclinedError` with `expectedCents`, `actualCents`, `currency`,
242
- `code: 'amount_mismatch'` and `stage`: `'pre_replay'` (checked right before
243
- the device would have sent the card; `authorizationId` names the declined
244
- authorization) or `'create'` (the intent already disagreed when the request
245
- was parked; no authorization exists, `authorizationId` is null). Per
246
- request, not per page: a merchant can still update an intent's amount
247
- until it is confirmed, so the adapters quiet the page's immediate retry
248
- and judge the next request afresh instead of latching.
249
- - `IntentNotConfirmableError`: the PaymentIntent was already charged, is
250
- processing, or is authorized and on hold (or canceled), so Agentcard
251
- refused to replay a confirm at it. An `ApprovalDeclinedError` with
252
- `code: 'intent_not_confirmable'`. Deliberately not "nothing was charged":
253
- check the intent at Stripe before retrying.
254
- - `ProcessorRefusedError`: the cardholder's device reported a processor
255
- request rejection. `pspErrorCode` carries the processor's code; optional
256
- `processorError` carries bounded Razorpay reason, source, step and payment/order
257
- identifiers when the API has them. A generic code such as `BAD_REQUEST_ERROR`
258
- does not establish an issuer decline or prove no money moved. Reconcile the
259
- merchant payment before retrying. This remains an `ApprovalDeclinedError`
260
- with `code: 'processor_refused'` for compatibility.
261
- The attachment records `status: 'declined', reason: 'processor_refused'`
262
- and holds further card requests. After confirming merchant failure, call
263
- `retryAfterMerchantFailure({ status: 'failed' })` to permit a deliberate new
264
- attempt immediately, without waiting for the user-decline cooldown. Do not
265
- automatically create a new attachment after this error;
266
- the guard applies only within the existing attachment.
267
- - `CheckoutApiError` with `code === 'amount_unverifiable'`: Stripe could not
268
- be asked (502; the SDK retries twice, 500ms then 1500ms, before throwing)
269
- or the paused request lacked its client secret or publishable key (400).
270
- `code === 'intent_not_confirmable'` at create (409) means the intent was
271
- already used; the adapters stop intercepting for that page.
272
- `code === 'cse_key_unavailable'` (502) means Adyen did not answer the
273
- public-key fetch and is retried the same way; `cse_client_key_unknown`
274
- (400) means Adyen does not know the merchant's `clientKey`, and
275
- `cse_template_unsupported` (400) means the paused body carries no
276
- encrypted card fields to fill (a stored card, a wallet, a single-blob
277
- `encryptedCard`); both are terminal for that page.
278
- `hosted_form_template_incomplete` (400) means the paused form lacks a
279
- field the device fills or the processor requires, `hosted_form_gated`
280
- (400) means it carries a live captcha token the device could never
281
- re-submit, and `hosted_form_field_refused` (400, with `field` and a
282
- `reason` of `stored_credential`, `not_a_sale` or `callback_host`) means
283
- the form asks the processor for something other than one plain sale
284
- reporting to the merchant you named (a reusable token in or out, a sale
285
- mode that is not a sale, a callback URL off the merchant's host: name the
286
- merchant by its hostname when the form carries callback URLs); all three
287
- are terminal for that page. `duplicate_submission` (409,
288
- with `prior_authorization_id` and `prior_status`) means this exact
289
- submission already has, or already had, its prompt: the adapters quiet the
290
- page's re-posts for `approvalCooldownMs` (no second notification for one
291
- payment) and judge the next request afresh, since the prior authorization
292
- declines or expires and the same form is then a new question.
293
- - `CardEncryptedError`: this processor encrypts the card in-page and its
294
- registry entry does not (yet) say the vault can produce that ciphertext;
295
- route the purchase to an Agentcard-issued card instead.
296
- - `UnsupportedModeError`: the registry requests a mode this SDK cannot finish
297
- before an authorization exists. Upgrade. An unexpected approved mode instead
298
- raises `PaymentOutcomeUnknownError` and requires reconciliation.
299
- - `SubstitutionError`: a `cse` approval could not be written into the paused
300
- body (the four encrypted fields were not there). The request is failed and
301
- the next one is judged afresh.
302
-
303
- ## Building this package
304
-
305
- It declares **no dependencies**, matching `packages/vault`, so the workspace
306
- lockfile needs no importer entry for it (a new package with its own deps cannot
307
- be installed here without regenerating the lockfile, and a full regen drifts
308
- unrelated transitive versions). Build it with the workspace TypeScript:
309
-
310
- ```bash
311
- cd packages/checkout && pnpm build && pnpm test
312
- ```
313
-
314
-
315
- ## Browser integration and merchant outcomes
316
-
317
- `attachToPlaywright` works with an existing Chromium page reached through
318
- `chromium.connectOverCDP`. Browserbase supplies `session.connectUrl`; Kernel
319
- supplies `browser.cdp_ws_url`; a custom browser must expose a compatible CDP
320
- endpoint. This is Agentcard's **direct SDK** path. Kernel's native Vault alias
321
- integration is a separate provider adapter with its own coverage and lifecycle;
322
- do not install both interceptors on the same checkout without validating how
323
- those routes interact.
324
-
325
- The local browser suite validates Chromium and nested cross-origin frames over
326
- both Playwright routing and a raw, session-aware CDP connection. It does not
327
- establish live Browserbase, Kernel, 3DS or merchant coverage. A raw page-scoped
328
- Playwright `CDPSession` is not the `CdpLike` interface. Raw CDP must preserve the
329
- `sessionId` on every command/event and allow recursive target attachment.
330
- Initial arming errors reject `attachToCdp`; a child that cannot be armed remains
331
- paused and reports `browser_interception_unavailable` for operator recovery.
332
-
333
- Await attachment before clicking Pay. Both adapters stop waiting for browser
334
- setup after 30 seconds and throw `CheckoutAttachmentError` with
335
- `code: 'checkout_attachment_failed'` and `reason: 'timeout'`, `'closed'` or
336
- `'unavailable'`. Use `attachmentTimeoutMs` to choose a setup deadline from 1 to
337
- 300000 milliseconds, separately from the approval's `timeoutMs`.
338
- Close the failed checkout page and create a fresh browser context before
339
- trying again. A late setup response cannot reopen the failed attachment or
340
- request approval; intercepted card requests remain blocked. A setup failure
341
- does not establish the status of any earlier purchase.
342
-
343
- Use a checkout context created with `serviceWorkers: 'block'`. Playwright cannot
344
- route requests intercepted by a service worker. The SDK rejects already active
345
- service workers, but that check cannot prevent a site from registering one
346
- later in an existing context configured to allow them. Attach before entering
347
- card fields; keep the existing checkout tab. Separate popup tabs need their own
348
- attachment. A page route does not cover a popup's first navigation; a popup
349
- which submits payment on that navigation requires a separately validated
350
- context/browser-level integration. Existing `page.route` handlers must call
351
- `route.fallback()` when they do not handle a request; later routes have priority.
352
-
353
- Both adapters now return a controller; existing code that ignores the return
354
- value continues to work. Choose `requireMerchantResult: true` for a pilot:
355
-
356
- ```ts
357
- const checkout = await attachToPlaywright(page, {
358
- vault, user, merchant, amount, currency,
359
- requireMerchantResult: true,
360
- onStateChange: state => recordState(state),
361
- onUserAction: action => deliverPrivatelyToUser(action),
362
- resolveMerchantResult: async state => readMerchantOrder(state),
363
- paymentEndpoints: [
364
- { origin: 'https://payments.example.com', pathname: '/submit', methods: ['POST'] },
365
- ],
366
- });
367
-
368
- // Your existing agent dispatches checkout. Later, once the paused request resumes:
369
- const state = await checkout.reconcile();
370
- if (state.status === 'completed') await finishAgentTask(state);
371
- ```
372
-
373
- `resolveMerchantResult` must verify the merchant's result for the original
374
- payment attempt. It returns one of:
375
-
376
- - `{ status: 'completed', orderId }`: merchant-confirmed success with a genuine order or receipt ID. Existing integrations keep this form.
377
- - `{ status: 'completed', confirmation: { kind: 'merchant_payment', authorizationId } }`: merchant-confirmed payment when no order or receipt ID is available. The ID must match the current checkout authorization.
378
- - `{ status: 'failed' }`: merchant confirmed the attempt failed; no successful payment/order exists.
379
- - `{ status: 'pending' }` or `{ status: 'unknown' }`: keep waiting or reconcile; never click Pay again.
380
- - `{ status: 'requires_user_action', reason: '3ds' | 'redirect' | 'other' }`: deliver your own browser live view or supported challenge UI to the user.
381
-
382
- For a merchant that confirms payment without returning an order ID, your
383
- resolver can return the explicit payment confirmation:
384
-
385
- ```ts
386
- const checkout = await attachToPlaywright(page, {
387
- vault, user, merchant, amount, currency,
388
- requireMerchantResult: true,
389
- resolveMerchantResult: async state => {
390
- const payment = await readOriginalMerchantPayment(state);
391
- if (!payment.confirmed || !state.authorizationId) return { status: 'unknown' };
392
- return {
393
- status: 'completed',
394
- confirmation: {
395
- kind: 'merchant_payment',
396
- authorizationId: state.authorizationId,
397
- },
398
- };
399
- },
400
- });
401
- ```
402
-
403
- `readOriginalMerchantPayment` is your merchant-specific check. The check must
404
- match the original payment request, amount, currency and selected card, and
405
- verify authoritative merchant success for that attempt. HTTP 200 alone, a card
406
- token, a success URL or text that anyone can open does not establish payment.
407
- Copying `state.authorizationId` without checking the payment is insufficient.
408
- The SDK checks the authorization binding; it does not independently authenticate
409
- the merchant evidence supplied by your resolver.
410
-
411
- Return one completion form at a time. The payment-confirmation form leaves
412
- `state.orderId` absent and exposes `state.confirmation` with the exported
413
- `MerchantPaymentConfirmation` type. Your consumer should finish on
414
- `state.status === 'completed'` and treat `orderId` as optional. A missing or
415
- mismatched authorization, or `{ status: 'completed' }` without either completion
416
- form, leaves the outcome unknown. Confirmed completion clears stale failure or
417
- authentication reasons and continues to block further payment submissions.
418
- Never invent an order ID from a token or authorization ID.
419
-
420
- The SDK does not infer order success from `authorized` or a tokenization reply,
421
- and does not claim to detect or solve arbitrary 3DS challenges. Your merchant
422
- resolver (or `checkout.requestUserAction('3ds')` when your browser observes it)
423
- drives that hook. Deliver `onUserAction` approval URLs privately: they are
424
- capabilities and never belong in general telemetry. Observer exceptions are
425
- isolated from the payment handoff.
426
-
427
- Native Stripe Checkout emits `checkout_blocked` before the existing `blocked`
428
- event when its local preparation rejects a request. Its
429
- `StripeCheckoutBlockedDetail` contains only fixed codes: `version: 1`,
430
- `processor: 'stripe'`, endpoint family, phase, stage, reason, optional validation code, gate state, and
431
- disposition. It contains no URLs, identifiers, request-derived field names or values, or exception text.
432
-
433
- | Field | Values |
434
- | --- | --- |
435
- | `endpoint_family` | `payment_methods`, `payment_page_confirm`, `other` |
436
- | `phase` | `tokenization`, `final`, `unknown` |
437
- | `stage` | `request_read`, `classification`, `claim`, `readiness`, `document`, `stub_response` |
438
- | `validation_code` | Shared-core `StripeCheckoutValidationCode`; present only for `request_validation_failed` |
439
- | `gate_state` | `fresh`, `stubbed`, `submitted`, `stopped` |
440
- | `disposition` | `active_claim_preserved`, `checkout_stopped` |
441
-
442
- `reason` is the exported `StripeCheckoutBlockReason` union. It identifies SDK
443
- claim and readiness failures, such as `duplicate_confirmation`, `document_changed`,
444
- or `attachment_not_ready`. A shared-core rejection reports
445
- `request_validation_failed` and a fixed `validation_code` identifying the failed
446
- check, or `unclassified` when no recognized code is available. Initial request
447
- classification uses phase `unknown`; a billing capture rejection uses
448
- `tokenization`, and a billing attachment rejection uses `final`.
449
- For example, `form_field_unknown` identifies an allowlist rejection without
450
- revealing the field name or value. Identifying that field requires a separate
451
- reviewed synthetic fixture; the code alone does not establish or fix a processor
452
- schema mismatch. `gate_state` records the state before the adapter handles
453
- the failure, though document validation may already have stopped the gate.
454
-
455
- `active_claim_preserved` means a valid duplicate was refused without invalidating
456
- the original request's claim. Do not cancel that checkout in response to the duplicate.
457
- `checkout_stopped` means the local preparation was retired; reconcile any existing
458
- authorization before another attempt. Neither disposition proves a payment outcome.
459
-
460
- With `requireMerchantResult`, subsequent card requests stay blocked after
461
- handoff. Stripe tokenization handoffs (`/v1/payment_methods`, `/v1/tokens`,
462
- `/v1/sources`, `/v1/confirmation_tokens`) always hold further recognized card
463
- requests, even when that option is false. A tokenization approval has no
464
- authoritative binding to a specific PaymentIntent, amount or currency, and the
465
- page cannot supply one. The SDK reports `awaiting_merchant` with reason
466
- `stripe_tokenization_unbound`, while ordinary browser traffic stays available.
467
-
468
- One follow-up can continue: the page's own card-free PaymentIntent confirm that
469
- pays with the approved token (`confirmCardPayment` with `payment_method: 'pm_...'`,
470
- `confirmPayment` with the confirmation token). The SDK asks the API, which reads
471
- the payment from Stripe and allows it only for the approved amount and
472
- currency, on the same Stripe account, paid with exactly the approved token.
473
- Allowed, the request continues untouched, the SDK emits `stripe_payment_continued`
474
- and the reason becomes `stripe_payment_continued`. One confirm continues per
475
- approval; every other follow-up, including an unrelated intent, a changed
476
- amount and a retry, stays blocked.
477
- Unrecognized merchant-server endpoints remain outside this guard unless listed
478
- in `paymentEndpoints`; this is not a guarantee against a merchant charging a
479
- saved token on its own server.
480
- A direct card-bearing PaymentIntent confirm remains supported with the backend's
481
- existing amount verification when `amount` and `currency` are supplied.
482
-
483
- Hosted-form submissions also always stay blocked because their payment outcome
484
- is unverified. `reconcile()` calls the resolver once, coalescing concurrent calls.
485
- After an explicit merchant-confirmed failure, the application may call
486
- `checkout.retryAfterMerchantFailure({ status: 'failed' })` to permit another
487
- attempt where the attachment permits recovery. This is an assertion from your
488
- merchant integration, not a timeout or a best guess. Completed orders, cancelled
489
- attachments and attachments that issued an unbound Stripe token cannot reset
490
- this way, including when the token's browser delivery acknowledgement was lost.
491
- Reconcile the merchant outcome and use a separately validated checkout flow;
492
- do not reuse that token in a new attachment as a workaround. Configuration and
493
- unsupported-mode failures require fixing the integration. Bank flows requiring
494
- another confirmation and other stored-token chains remain unverified.
495
-
496
- Worldpay, Bambora and Mercado Pago preparation requires SDK 0.6.0 or later and the matching API and Vault release.
497
-
498
- Prepare a Square, Braintree, Worldpay, Bambora or Mercado Pago checkout before the first Pay action so the cardholder can approve before the native card request starts. The API and Vault deployments must support the selected processor:
499
-
500
- ```ts
501
- const checkout = await attachToPlaywright(page, {
502
- vault, user: 'your-user-id', merchant: 'Example merchant',
503
- amount: 100, currency: 'USD',
504
- onApprovalUrl: deliverPrivatelyToCardholder,
505
- });
506
- const preparation = await checkout.prepare({
507
- psp: 'braintree',
508
- environment: 'production', // Square, Braintree and Worldpay: production or sandbox
509
- });
510
- // The cardholder has consented and unlocked the same approval document.
511
- // No processor request or payment has started.
512
- await page.getByRole('button', { name: 'Pay', exact: true }).click();
513
- ```
514
-
515
- `prepare()` is available on both Playwright and raw CDP controllers. It requires `amount` and `currency`, must precede the first recognized card request, and returns only when the cardholder's device is ready. It delivers the preparation URL through `onApprovalUrl` and `onUserAction`; binding the subsequent authorization sends no second approval link or SMS. The approval page on the cardholder's device must stay open. Its selected card, merchant origin, declared merchant, amount, currency, processor and environment bind one fresh request. The amount's authority is `agent`; a card token does not enforce the merchant's eventual charge amount.
516
-
517
- | Processor | `environment` | Fresh native request |
518
- | --- | --- | --- |
519
- | Square | `production` or `sandbox` | Matching Square `/v2/card-nonce` host |
520
- | Braintree | `production` or `sandbox` | Matching Braintree GraphQL host and guest `TokenizeCreditCard` mutation |
521
- | Worldpay | `production` or `sandbox` | Matching Access Worldpay host and `/sessions/card` |
522
- | Checkout.com | `production` or `sandbox` | Fresh guest-card JSON POST to `/tokens` on the matching API or card-acquisition-gateway host |
523
- | Bambora | `shared` | `/scripts/tokenization/tokens` on `api.bam.shift4api.net` or `api.na.bambora.com` |
524
- | Mercado Pago | `shared` | `api.mercadopago.com/v1/card_tokens` with a fresh card body |
525
- | Recurly | `shared` | Form-encoded POST to `/js/v1/token` on `api.recurly.com` or `api.eu.recurly.com` |
526
- | Spreedly | `shared` | Native iframe JSON POST to `/v1/payment_methods/restricted.json` on `core.spreedly.com` |
527
- | Adyen | `production` or `sandbox` | The Sessions `/checkoutshopper/v1/sessions/{id}/payments` POST on the matching Adyen host family (`sandbox` is `checkoutshopper-test.adyen.com` with a `test_` client key; `production` is the live hosts with a `live_` key), carrying a fresh card's four encrypted fields |
528
-
529
- Use `environment: 'shared'` for Bambora, Mercado Pago, Recurly and Spreedly because the same endpoint serves test and live requests. Agentcard cannot establish the processor's test mode from that URL or a credential prefix. Configure test mode through the merchant's processor account when testing. Agentcard's own `sandbox` flag remains separate. Prepared Worldpay, Bambora, Mercado Pago and Recurly requests reject saved-card and recurring request bodies; a refused request retires the local preparation. Reconcile any existing merchant attempt before creating a new attachment.
530
-
531
- Prepare a Checkout.com checkout before the agent clicks Pay:
532
-
533
- ```ts
534
- await controller.prepare({ psp: 'checkout_com', environment: 'production' });
535
- ```
536
-
537
- Keep the phone approval page open, then click Pay once after readiness. Checkout.com Flow 1.228.0 gives its card iframe 12 seconds to return a token; approval before Pay leaves that interval for request binding and tokenization. Prepared requests must use an exact `/tokens` URL on `api.checkout.com` or `card-acquisition-gateway.checkout.com`, or the corresponding `.sandbox.checkout.com` host for `sandbox`. The request must describe a fresh card with a CVV and public-key authentication. Wallet, saved-card, reusable-token and recurring variants are refused. A native API fallback after the first CAG request cannot reuse approval. Match the actual merchant charge to the approved amount separately; a token does not enforce that amount.
538
-
539
- Prepare an Adyen Sessions checkout before the agent clicks Pay:
540
-
541
- ```ts
542
- await controller.prepare({ psp: 'adyen', environment: 'production' }); // 'sandbox' for Adyen's test host
543
- ```
544
-
545
- After approval, click Pay once. adyen-web encrypts the agent's placeholder digits and posts its Sessions `/payments` request; the SDK pauses it and binds it to the approval, the cardholder's device (still on the approval page) encrypts the approved card under the merchant's Adyen key, and the request continues from your browser with only the four encrypted fields swapped and `brand` dropped. Adyen answers your browser, so the merchant's order state is the outcome to poll. The bound request must be a fresh `scheme` card on the approved host family with a client key of that environment; a stored-card, single-blob or store-the-card request never uses the approval. Adyen Web's own 60-second request timeout then covers only the pause, the bind and the device's encryption.
546
-
547
- Prepare a Spreedly checkout before submitting the merchant's card form:
548
-
549
- ```ts
550
- await controller.prepare({ psp: 'spreedly', environment: 'shared' });
551
- ```
552
-
553
- After approval, submit the form once. The Vault replaces the card fields on your device and sends the native request through an encrypted connection to Spreedly. The merchant's environment and signed session stay unchanged. The API, Vault, relay and preparation migration must support Spreedly before you use this flow; the API refuses preparations while its relay rollout flag is disabled.
554
-
555
- Initial coverage includes the hosted iframe's new-card request. Saved-card CVV updates, Express, wallets, bank accounts and gateway purchase APIs require separate support. A returned card token does not confirm payment; wait for the merchant's result before reporting success. Autopilot is unavailable for Spreedly.
556
-
557
- Prepare Recurly before clicking the merchant's payment button:
558
-
559
- ```ts
560
- await controller.prepare({ psp: 'recurly', environment: 'shared' });
561
- ```
562
-
563
- After approval, submit the merchant's form once. Recurly's native request timer starts with that submission. The SDK preserves the captured US or EU endpoint and accepts a new-card form only. Legacy JSONP, saved tokens, bank accounts, alternative payments and proactive authentication requests require separate support. Prepared requests also refuse nonempty Worldpay or Cybersource risk results because those sessions can depend on the original card. A nonempty co-badged network preference is also refused until the selected card’s supported networks can be checked. A card token does not confirm a donation, subscription or purchase; confirm the merchant's result before reporting payment success.
564
-
565
- ### Use Checkout Pro in Mexico
566
-
567
- Attach the SDK before entering card fields on `www.mercadopago.com.mx`, then prepare the guest card checkout:
3
+ The older name of [`@agent-cards/sdk`](../checkout/README.md), Agentcard's SDK. This package re-exports every entry of `@agent-cards/sdk` (`.`, `./cdp`, `./playwright`, `./preflight`) for one release, so an import of `@agent-cards/checkout` keeps working while you rename it. The two JSON files under `./preflight/` are not re-exported: read them from `@agent-cards/sdk/preflight/catalog.json` and `@agent-cards/sdk/preflight/schemas.json`.
568
4
 
569
5
  ```ts
570
- await controller.prepare({ psp: 'mercado_pago', environment: 'shared' });
571
- ```
572
-
573
- The Vault checks the selected card's issuer using the merchant's current checkout configuration. Your browser receives the processor's original card-token response, and the SDK replaces the issuer in one native card association. The card number, security code and eight-digit prefix stay out of your SDK process. The encrypted relay carries the configuration lookup between the approval device and Mercado Pago.
574
-
575
- The selected card must have the same brand and card type as the native form. Missing or ambiguous configuration, changed checkout details, and a repeated card association stop continuation. Start a fresh checkout and approval after resolving the mismatch. Final Pay and merchant confirmation still follow your existing checkout integration.
576
-
577
- Use the matching SDK, API, Vault and relay releases together. The issuer correction covers the observed guest Checkout Pro flow in Mexico. CardForm or Bricks on merchant websites, other countries, installment changes and Autopilot need separate validation. Successful tokenization and issuer association do not establish a paid order; keep `requireMerchantResult` and a merchant receipt resolver when validating a purchase.
578
-
579
- Braintree's native `ClientConfiguration` GraphQL query can run before, during or after preparation without using the approval. Only a single `TokenizeCreditCard` mutation can consume the prepared Braintree checkout. Prepared requests require guest card tokenization with explicit `options.validate: false`; omitted validation options, saved-card fields and `validate: true` are refused. Other GraphQL operations, batches and compound mutations are blocked. Braintree's legacy REST fallback cannot consume a prepared authorization.
580
-
581
- Readiness lasts up to 30 seconds (`preparation.expiresAt`) and appears as `ready_to_submit`, with `paymentStatus: 'not_started'`. Trigger the caller-owned Pay action immediately after the promise resolves. Expiry, navigation, cancellation, an early request or a changed checkout fails closed. A preparation and its attachment are single use; reconcile any bound authorization before creating a new attachment. The SDK never clicks Pay, reuses a stale request, changes native request deadlines, or automatically retries a failed prepared checkout.
582
-
583
- After Pay, the processor's native deadline still covers fresh authorization binding, device replay and token handoff. A disconnected or backgrounded cardholder device, or a slow transport, can still miss it. A subsequent SCA challenge has its own lifetime after token handoff. If the page's own script abandons an unprepared `token` or `cse` request while the cardholder decides, the approval survives and answers the page's next request for the same purchase (see "The approval outlives the page's own request" above). If the frame that sent the request is removed, the document moves to another URL, the page closes or crashes, or a prepared request is abandoned, the attachment blocks further requests and tries to retire the pre-replay authorization; a started replay or unconfirmed cancellation remains unknown. Without `prepare()`, approval loading and human interaction still share the native deadline, so a delayed approval finishes the page's retry rather than its first request.
584
-
585
- Lost authorization polling, local approval timeouts, or interrupted browser
586
- handoffs produce `outcome_unknown` and block automatic retry. The thrown
587
- `PaymentOutcomeUnknownError` carries `authorizationId` when creation was
588
- acknowledged. `checkout.cancel()` stops the local attachment and polling; it
589
- does not revoke a pending approval link or undo a processor payment. Cancellation
590
- after an attempt starts is therefore unknown until reconciled. A cancelled
591
- attachment cannot restart.
592
-
593
- ### Unsupported endpoints
594
-
595
- `paymentEndpoints` is an explicit list supplied by the integrator after observing
596
- the site's payment requests. Each guard uses a canonical origin, exact path and
597
- mutation methods; it never examines or logs card bodies. If a guarded endpoint
598
- is not recognized, the SDK aborts it and reports `unsupported_checkout` /
599
- `unsupported` without creating an approval. Preflights and ordinary page traffic
600
- continue. There is no wildcard or intercept-all fallback, and no automatic
601
- conversion to an issued card. Unknown endpoints absent from these guards remain
602
- untouched; the SDK cannot identify every payment request from its URL.
603
-
604
- ### Runnable integration and local verification
605
-
606
- `examples/existing-browser.mjs` runs against an existing provider session, using
607
- an application-owned driver module for the agent's actions, user communication
608
- and merchant-result resolver. Set `CHECKOUT_DRIVER` to that module's absolute
609
- path and `CHECKOUT_CDP_URL` to the provider connection URL; optionally select the
610
- existing tab with `CHECKOUT_PAGE_INDEX`. The module must export
611
- `prepareCheckout(page)`, `submitCheckout(page)`, `resolveMerchantResult({page,
612
- state})`, `onUserAction(action, {page})`, and `finishAfterPayment({page, orderId})`.
613
- `prepareCheckout` returns the checkout options above. `submitCheckout` dispatches
614
- the existing agent's approved purchase and returns without waiting for approval.
615
- Install `playwright-core` in the example's host project. The SDK itself keeps no
616
- runtime dependencies. The example is integration scaffolding, not a universal
617
- merchant driver and not evidence of a live provider checkout.
618
-
619
- ```sh
620
- pnpm build
621
- pnpm test
622
- # Uses installed playwright-core, falling back to the monorepo backend dependency.
623
- # Set CHECKOUT_CHROME_PATH if Chromium is not installed in Playwright's cache.
624
- pnpm test:browser
6
+ // before
7
+ import { VaultClient } from '@agent-cards/checkout';
8
+ // after
9
+ import { Agentcard } from '@agent-cards/sdk';
625
10
  ```
626
11
 
627
- The browser fixtures never contact a payment service. The general suite uses
628
- `psp.invalid`; the Stripe continuation suite forces `api.stripe.com` through an
629
- allowlisted loopback proxy and a temporary self-signed TLS stub (requires the
630
- `openssl` CLI). All other proxy destinations are rejected. These suites use an
631
- in-process Agentcard API fixture and loopback merchant pages. The preparation
632
- fixture denies all external traffic and waits beyond each modeled request deadline before any card request: eleven seconds for Square, sixty-one seconds for Braintree, and six and a half seconds for Worldpay, Bambora and Mercado Pago. Each fixture then checks one fresh request with an unchanged abort timer. The five-second timer matches the inspected Worldpay and Bambora source; the Mercado Pago timer is a test boundary, not a measured native deadline. The fixture exercises SDK ordering with native-shaped request bodies and synthetic responses, not processor acceptance. It proves nested-frame pause/resume, agent control during approval,
633
- post-payment tasks in the same page, decline/expiry/cancel, unknown-outcome retry
634
- blocking, explicit unsupported endpoint behavior, and blocking an immediate real-browser
635
- Stripe token-to-intent fetch chain, including unrelated first intents, changed
636
- amounts/currencies and delayed CDP acknowledgement. It does not test card
637
- cryptography, real bank authorization or a cloud-provider deployment.
638
-
639
- Provider/API references checked for this integration:
640
- [Playwright CDP](https://playwright.dev/docs/api/class-browsertype#browser-type-connect-over-cdp),
641
- [Playwright routing limitations](https://playwright.dev/docs/api/class-page#page-route),
642
- [Browserbase Playwright quickstart](https://docs.browserbase.com/welcome/quickstarts/playwright),
643
- [Kernel native Agentcard integration](https://www.kernel.sh/docs/integrations/payments/agentcard).
644
-
645
-
646
- The authenticated `GET /v2/checkout/coverage` endpoint describes direct-SDK
647
- processor modes, limitations and verification levels. Use
648
- `POST /v2/checkout/coverage/assess` with up to 1,000 uniquely identified cases:
649
- `{ cases: [{ id, request_url, method: "POST", scenario: "one_time", weight: 1,
650
- requires_3ds: false }] }`. Scenarios also include `save_card`,
651
- `subscription_initial` and `subscription_renewal`. The endpoint assesses request
652
- recognition, not purchases: `recognized`, `unsupported` and `unverified` are
653
- coverage classifications, `recognized_traffic_share` is traffic-weighted, and
654
- `purchase_success_rate` stays null without observed merchant outcomes. Do not
655
- substitute the assessor for a browser/merchant validation run or use native
656
- Kernel adapter coverage as evidence for this SDK's coverage.
657
- ### Autopilot execution metadata
658
-
659
- When the cardholder has enabled an eligible spending rule in their vault, the same authorization can run through autopilot. The SDK keeps polling the existing authorization and returns optional `executionMode: 'autopilot' | 'user_approval'` and `grantId` metadata. Older API responses remain supported.
660
-
661
- `authorize()` accepts optional `executionMode` and `grantId` routing hints, sent as `execution_mode` and `grant_id`. These never establish permission to spend; the protected payment service checks the cardholder's signed rule. Autopilot suppresses `onApprovalUrl` while it is executing. A definite fallback to user approval delivers the existing authorization's URL once. A lost outcome remains `PaymentOutcomeUnknownError`; it does not create or submit a second payment.
662
-
663
- Use `executionMode: 'user_approval'` to require the existing confirmation flow. Without a selected card or grant, the backend can use exactly one eligible rule; ambiguous card selection keeps confirmation. A `grantId` restricts selection to that rule. The initial executor supports only the configured controlled Stripe test flow, and production remains disabled.
664
-
665
- For controlled hosted Stripe Checkout, both attachment functions accept `stripeCheckout: { sessionId, publishableKey, environment? }`. The default is TEST, requiring `cs_test_` and `pk_test_`. LIVE requires explicit `environment: 'production'`, a `cs_live_` Session and its `pk_live_` key. Both modes require `executionMode: 'autopilot'`, an explicit `grantId`, and a positive numeric USD `amount` in cents. The top-level document must be that exact Session on `https://checkout.stripe.com`, at `/c/pay/{sessionId}`, `/pay/{sessionId}`, `/g/pay/{sessionId}`, or `/f/pay/{sessionId}`. The SDK validates the existing URL without rewriting or navigating it. Direct `authorize()` calls use `stripeCheckoutEnvironment: 'production'` for the same explicit LIVE opt-in; the attachment functions pass it automatically.
666
-
667
- The SDK answers dummy-card tokenization locally, then sends the browser's final native confirmation through the shared core and enclave. The local response is preparation only: it creates no authorization and makes no processor request. The final result includes `checkoutSessionId` only after the selected grant, restricted terminal response, and captured amount have been checked. Continue to confirm the merchant order through the checkout controller.
668
-
669
- When Agentcard reports `autopilot_status: action_required` for the same authorization and grant, the SDK stops waiting and calls `onUserAction` with `reason: 'other'` and the authorization ID. The checkout controller records `requires_user_action` and holds further card requests. A direct `VaultClient.authorize()` call rejects with `PaymentOutcomeUnknownError` and reason `autopilot_action_required`. The existing authorization and reservation remain held; the SDK does not cancel or create another payment, return a processor payload, or infer a 3DS challenge. Your application's `resolveMerchantResult` can report the authoritative outcome of this same payment through `reconcile()`. Challenge execution and automatic continuation are not provided by this status report.
670
-
671
- The shared core carries a validated billing email from that tokenization request into the final confirmation before authorization. The email stays bound to the same Session and local PaymentMethod reference. Missing email stays missing; duplicate or conflicting values are refused. The SDK does not fill an email from the Agentcard account or invent one for the merchant.
672
-
673
- This integration requires matching backend and measured executor releases. TEST and LIVE have separate admission controls. LIVE also requires a newly authorized production grant on the selected real card, a fixed merchant account/profile with exactly one complete fixed-price USD line item and an independently reviewed operation qualification reference; the SDK option supplies none of these. LIVE deployment remains unqualified and disabled until that evidence exists. Exact observed totals do not establish atomic amount enforcement by Stripe. Subscriptions, saved-card flows and authentication continuations remain excluded. An unavailable preparation or uncertain result is declined or held for reconciliation; it cannot switch to a competing human-approval payment.
674
-
675
- The adapters also send the observed top-level HTTPS `merchantOrigin`, such as `https://shop.example`, without a path, query or trailing slash. Direct `authorize()` callers can supply that origin explicitly. It is a routing hint; the protected adapter independently verifies the processor account, payee and amount. If a browser cannot provide its top-level URL, an explicit `merchantOrigin` option can supply the hint; otherwise the regular approval path remains available.
12
+ `VaultClient` constructs the same client as `Agentcard` and stays for the same one release.