From 9eb66185dd6568d0f43fb395c62616cb79a90793 Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Thu, 29 Oct 2026 21:19:32 +0000 Subject: [PATCH 1/2] Add KERNEL card path to the browser-agent payments walkthrough --- browsers/enable-payments-in-browser-agent.mdx | 326 +++++++++++++++++- browsers/payments.mdx | 2 +- integrations/wallets/kernel.mdx | 1 + integrations/wallets/overview.mdx | 2 +- 4 files changed, 327 insertions(+), 4 deletions(-) diff --git a/browsers/enable-payments-in-browser-agent.mdx b/browsers/enable-payments-in-browser-agent.mdx index db618228..076eacdd 100644 --- a/browsers/enable-payments-in-browser-agent.mdx +++ b/browsers/enable-payments-in-browser-agent.mdx @@ -12,7 +12,7 @@ verify and approve each purchase before completing checkout. - have an existing browser agent. this guide adds payment handling, not navigation or reasoning. - choose a wallet integration from the [wallet overview](/integrations/wallets/overview), then follow its tab below. - install a KERNEL sdk with the `vaults` resource. set `KERNEL_API_KEY` and `KERNEL_PROJECT_ID` in your controller. -- use a low-value checkout you control. link is live-only and requires an https merchant origin. on stripe checkouts that support link pay tokens, KERNEL fills the payment without field selectors; elsewhere you need verified field selectors for the virtual card. agentcard requires a [native processor adapter](/integrations/wallets/overview#checkout-and-processor-coverage); the processor doesn't need to be stripe. +- use a low-value checkout you control. KERNEL card is live-only, and `fill` writes only on the origin of the purchase's `merchant_url`, so you need the top-level checkout page's url and verified field selectors for the card inputs. link is live-only and requires an https merchant origin. on stripe checkouts that support link pay tokens, KERNEL fills the payment without field selectors; elsewhere you need verified field selectors for the virtual card. agentcard requires a [native processor adapter](/integrations/wallets/overview#checkout-and-processor-coverage); the processor doesn't need to be stripe. - for agentcard, set `AGENTCARD_MODE` in your controller and verify it against the credential's mode. customer-owned configs expose `test_mode` (`true` means sandbox); for KERNEL-managed credentials, confirm the deployment's mode. stop if the mode is unknown or mismatched. - identify how you'll read the merchant's trusted order record. you need it to confirm a matching paid order. deterministic checkout data can verify purchase details before submission, but without the order record afterward, the result remains indeterminate. @@ -97,7 +97,7 @@ deleting a wallet invalidates its dependent card items; use an explicit remove-and-replace flow instead of adding a second wallet. 1. let the user select a provider that doesn't already have a wallet in the vault. -2. create the wallet item through your controller, following the [link setup](/integrations/wallets/stripe-link) or [agentcard setup](/integrations/wallets/agentcard). +2. create the wallet item through your controller, following the [KERNEL card setup](/integrations/wallets/kernel#connect-a-card), [link setup](/integrations/wallets/stripe-link), or [agentcard setup](/integrations/wallets/agentcard). 3. present the returned action using the authenticated flow below. the user connects or enrolls their real payment method with the provider. 4. wait for the wallet's status to become `connected` before preparing a purchase. @@ -210,6 +210,262 @@ follow the tab for your provider. each path uses the frozen purchase object from step 2, a connected wallet in the same vault, and this browser session. + + +#### Create and authorize the purchase + +a KERNEL card purchase is approved before checkout, so `authorize` takes no +browser or page parameters. authorize once the agent reaches the final checkout +page: the one-time security code expires shortly after it's issued. + +1. [create a KERNEL card item](/integrations/wallets/kernel#make-a-purchase) named `notebook-order` from the verified purchase. set `merchant_url` to the top-level checkout page's url. `fill` writes only on that url's exact origin, so a checkout hosted on another domain needs that domain's url. visa cards also require `merchant_country`. KERNEL card items can't be updated; to change the purchase, delete the item and create a new one. +2. require `authorize` in `available_operations` and invoke it, then present any returned `spend_approval` action through your controller. visa cards use it for the cardholder's approval, and [cards without network tokens](/integrations/wallets/kernel#make-a-purchase) use it to collect a fresh security code. supported mastercard cards return no action. +3. wait for the card to become `ready`. if it's still `pending_authorization`, retrieve it again; an unused approval link expires after 30 minutes. + + + +```typescript TypeScript +const created = await kernel.vaults.items.upsert("notebook-order", { + id_or_name: vault.id, + type: "card", + spec: { + provider: "kernel", + wallet: "card-wallet", + amount: 2306, + currency: "usd", + merchant_name: "example shop", + merchant_url: "https://shop.example.com/checkout", + merchant_country: "US", + }, +}); +if (!created.available_operations.some(({ type }) => type === "authorize")) { + throw new Error("authorize is unavailable for this card"); +} + +const authorized = await kernel.vaults.items.performOperation(created.key, { + id_or_name: vault.id, + type: "authorize", +}); +if (authorized.type !== "card") throw new Error("unexpected operation response"); +if (authorized.action && "url" in authorized.action) { + await presentProviderAction({ + userID: authenticatedUser.id, + vaultID: vault.id, + item: authorized, + }); +} + +const card = await kernel.vaults.items.retrieve(created.key, { + id_or_name: vault.id, + wait: 60, +}); +if (card.type !== "card" || card.state.status !== "ready") { + throw new Error(`payment item is ${card.state.status}`); +} +``` + +```python Python +created = kernel.vaults.items.upsert( + "notebook-order", + id_or_name=vault.id, + type="card", + spec={ + "provider": "kernel", + "wallet": "card-wallet", + "amount": 2306, + "currency": "usd", + "merchant_name": "example shop", + "merchant_url": "https://shop.example.com/checkout", + "merchant_country": "US", + }, +) +if not any(operation.type == "authorize" for operation in created.available_operations): + raise RuntimeError("authorize is unavailable for this card") + +authorized = kernel.vaults.items.perform_operation( + created.key, + id_or_name=vault.id, + type="authorize", +) +if authorized.type != "card": + raise RuntimeError("unexpected operation response") +if authorized.action is not None and hasattr(authorized.action, "url"): + present_provider_action( + user_id=authenticated_user.id, + vault_id=vault.id, + item=authorized, + ) + +card = kernel.vaults.items.retrieve(created.key, id_or_name=vault.id, wait=60) +if card.type != "card" or card.state.status != "ready": + raise RuntimeError(f"payment item is {card.state.status}") +``` + +```bash CLI +kernel vaults cards create user-12345 notebook-order \ + --provider kernel \ + --spec '{ + "wallet": "card-wallet", + "amount": 2306, + "currency": "usd", + "merchant_name": "example shop", + "merchant_url": "https://shop.example.com/checkout", + "merchant_country": "US" + }' +kernel vaults items invoke user-12345 notebook-order authorize --open +kernel vaults items get user-12345 notebook-order --wait 60 -o json +``` + + + +run the cli's `--open` only in your controller or a human-operated terminal, and +keep its output outside the agent. + +`declined` means the card network refused to issue a code. stop; KERNEL never +approves the purchase again on its own, so the cardholder must approve a new +purchase. for `recovery_required`, stop and follow +[verify the outcome](#verify-the-outcome). + +#### Fill the card + +for eligible cards, `fill` writes a network token number, its expiry, and a +one-time security code. `checkoutURL` / `checkout_url` is the browser's exact +current top-level https url, including query and fragment, on the origin of +`merchant_url`. a page on another origin returns `403` with `destination_denied` +before any writes. payment inputs inside processor iframes are supported. + +KERNEL cards have no stored billing details. requesting a `billing_*` field +returns `field_unavailable` before any writes, so enter billing details from +the user's approved data instead. + + + +```typescript TypeScript +const kernelCard = await kernel.vaults.items.retrieve(card.key, { + id_or_name: vault.id, +}); +if ( + kernelCard.type !== "card" || + kernelCard.spec.provider !== "kernel" || + !kernelCard.available_operations.some((operation) => operation.type === "fill") +) { + throw new Error("fill is unavailable for this card"); +} +const result = await kernel.vaults.items.performOperation(kernelCard.key, { + id_or_name: vault.id, + type: "fill", + browser_id: browser.session_id, + page_url: checkoutURL, + fields: [ + { field: "number", selector: "#card-number" }, + { field: "expiration", selector: "#expiry", format: "MM/YY" }, + { field: "cvc", selector: "#security-code" }, + ], +}); +if (result.type !== "fill") throw new Error("unexpected operation response"); +console.log(result.status, result.fields); +if (result.status !== "completed") { + throw new Error("fill did not complete; inspect per-field outcomes and retry"); +} +``` + +```python Python +kernel_card = kernel.vaults.items.retrieve(card.key, id_or_name=vault.id) +if ( + kernel_card.type != "card" + or kernel_card.spec.provider != "kernel" + or not any(operation.type == "fill" for operation in kernel_card.available_operations) +): + raise RuntimeError("fill is unavailable for this card") +result = kernel.vaults.items.perform_operation( + kernel_card.key, + id_or_name=vault.id, + type="fill", + browser_id=browser.session_id, + page_url=checkout_url, + fields=[ + {"field": "number", "selector": "#card-number"}, + {"field": "expiration", "selector": "#expiry", "format": "MM/YY"}, + {"field": "cvc", "selector": "#security-code"}, + ], +) +if result.type != "fill": + raise RuntimeError("unexpected operation response") +print(result.status, result.fields) +if result.status != "completed": + raise RuntimeError("fill did not complete; inspect per-field outcomes and retry") +``` + + + +for separate month and year inputs, bind `exp_month` (`MM`) and `exp_year` +(`YYYY`) instead of `expiration`. request only fields that are currently +visible. if a completed fill reveals another field, make a separate fill with +only the new field. + +handle the per-field outcomes as in the [`fill` outcome contract](/vaults/fill#handle-the-outcome): +`fill` never submits, so a retry is safe. retry only after checking the page, +and never in an automatic loop. + +only continue on `completed`. complete the remaining customer fields and +disclosures, then submit once, promptly: the one-time security code expires. +[verify the outcome](#verify-the-outcome) against the merchant's order record. + +#### Report the merchant outcome + +after the merchant's order record shows a definitive outcome, report it once +with `confirm_transaction` when the card advertises it. report the amount the +merchant charged, in minor units, and the time you observed the outcome. don't +report an outcome you couldn't verify, and don't repeat a report whose response +was lost. `waitForMerchantResolution` represents your order backend. + + + +```typescript TypeScript +const order = await waitForMerchantResolution(verifiedPurchase.orderID); +const settled = await kernel.vaults.items.retrieve(card.key, { + id_or_name: vault.id, +}); +if ( + order.paid && + settled.available_operations.some(({ type }) => type === "confirm_transaction") +) { + await kernel.vaults.items.performOperation(settled.key, { + id_or_name: vault.id, + type: "confirm_transaction", + status: "APPROVED", + transaction_type: "PURCHASE", + amount: order.amount, + currency: order.currency, + occurred_at: order.observedAt, + }); +} +``` + +```python Python +order = wait_for_merchant_resolution(verified_purchase.order_id) +settled = kernel.vaults.items.retrieve(card.key, id_or_name=vault.id) +if order.paid and any( + operation.type == "confirm_transaction" for operation in settled.available_operations +): + kernel.vaults.items.perform_operation( + settled.key, + id_or_name=vault.id, + type="confirm_transaction", + status="APPROVED", + transaction_type="PURCHASE", + amount=order.amount, + currency=order.currency, + occurred_at=order.observed_at, + ) +``` + + + +a report records the outcome you observed. it doesn't prove that payment +succeeded and isn't a substitute for the merchant's order record. + + #### Prepare and authorize the card @@ -696,6 +952,72 @@ they remain self-contained when copied. +```text KERNEL card +use the KERNEL cli to complete the following checkout with a KERNEL card. use +cli commands rather than sdk or direct api calls. + +a vault groups items. wallet and card are the payment item types. a card item +references a wallet item in the same vault, so create the wallet before the +card. + +1. create or retrieve a vault named `user-12345`. +2. list the vault's items and locate its only KERNEL wallet. require its + status to be `connected` and reuse it. if none exists, more than one exists, + or an action is present, stop and ask me to resolve wallet setup outside this + agent. do not create another wallet or print, return, or open an action url. +3. create a KERNEL browser with `user-12345` attached as a vault, then navigate + it to https://buy.stripe.com/28E5kw7DtgKXdLqgiY53O00. +4. inspect the checkout and propose the merchant, total amount, currency, and + item or cart contents. treat this proposal as untrusted and do not create a + card item from it. +5. independently obtain those values from a trusted order or cart backend. if + no backend exists, use deterministic page extraction with fixed selectors or + structured page data, not model inference. normalize and compare every value. + if adaptive pricing is active, use the checkout's active presentment amount + and currency rather than its base integration values. + if any value is missing, cannot be verified, or disagrees, stop without + creating or authorizing a card. +6. show me the verified merchant, amount, currency, and item or cart contents. + wait for my explicit confirmation, then freeze that verified purchase object. +7. with the browser on the final checkout page, create a KERNEL card item named + `checkout-card` from that same verified, confirmed object. use the exact + current top-level checkout url as `merchant_url` and include + `merchant_country`. KERNEL cards can't be updated; if a value is wrong, + stop and ask me. +8. retrieve the card item and confirm that `authorize` appears in + `available_operations`. give me the exact cli command to invoke + authorization, but do not run it. ask me to invoke it and complete any + approval from a trusted terminal or application outside this agent. after i + confirm completion, retrieve the item again and require its status to be + `ready`. do not print, return, or open an action url. +9. require `fill` in the card's `available_operations` and follow its + description. invoke `kernel vaults items invoke user-12345 checkout-card fill` + with `--params` or `--spec-file`: browser_id is the attached session id and + page_url is the exact current top-level https url (including query and + fragment), on the origin of `merchant_url`. inspect the checkout's inputs and + add fields mapping `number`, `cvc`, and either `expiration` (format MM/YY or + MM/YYYY) or `exp_month` and `exp_year` to unique css selectors. KERNEL cards + have no billing fields. fill writes actual values into the browser; do not + read them back, expose them in model context, or assume recordings omit them. +10. preserve the value-free fill result and every per-field outcome. continue + only on `completed`. after failed or unknown outcomes or transport errors: + fill never submits, so a retry is safe. retry only after checking the page, + and never in an automatic loop. supply other customer fields only from + approved user data, complete any agent disclosure truthfully, then submit + checkout once, promptly, before the one-time security code expires. +11. inspect the checkout result, fill outcomes, and item events. report success + only when a trusted merchant order record confirms a paid order matching the + frozen merchant, amount, currency, and items. fill or a success page alone + is not proof. if that record is unavailable or the sources disagree, report + an indeterminate result. never retry a failed, timed-out, or indeterminate + payment; retain the vault id, card key, browser id, and last event id for + reconciliation. if the item reports recovery_required, do not delete it or + its parent wallet or vault. +12. if the order record shows a definitive outcome and the card advertises + `confirm_transaction`, give me the exact cli command to report it once, but + do not run it. +``` + ```text link by stripe use the KERNEL cli to complete the following checkout with link by stripe. use cli commands rather than sdk or direct api calls. diff --git a/browsers/payments.mdx b/browsers/payments.mdx index d04c7044..ef702c51 100644 --- a/browsers/payments.mdx +++ b/browsers/payments.mdx @@ -23,7 +23,7 @@ agentcard uses an integration-specific alternative: the agent enters non-sensiti 1. create a vault for the user or task. 2. create a wallet item and send the user through the provider-hosted collection flow. 3. attach the vault when you create the browser session. the attachment also covers items created later in that vault. -4. create a card item for the verified, user-confirmed purchase. complete the provider's required preparation and approval. link authorizes at the checkout page and issues a merchant-bound link pay token on supported stripe checkouts or a single-use virtual card elsewhere; agentcard can reuse a card item with separate approval for each checkout. +4. create a card item for the verified, user-confirmed purchase. complete the provider's required preparation and approval. KERNEL card authorizes before checkout and, for eligible cards, fills a network token with a one-time security code; link authorizes at the checkout page and issues a merchant-bound link pay token on supported stripe checkouts or a single-use virtual card elsewhere; agentcard can reuse a card item with separate approval for each checkout. 5. invoke KERNEL's advertised `fill` operation to inject payment fields without supplying their values, then inspect the outcomes before submitting. for agentcard's alias-based flow, enter aliases and keep a trusted approval observer running while the checkout request is held. 6. submit checkout once, complete any remaining provider-hosted approval, and reconcile with the merchant's order state. fill or approval alone is not payment success. diff --git a/integrations/wallets/kernel.mdx b/integrations/wallets/kernel.mdx index 848e32db..e5fa7b41 100644 --- a/integrations/wallets/kernel.mdx +++ b/integrations/wallets/kernel.mdx @@ -159,5 +159,6 @@ there's no general standing approval or spending-limit setting across these path ## Reference +- [enable payments in a browser agent](/browsers/enable-payments-in-browser-agent): the controller, agent, fill, and checkout walkthrough. - [vault items api](https://kernel.sh/docs/api-reference/vaults/create-or-retrieve-an-identical-vault-item-by-immutable-key): request fields, eligibility, operations, and expiry. - [pricing](/info/pricing): vault limits and payment charges. diff --git a/integrations/wallets/overview.mdx b/integrations/wallets/overview.mdx index 25873a85..ddf71d42 100644 --- a/integrations/wallets/overview.mdx +++ b/integrations/wallets/overview.mdx @@ -82,7 +82,7 @@ operation; deletion is not payment recovery. ## Next step -start with the provider guide in [choose a provider](#choose-a-provider). for a complete agent workflow using link or agentcard, follow [enable payments in a browser agent](/browsers/enable-payments-in-browser-agent). +start with the provider guide in [choose a provider](#choose-a-provider). for a complete agent workflow using KERNEL card, link, or agentcard, follow [enable payments in a browser agent](/browsers/enable-payments-in-browser-agent). the provider guides include CLI commands for creating wallets and cards. once the card item is ready, the shared CLI flow is: From fc1c4afb496356db15b4d6f7816e5dcebc3b295a Mon Sep 17 00:00:00 2001 From: hiroTamada <88675973+hiroTamada@users.noreply.github.com> Date: Sat, 31 Oct 2026 00:16:18 +0000 Subject: [PATCH 2/2] Poll KERNEL card authorization and uppercase confirm_transaction currency --- browsers/enable-payments-in-browser-agent.mdx | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/browsers/enable-payments-in-browser-agent.mdx b/browsers/enable-payments-in-browser-agent.mdx index 076eacdd..84899891 100644 --- a/browsers/enable-payments-in-browser-agent.mdx +++ b/browsers/enable-payments-in-browser-agent.mdx @@ -220,7 +220,7 @@ page: the one-time security code expires shortly after it's issued. 1. [create a KERNEL card item](/integrations/wallets/kernel#make-a-purchase) named `notebook-order` from the verified purchase. set `merchant_url` to the top-level checkout page's url. `fill` writes only on that url's exact origin, so a checkout hosted on another domain needs that domain's url. visa cards also require `merchant_country`. KERNEL card items can't be updated; to change the purchase, delete the item and create a new one. 2. require `authorize` in `available_operations` and invoke it, then present any returned `spend_approval` action through your controller. visa cards use it for the cardholder's approval, and [cards without network tokens](/integrations/wallets/kernel#make-a-purchase) use it to collect a fresh security code. supported mastercard cards return no action. -3. wait for the card to become `ready`. if it's still `pending_authorization`, retrieve it again; an unused approval link expires after 30 minutes. +3. wait for the card to become `ready`. a `wait` read holds for at most 60 seconds, so keep retrieving while it's `pending_authorization`; the cardholder has up to 30 minutes to use the approval link. @@ -255,10 +255,16 @@ if (authorized.action && "url" in authorized.action) { }); } -const card = await kernel.vaults.items.retrieve(created.key, { +let card = await kernel.vaults.items.retrieve(created.key, { id_or_name: vault.id, wait: 60, }); +while (card.type === "card" && card.state.status === "pending_authorization") { + card = await kernel.vaults.items.retrieve(created.key, { + id_or_name: vault.id, + wait: 60, + }); +} if (card.type !== "card" || card.state.status !== "ready") { throw new Error(`payment item is ${card.state.status}`); } @@ -297,6 +303,8 @@ if authorized.action is not None and hasattr(authorized.action, "url"): ) card = kernel.vaults.items.retrieve(created.key, id_or_name=vault.id, wait=60) +while card.type == "card" and card.state.status == "pending_authorization": + card = kernel.vaults.items.retrieve(created.key, id_or_name=vault.id, wait=60) if card.type != "card" or card.state.status != "ready": raise RuntimeError(f"payment item is {card.state.status}") ``` @@ -319,7 +327,8 @@ kernel vaults items get user-12345 notebook-order --wait 60 -o json run the cli's `--open` only in your controller or a human-operated terminal, and -keep its output outside the agent. +keep its output outside the agent. repeat the `get` while `state.status` is +`pending_authorization`. `declined` means the card network refused to issue a code. stop; KERNEL never approves the purchase again on its own, so the cardholder must approve a new @@ -415,9 +424,9 @@ disclosures, then submit once, promptly: the one-time security code expires. after the merchant's order record shows a definitive outcome, report it once with `confirm_transaction` when the card advertises it. report the amount the -merchant charged, in minor units, and the time you observed the outcome. don't -report an outcome you couldn't verify, and don't repeat a report whose response -was lost. `waitForMerchantResolution` represents your order backend. +merchant charged, in minor units, its currency as an uppercase code such as +`USD`, and the time you observed the outcome. don't report an outcome you +couldn't verify, and don't repeat a report whose response was lost. `waitForMerchantResolution` represents your order backend. @@ -436,7 +445,7 @@ if ( status: "APPROVED", transaction_type: "PURCHASE", amount: order.amount, - currency: order.currency, + currency: order.currency.toUpperCase(), occurred_at: order.observedAt, }); } @@ -455,7 +464,7 @@ if order.paid and any( status="APPROVED", transaction_type="PURCHASE", amount=order.amount, - currency=order.currency, + currency=order.currency.upper(), occurred_at=order.observed_at, ) ``` @@ -988,7 +997,8 @@ card. `available_operations`. give me the exact cli command to invoke authorization, but do not run it. ask me to invoke it and complete any approval from a trusted terminal or application outside this agent. after i - confirm completion, retrieve the item again and require its status to be + confirm completion, retrieve the item again with `--wait 60`, repeating + while its status is `pending_authorization`, and require its status to be `ready`. do not print, return, or open an action url. 9. require `fill` in the card's `available_operations` and follow its description. invoke `kernel vaults items invoke user-12345 checkout-card fill` @@ -1014,8 +1024,8 @@ card. reconciliation. if the item reports recovery_required, do not delete it or its parent wallet or vault. 12. if the order record shows a definitive outcome and the card advertises - `confirm_transaction`, give me the exact cli command to report it once, but - do not run it. + `confirm_transaction`, give me the exact cli command to report it once, with + the currency as an uppercase code such as `USD`, but do not run it. ``` ```text link by stripe