diff --git a/browsers/enable-payments-in-browser-agent.mdx b/browsers/enable-payments-in-browser-agent.mdx index db1730a..f69450f 100644 --- a/browsers/enable-payments-in-browser-agent.mdx +++ b/browsers/enable-payments-in-browser-agent.mdx @@ -23,7 +23,7 @@ credential is created and when the user approves it: | card source | provider-minted, one-use card for a specific purchase | user's enrolled card, represented by a reusable card item | | approval timing | before the browser submits checkout | after the browser submits checkout and while the request is held | | reuse | card item and aliases are consumed after the first native handoff | card item and aliases return to `ready`; every checkout requires approval | -| mode | live only | deployment credential; the vault api doesn't expose sandbox or live mode | +| mode | live only | credential-defined; customer-owned configs expose `test_mode` | agentcard is backed by a card the user enrolls, but the agent and browser still enter aliases rather than the underlying card details. those details remain @@ -141,7 +141,11 @@ this guide starts after you have an existing browser agent. it changes how you p - set `KERNEL_API_KEY` and `KERNEL_PROJECT_ID` in the trusted controller that creates your browser. - use a low-value web checkout you control whose outgoing payment request matches a [native processor adapter](/integrations/payments/overview#checkout-and-processor-coverage). the merchant processor doesn't need to be stripe. - link card creation is live-only. agentcard mode comes from the integration's configured credential. -- for agentcard, keep an application-owned `AGENTCARD_MODE` deployment setting and fail closed unless it explicitly matches the sandbox or live environment you intend to use. the vault api does not return this mode. +- for agentcard, keep an application-owned `AGENTCARD_MODE` setting and verify it against the credential's mode. customer-owned configs return `test_mode` (`true` means sandbox); for KERNEL-managed credentials, confirm the deployment's mode. fail closed if the mode is unknown or mismatched. + +use KERNEL-managed credentials by default. optional client setup belongs in your +trusted backend, not the browser agent: see [link](/integrations/payments/stripe-link#bring-your-own-link-oauth-client) +or [agentcard](/integrations/payments/agentcard#bring-your-own-agentcard-oauth-client). keep wallet collection and payment approval outside the agent. show hosted @@ -634,6 +638,7 @@ use item state, item events, the checkout page, and the merchant's order record | link `consumed`, `credential_submitted`, or `credential_tokenized` | confirm processor and merchant state; credential use alone does not prove purchase success | | agentcard `ready` or authorization `approved` | inspect charge, replay, and merchant state; reusable item state does not prove purchase success | | decline, expiry, rejection, failure, abandonment, or `payment_unknown` | stop and reconcile the existing attempt before deciding whether a new purchase is appropriate | +| `recovery_required` | stop; reconcile the unresolved provider outcome. do not retry or delete the item or its parent wallet/vault | use the merchant order record as the authority for whether the expected order was created and paid. require its merchant, amount, currency, and items to match diff --git a/integrations/payments/agentcard.mdx b/integrations/payments/agentcard.mdx index 5e1b3cf..e59f434 100644 --- a/integrations/payments/agentcard.mdx +++ b/integrations/payments/agentcard.mdx @@ -56,13 +56,158 @@ kernel vaults create --name user-12345 -the vault api does not expose whether the configured agentcard credential is -sandbox or live. `AGENTCARD_MODE` is an application-owned assertion, not a -value read from KERNEL. set it from the deployment configuration that owns the -agentcard credential, show the mode in internal checkout controls, and fail -closed when it is missing or does not match the environment you intend to use. -set `AGENTCARD_MODE=sandbox` instead only when that deployment uses a sandbox -credential. +for KERNEL-managed credentials, the vault api does not expose whether the +agentcard credential is sandbox or live. customer-owned provider configuration +responses expose `test_mode`, as shown below. `AGENTCARD_MODE` remains an +application-owned assertion in both cases: show it in internal checkout +controls and fail closed when it is missing or does not match the credential. + +## Choose an oauth client + +use KERNEL's oauth client by default. if you bring your own client, complete the +setup below before following the shared payment lifecycle. + +### bring your own agentcard oauth client + +register a [provider configuration](/vaults#provider-configurations) once for +your organization. configurations are shared across projects, so authenticate +this step with an [organization-scoped api key](/info/api-keys). project-scoped +credentials can reference an existing configuration when creating a wallet, but +can't create, update, or delete configurations. + +keep the client credentials outside the agent's accessible files. the SDK +examples read them from backend environment variables. for the CLI, create a +protected file readable only by its owner (for example, mode `0600`): + +```json agentcard-client.json +{ + "client_id": "example-client-id", + "client_secret": "example-client-secret" +} +``` + + + +```typescript TypeScript +const orgKernel = new Kernel({ + apiKey: process.env.KERNEL_ORG_API_KEY!, +}); +const agentcardConfig = await orgKernel.vaultProviderConfigs.create({ + name: "checkout-agentcard", + provider: "agentcard", + credentials: { + client_id: process.env.AGENTCARD_CLIENT_ID!, + client_secret: process.env.AGENTCARD_CLIENT_SECRET!, + }, +}); + +if ( + agentcardConfig.provider !== "agentcard" || + agentcardConfig.test_mode !== (agentcardMode === "sandbox") +) { + throw new Error("agentcard credential mode does not match AGENTCARD_MODE"); +} +``` + +```python Python +org_kernel = Kernel(api_key=os.environ["KERNEL_ORG_API_KEY"]) +agentcard_config = org_kernel.vault_provider_configs.create( + name="checkout-agentcard", + provider="agentcard", + credentials={ + "client_id": os.environ["AGENTCARD_CLIENT_ID"], + "client_secret": os.environ["AGENTCARD_CLIENT_SECRET"], + }, +) + +if ( + agentcard_config.provider != "agentcard" + or agentcard_config.test_mode != (agentcard_mode == "sandbox") +): + raise RuntimeError("agentcard credential mode does not match AGENTCARD_MODE") +``` + +```bash CLI +# run with KERNEL_API_KEY set to an organization-scoped key +kernel vault-provider-configs create \ + --name checkout-agentcard \ + --provider agentcard \ + --credentials-file "$HOME/.config/kernel/agentcard-client.json" +``` + + + +the response exposes `test_mode` (`true` means sandbox, `false` means live). +verify it against `AGENTCARD_MODE`. then create the wallet with the +project-scoped `kernel` client and `vault` from +[Before you start](#before-you-start). CLI users should restore their +project-scoped `KERNEL_API_KEY` before running the wallet command. + + + +```typescript TypeScript +let wallet = await kernel.vaults.items.upsert("agentcard-wallet", { + id_or_name: vault.id, + type: "wallet", + spec: { + provider: "agentcard", + provider_config: { name: agentcardConfig.name }, + }, +}); + +if (wallet.action?.name === "card_enrollment") { + await presentProviderAction({ + userID: authenticatedUser.id, + vaultID: vault.id, + item: wallet, + }); +} +wallet = await kernel.vaults.items.retrieve(wallet.key, { + id_or_name: vault.id, + wait: 60, +}); +``` + +```python Python +wallet = kernel.vaults.items.upsert( + "agentcard-wallet", + id_or_name=vault.id, + type="wallet", + spec={ + "provider": "agentcard", + "provider_config": {"name": agentcard_config.name}, + }, +) + +if wallet.action is not None and wallet.action.name == "card_enrollment": + present_provider_action( + user_id=authenticated_user.id, + vault_id=vault.id, + item=wallet, + ) +wallet = kernel.vaults.items.retrieve( + wallet.key, + id_or_name=vault.id, + wait=60, +) +``` + +```bash CLI +kernel vaults wallets create user-12345 agentcard-wallet \ + --provider agentcard \ + --provider-config-name checkout-agentcard \ + --spec '{}' \ + --open +kernel vaults items get user-12345 agentcard-wallet --wait 60 +``` + + + +the SDK examples use the authenticated action-presentation contract described +in [Enroll a card](#enroll-a-card). run CLI `--open` only in a trusted, +human-operated terminal. after the user completes enrollment, repeat the +bounded wallet read until its status is `connected` or your application +deadline expires. then continue with [Create a card item](#create-a-card-item). ## Lifecycle @@ -78,6 +223,10 @@ the card item returns to `ready` after an authorization settles and can be used ## Enroll a card +this section shows wallet creation with KERNEL-managed credentials. if you +connected a wallet with your own client above, continue with +[Create a card item](#create-a-card-item). + before showing an agentcard enrollment option, list the vault's items. if an agentcard wallet already exists in any state, reuse it and do not let the user add another. show its existing action or status instead. the api makes item keys diff --git a/integrations/payments/overview.mdx b/integrations/payments/overview.mdx index 40737c9..4b7bb14 100644 --- a/integrations/payments/overview.mdx +++ b/integrations/payments/overview.mdx @@ -22,6 +22,12 @@ native KERNEL processor adapter. ## How payments work +use KERNEL-managed credentials by default. if you need to use your own oauth +client, choose the customer-managed setup near the start of the +[link](/integrations/payments/stripe-link#choose-an-oauth-client) or +[agentcard](/integrations/payments/agentcard#choose-an-oauth-client) guide before +you create a wallet. + both credential providers use the same integration shape: 1. create a vault for the user or task. @@ -83,7 +89,7 @@ the card number and cvc stay outside the agent-controlled environment. the brows | purchase authorization | explicit `authorize` operation before checkout | the user approves with Face ID | | payment handoff | one-use credential substituted at egress | agentcard executes the request and returns the response | | reuse | card item and aliases are consumed on first substitution | cards can be reused for recurring and one-time purchases | -| environment | live only | configured agentcard credential; not exposed through the vault api | +| environment | live only | credential-defined; customer-owned configs expose `test_mode` | choose [link by stripe](https://hypeship.dev/integrations/payments/stripe-link) when each purchase requires a newly approved, single-use credential. choose [agentcard](https://hypeship.dev/integrations/payments/agentcard) when one enrolled card must support multiple purchases, with separate approval for each. diff --git a/integrations/payments/stripe-link.mdx b/integrations/payments/stripe-link.mdx index aada721..e661491 100644 --- a/integrations/payments/stripe-link.mdx +++ b/integrations/payments/stripe-link.mdx @@ -40,10 +40,144 @@ kernel vaults create --name user-12345 +## Choose an oauth client + +use KERNEL's oauth client by default. if you bring your own client, complete the +setup below before following the shared payment lifecycle. + +### bring your own link oauth client + +register a [provider configuration](/vaults#provider-configurations) once for +your organization. configurations are shared across projects, so authenticate +this step with an [organization-scoped api key](/info/api-keys). project-scoped +credentials can reference an existing configuration when creating a wallet, but +can't create, update, or delete configurations. + +keep the client credentials outside the agent's accessible files. the SDK +examples read them from backend environment variables. for the CLI, create a +protected file readable only by its owner (for example, mode `0600`): + +```json link-client.json +{ + "client_id": "example-client-id", + "client_secret": "example-client-secret" +} +``` + + + +```typescript TypeScript +const orgKernel = new Kernel({ + apiKey: process.env.KERNEL_ORG_API_KEY!, +}); +const linkConfig = await orgKernel.vaultProviderConfigs.create({ + name: "checkout-link", + provider: "link", + credentials: { + client_id: process.env.LINK_CLIENT_ID!, + client_secret: process.env.LINK_CLIENT_SECRET!, + }, +}); +``` + +```python Python +org_kernel = Kernel(api_key=os.environ["KERNEL_ORG_API_KEY"]) +link_config = org_kernel.vault_provider_configs.create( + name="checkout-link", + provider="link", + credentials={ + "client_id": os.environ["LINK_CLIENT_ID"], + "client_secret": os.environ["LINK_CLIENT_SECRET"], + }, +) +``` + +```bash CLI +# run with KERNEL_API_KEY set to an organization-scoped key +kernel vault-provider-configs create --name checkout-link --provider link \ + --credentials-file "$HOME/.config/kernel/link-client.json" +``` + + + +for each end user, complete your existing link oauth flow in your backend and +obtain its access and refresh tokens. keep the client secret, pkce verifier, and +tokens out of agent context, browser code, urls, and logs. + +create the wallet with the project-scoped `kernel` client and `vault` from +[Before you start](#before-you-start). supply the access and refresh tokens from +the same grant, with a currently valid access token. CLI users should restore +their project-scoped `KERNEL_API_KEY` before running the wallet command and put +the grant in this protected file: + +```json link-grant.json +{ + "access_token": "example-access-token", + "refresh_token": "example-refresh-token" +} +``` + + + +```typescript TypeScript +const wallet = await kernel.vaults.items.upsert("link-wallet", { + id_or_name: vault.id, + type: "wallet", + spec: { + provider: "link", + authorization: { + method: "oauth", + client: { + type: "customer_managed", + provider_config: { name: linkConfig.name }, + }, + tokens: { + access_token: process.env.LINK_ACCESS_TOKEN!, + refresh_token: process.env.LINK_REFRESH_TOKEN!, + }, + }, + }, +}); +``` + +```python Python +wallet = kernel.vaults.items.upsert( + "link-wallet", + id_or_name=vault.id, + type="wallet", + spec={ + "provider": "link", + "authorization": { + "method": "oauth", + "client": { + "type": "customer_managed", + "provider_config": {"name": link_config.name}, + }, + "tokens": { + "access_token": os.environ["LINK_ACCESS_TOKEN"], + "refresh_token": os.environ["LINK_REFRESH_TOKEN"], + }, + }, + }, +) +``` + +```bash CLI +kernel vaults wallets create user-12345 link-wallet --provider link \ + --provider-config-name checkout-link --spec '{}' \ + --tokens-file "$HOME/.config/kernel/link-grant.json" +``` + + + +a successful import returns a `connected` wallet without a `link_oauth` action. +KERNEL takes over refresh-token rotation, so your backend must stop refreshing +that grant. continue with [Select a payment method](#select-a-payment-method). + ## Lifecycle -1. create a `wallet` item with the link oauth specification. -2. open the returned `link_oauth` action for the user and wait for the wallet to become `connected`. +1. connect a wallet: use KERNEL's client and present the returned `link_oauth` action, or complete oauth in your backend and import the grant with your provider configuration. +2. require the wallet to be `connected`. 3. request the advertised `payment_methods` expansion and let the user choose an eligible method. 4. create a `card` item with the purchase details. 5. retrieve the card, verify that it advertises `authorize`, and perform that operation after explicit user approval. @@ -52,6 +186,10 @@ kernel vaults create --name user-12345 ## Connect a wallet +this section shows the KERNEL-managed oauth path. if you imported a connected +wallet with your own client above, continue with +[Select a payment method](#select-a-payment-method). + before showing a link connection option, list the vault's items. if a link wallet already exists in any state, reuse it and do not let the user add another. show its existing action or status instead. the api makes item keys diff --git a/vaults.mdx b/vaults.mdx index 9ca0c7c..8e4d216 100644 --- a/vaults.mdx +++ b/vaults.mdx @@ -182,6 +182,25 @@ read the [payments overview](/integrations/payments/overview) for the shared lifecycle or use the [link by stripe](/integrations/payments/stripe-link) and [agentcard](/integrations/payments/agentcard) provider guides. +## provider configurations + +use KERNEL-managed credentials by default. if you need your own client, follow +the optional setup in [link](/integrations/payments/stripe-link#bring-your-own-link-oauth-client) +or [agentcard](/integrations/payments/agentcard#bring-your-own-agentcard-oauth-client). a named +provider configuration stores your application's `client_id` and `client_secret`, +not an end user's wallet grant. credentials are encrypted at rest; secrets are +never returned. + +configurations are organization-scoped and shared across projects, unlike +project-scoped vaults. create, update, and delete require organization-scoped +authentication; project-scoped api keys receive `403`. names are unique within +the organization, and duplicate creates return `409` without replacing secrets. + +- **selection:** choose exactly one config `id` or `name` when creating a wallet. the cli accepts `--provider-config-id` or `--provider-config-name`; responses resolve names to ids. +- **binding:** the wallet's configuration is immutable, and cards inherit it. renaming a config preserves bindings. +- **rotation:** updating `client_secret` affects all bound wallets. provider, client id, and agentcard mode cannot change; changing clients requires a new config and new wallets. +- **deletion:** returns `409` while any non-deleted item references the config, even if disconnected. it does not delete the external oauth client or revoke unrelated grants. + ## Initial item specifications the initial release accepts these `spec` fields. fields not listed here are rejected. @@ -190,8 +209,12 @@ the initial release accepts these `spec` fields. fields not listed here are reje | provider | required fields | optional fields | | --------- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------- | -| link | `provider: 'link'`, `authorization.method: 'oauth'`, `authorization.client.type: 'kernel_managed'` | none | -| agentcard | `provider: 'agentcard'` | `user_id` for a user already enrolled through a wallet in the organization | +| link | `provider: 'link'`, `authorization.method: 'oauth'`, `authorization.client` | write-only `authorization.tokens` is required only with a customer-managed client | +| agentcard | `provider: 'agentcard'` | `provider_config`; `user_id` for a user already enrolled in the organization under the same configuration | + +the default link client is `{type: 'kernel_managed'}`. for your own client, set +`authorization.client` to `{type: 'customer_managed', provider_config: {name: 'checkout-link'}}` +and supply `authorization.tokens` with `access_token` and `refresh_token`. for payment settings ui, enforce at most one wallet per provider in each vault. the api currently enforces uniqueness by item key, not by wallet provider, so a @@ -231,8 +254,14 @@ wallet status values are: card status values are: -- link: `requested`, `pending_authorization`, `ready`, `consumed`, `expired`, `declined` -- agentcard: `requested`, `ready`, `pending_approval`, `degraded` +- link: `requested`, `pending_authorization`, `ready`, `consumed`, `expired`, `declined`, `recovery_required` +- agentcard: `requested`, `ready`, `pending_approval`, `degraded`, `recovery_required` + +`recovery_required` means a card's provider outcome is unresolved. it stops +item wait loops and blocks new authorization, checkout, and deletion of the +card or its parent wallet or vault. inspect existing evidence and contact the +provider or support when manual reconciliation is needed. there is no reset +operation; deletion is not payment recovery. card state can include `masks.brand`, `masks.last4`, and read-only aliases: `number`, `cvc`, `exp_month`, and `exp_year`. aliases are non-sensitive stand-ins, not standalone credentials or permission to use the provider-backed value.