> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clinkbill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Browser Pay 平台配置

> 为多用户平台配置 Browser Pay，避免重复阅读支付实现文档。

本页只说明 Browser Pay 的平台适配层：凭据、按用户建立 OAuth 连接、租户隔离和上线检查。Browser Pay 可以与 Vault SDK 在同一个平台共存，但每次支付尝试开始前要先选择一条路径。支付状态机不在这里重复。结账流程、凭证处理、结果上报和恢复方式见 [Browser Pay 指南](https://docs.clinkbill.com/cn/guides/agent/browser_pay)。

> [!NOTE]
>
> 平台会接触两类 OAuth。服务端使用平台凭据管理用户连接；每位用户再通过 Authorization Code + PKCE 授权自己的 MCP 连接。Clink 可以为两种用途登记同一个 `client_id`，但用户授权流程绝不发送 `client_secret`。

## 准备平台接入

按环境向 Clink 获取或确认以下配置：

| 项目 | 平台要求 |
| - | - |
| 合作方凭据 | 用于服务端平台 Token 的 `client_id` 和 `client_secret`。Secret 只放在服务端。 |
| 用户 OAuth Client | 用于 Authorization Code + PKCE 的 public client。确认其 `client_id` 是合作方 `client_id`，还是单独登记的值。 |
| MCP resource | 同一环境对应的 MCP URL 和 OAuth resource。 |
| 回调地址 | 为用户连接登记固定的 HTTPS `redirect_uri`。 |
| 凭证加密 | 如果 Clink 返回加密凭证，提供 PEM X.509 SubjectPublicKeyInfo 格式的 RSA 公钥（`BEGIN PUBLIC KEY`）。它与 OAuth Client 登记是两个步骤。 |

用户 OAuth Client 的登记方式按 [MCP 连接与 OAuth](https://docs.clinkbill.com/cn/guides/agent/mcp_integration.md) 选择 CIMD、预注册或 DCR。OAuth Client 登记和 RSA 公钥登记不能混为一步。

Browser Pay 通常需要这些用户 Scope：

```text theme={null}
wallet:read payment:execute instruction:read instruction:write events:read events:consume offline_access
```

平台修改用户默认卡时，再加入 `wallet:setup`。OAuth Client 的登记范围必须覆盖授权流程请求的全部 Scope。Scope 和工具要求见 [Browser Pay 指南](https://docs.clinkbill.com/cn/guides/agent/browser_pay)及 [Browser Pay 接口参考](https://docs.clinkbill.com/cn/api-reference/mcp/browser_pay.md)。

## 为每位用户建立连接

平台 Token 和用户 Token 分开保存。平台 Token 只调用合作方连接接口；平台调用 MCP 时使用代表某一位用户的 User Token。

平台连接分为两个阶段：服务端创建或管理 Clink session，用户再完成 Authorization Code + PKCE。打开 MCP 连接前，先将回调状态绑定到对应的平台用户。

```mermaid theme={null}
%%{init: {"theme":"base","themeVariables":{"fontFamily":"inherit","actorBkg":"#F7F7F7","actorBorder":"#E7E7E7","actorTextColor":"#161616","actorLineColor":"#E7E7E7","signalColor":"#71717A","signalTextColor":"#3F3F46","noteBkgColor":"#F8E1CC","noteBorderColor":"#F8E1CC","noteTextColor":"#161616"},"sequence":{"width":116,"height":44,"actorMargin":64,"diagramMarginX":12,"diagramMarginY":12,"messageMargin":28,"noteMargin":14,"boxMargin":12,"wrap":false,"mirrorActors":false,"actorFontSize":14,"messageFontSize":14,"noteFontSize":14}}}%%
sequenceDiagram
  participant P as 平台服务端
  participant C as Clink 平台 API
  participant U as 用户浏览器
  participant O as Clink OAuth Server
  participant M as Clink MCP
  P->>C: client_credentials
  C-->>P: 平台 Access Token
  P->>C: POST /platform/sessions
  C-->>P: authorized 或 authorization_required
  alt 需要用户授权
    P-->>U: 展示 authorization_url
    U->>O: 使用 PKCE 授权
    O-->>U: 携带 code、state、iss 重定向
    U-->>P: OAuth 回调
    P->>O: 使用 code + code_verifier 交换
    O-->>P: 用户 Access Token + Refresh Token
  end
  P->>M: 使用用户 Access Token 连接
  M-->>P: 工具列表和钱包状态
```

### 获取平台 Token

使用合作方凭据通过 `client_credentials` 获取：

```http theme={null}
POST {API_BASE_URL}/agent/cwallet/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id={PLATFORM_CLIENT_ID}
&client_secret={PLATFORM_CLIENT_SECRET}
&scope=agent-platform:account
```

按 `expires_in` 缓存 `access_token`，到期后重新获取。这个 Token 用于管理平台 session，不能直接调用用户 MCP 工具。

### 创建或恢复用户 session

用户启用 Clink 时，生成随机 `state` 和 PKCE `code_verifier`，计算 S256 `code_challenge`，并将它们临时绑定到平台用户。请求使用已验证的用户邮箱和稳定的 `external_user_id`：

```http theme={null}
POST {API_BASE_URL}/agent/cwallet/platform/sessions
Authorization: Bearer {PLATFORM_ACCESS_TOKEN}
Content-Type: application/json
```

```json theme={null}
{
  "email": "user@example.com",
  "external_user_id": "platform-user-123",
  "redirect_uri": "https://agent.example.com/oauth/clink/callback",
  "state": "{RANDOM_STATE}",
  "code_challenge": "{S256_CODE_CHALLENGE}",
  "code_challenge_method": "S256"
}
```

读取响应中的 `data.status`。正式 `data` schema 和字段返回条件需要在开通时向 Clink 确认：

| status | 平台处理 |
| - | - |
| `authorized` | 保存 `customer_id`、用户 `access_token`、过期时间和返回的 `refresh_token`。没有新 Refresh Token 时保留原有有效值。 |
| `authorization_required` | 保存并展示 `authorization_url`；在回调完成前保留 `state` 和 `code_verifier`。 |

回调返回 `code`、`state` 和 `iss` 后，确认 state 属于发起连接的用户，并确认 `iss` 与同一环境 Authorization Server Metadata 中的 issuer 一致。取消或失败的连接不要持久化。

向 Authorization Server Metadata 中的 `token_endpoint` 以 public client 交换授权码，不发送 `client_secret`：

```http theme={null}
POST {TOKEN_ENDPOINT}
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&client_id={OAUTH_CLIENT_ID}
&code={AUTHORIZATION_CODE}
&redirect_uri={REGISTERED_REDIRECT_URI}
&code_verifier={SAVED_CODE_VERIFIER}
&resource={MCP_RESOURCE}
```

`redirect_uri` 必须与登记和授权时完全一致。`token_endpoint` 和 issuer 从 `{API_BASE_URL}/.well-known/oauth-authorization-server` 读取，不要硬编码，也不要混用 UAT 和 Production；`resource` 和 MCP URL 必须属于同一环境。

## 安全保存平台状态

每位平台用户、每个环境和每个 OAuth Client 使用独立记录。平台数据库保存 `external_user_id` 与连接的关联；不能用 Clink 的 `customer_id` 替代平台用户 ID。

| 数据 | 保存方式和用途 |
| - | - |
| `external_user_id` | 查找用户连接的稳定平台主键。 |
| `customer_id` | 当前环境和用户对应的 Clink 客户关联。 |
| 用户 `access_token`、`refresh_token`、过期时间 | 服务端加密保存的凭据，不得跨用户或环境复用。 |
| `state`、`code_verifier` | 短期回调状态，成功或失败后删除。 |
| 结账信息、`operation_id`、支付标识 | 同一笔购买的恢复上下文，生命周期见 [Browser Pay 指南](https://docs.clinkbill.com/cn/guides/agent/browser_pay)。 |

每位用户串行刷新 Token，多实例部署时也要保证。每次刷新都会返回新的 `refresh_token`，旧值随即失效：先保存新值再使用新的 Access Token，不要再次提交已失效的旧值。旧 Refresh Token 被再次提交时，Clink 会撤销这次授权下的全部 Refresh Token，用户必须重新授权。刷新失败时，停止该用户的新 MCP 操作并重新授权。详见[刷新和恢复](https://docs.clinkbill.com/cn/guides/agent/mcp_integration.md#刷新和恢复)。

用户断开连接时，停止新操作，使用配置的 OAuth Client 调用 `/agent/cwallet/oauth/revoke` 撤销 Refresh Token，并清理本地凭据。钱包为空仍然表示连接成功；可读钱包或默认卡不代表已经授权购买。

使用用户 Token 连接 MCP，读取工具列表和 `clink://skill/rules`。Host 无法读取资源时，调用 `get_skill_manifest` 并传入 `include_rules: true`。启用支付动作前，平台应先用 `get_account_state` 做只读连接检查。

Node.js smoke test 使用 Node.js 20.19 或更新版本和 `@modelcontextprotocol/client@2`：用用户 Access Token 连接当前环境的 MCP URL，读取工具列表，使用 `{}` 调用 `get_account_state`，再检查 `structuredContent`。`isError: true` 按连接失败处理。`connected: true` 且钱包可读就证明连接成功；`cardCount: 0` 表示钱包为空，不是 OAuth 失败。检查完成后使用平台正常的按用户 Token 刷新流程。

> [accordion] 最小 Node.js 钱包连接检查
>
> ```bash theme={null}
> npm install @modelcontextprotocol/client@2
> ```
>
> ```javascript theme={null}
> import { Client, StreamableHTTPClientTransport } from "@modelcontextprotocol/client";
>
> const token = process.env.CLINK_USER_ACCESS_TOKEN;
> if (!token) throw new Error("请设置 CLINK_USER_ACCESS_TOKEN。");
>
> const client = new Client({ name: "clink-wallet-check", version: "1.0.0" });
> const transport = new StreamableHTTPClientTransport(
>   new URL(process.env.CLINK_MCP_URL ?? "https://uat-api.clinkbill.com/mcp"),
>   { authProvider: { token: async () => token } },
> );
>
> try {
>   await client.connect(transport);
>   const { tools } = await client.listTools();
>   if (!tools.some(tool => tool.name === "get_account_state")) {
>     throw new Error("get_account_state 不可用。");
>   }
>   const result = await client.callTool({ name: "get_account_state", arguments: {} });
>   if (result.isError) throw new Error("钱包读取失败。");
>   const account = result.structuredContent;
>   if (!account || !Array.isArray(account.paymentMethods)) {
>     throw new Error("钱包响应结构不符合预期。");
>   }
>   console.log({ connected: true, walletReadable: true, cardCount: account.paymentMethods.length });
> } finally {
>   try {
>     await transport.terminateSession();
>   } finally {
>     await client.close();
>   }
> }
> ```

OAuth 只授予 Clink 工具访问权限，不代表已经授权购买。购买指令或一次性用户操作才定义 Agent 可以消费的范围。Browser Pay 返回 Passkey 等用户操作时，必须交给对应用户，并通过同一个 Operation 恢复。

## 将结账交给 Browser Pay

平台仍负责商户浏览器和用户体验。完整 [Browser Pay 指南](https://docs.clinkbill.com/cn/guides/agent/browser_pay)定义支付流程；平台适配层需要提供以下输入并完成这些交接：

* 从商户最终结账页读取金额、币种、商户信息和结账字段。金额使用最小货币单位，恢复 Operation 时保留相同信息。
* 选择具体 `paymentInstrumentId` 前刷新钱包，让用户按品牌和尾号确认卡片，并且只使用 `agent_pay_support: true` 的卡。
* 将 `next_action.params.redirect_url` 以及二维码、用户设备操作原样交给用户。`USER_DEVICE_ONLY` 链接不能在 Agent 浏览器中打开。
* 在填写商户表单前保存签发 Operation 和支付标识。加密凭证和解密后的卡字段不能进入模型输入、日志或普通记录。
* 根据商户页面观察到的结果调用 `confirm_result`。Clink 的上报结果或 PSP 响应不能代替商户确认。

金额核对、凭证解密、`next_action`、结果枚举和避免重复支付的恢复规则见 [Browser Pay 指南](https://docs.clinkbill.com/cn/guides/agent/browser_pay)。卡片和购买授权管理见 [Wallet Service](https://docs.clinkbill.com/cn/guides/agent/wallet_service.md)。

## 上线前验证

先在 Sandbox UAT 验证，再在 Production 重复相关检查：

* 两位平台用户可以共用一个 OAuth Client，但只能看到各自的钱包、Operation 和支付上下文。
* 已授权用户可以取得凭证；需要用户操作时，平台可以展示链接并通过同一个 Operation 恢复。
* 已登记的 RSA 公钥能在平台可信组件中解密凭证，私钥和卡字段不会进入 Agent 或日志。
* Access Token 过期、Refresh Token 轮换、回调重放、用户断开和授权失败都有明确处理。
* 成功、失败、未提交和未知结果都使用正确的 Clink 标识上报，不会自动发起重复支付。

## 开通时向 Clink 确认

上线前确认 `/platform/sessions` 的 `data` schema、合作方与用户 OAuth public `client_id` 的关系、OAuth Scope 登记、各环境 resource 值和公钥登记流程。将这些值写入部署配置，不要从示例自行推断。

## 相关文档

* [Browser Pay 指南](https://docs.clinkbill.com/cn/guides/agent/browser_pay)：支付机制、凭证保护、结果上报和恢复。
* [MCP 连接与 OAuth](https://docs.clinkbill.com/cn/guides/agent/mcp_integration.md)：协议连接细节。
* [Browser Pay 接口参考](https://docs.clinkbill.com/cn/api-reference/mcp/browser_pay.md)：工具字段和响应结构。
* [Operation、规则与恢复](https://docs.clinkbill.com/cn/api-reference/mcp/operations.md)：Operation 状态和恢复规则。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.