Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
336 changes: 334 additions & 2 deletions browsers/enable-payments-in-browser-agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -210,6 +210,271 @@ 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.

<Tabs>
<Tab title="KERNEL card">

#### 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`. 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.

<CodeGroup>

```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,
});
}

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}`);
}
```

```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)
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}")
```

```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
```

</CodeGroup>

run the cli's `--open` only in your controller or a human-operated terminal, and
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
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.

<CodeGroup>

```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")
```

</CodeGroup>

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, 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.

<CodeGroup>

```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.toUpperCase(),
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.upper(),
occurred_at=order.observed_at,
)
```

</CodeGroup>

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.

</Tab>
<Tab title="Link by Stripe">

#### Prepare and authorize the card
Expand Down Expand Up @@ -696,6 +961,73 @@ they remain self-contained when copied.

<CodeGroup>

```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 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`
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, with
the currency as an uppercase code such as `USD`, 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.
Expand Down
2 changes: 1 addition & 1 deletion browsers/payments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions integrations/wallets/kernel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading
Loading