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

# Agent Vault SDK 平台配置

> 配置 Agent Vault SDK 的平台边界，避免重复阅读 SDK 支付指南。

本页只说明 Agent Vault SDK 的平台适配层：服务端凭据、用户与卡片关联、浏览器边界和上线检查。Vault SDK 可以与 Browser Pay 在同一个平台共存，但每次支付尝试开始前要先选择一条路径。SDK 状态机和 PSP 行为不在这里重复。`attach()`、`pay()`、`reconcile()`、状态、错误和完整代码见 [Agent Vault SDK 指南](https://docs.clinkbill.com/cn/guides/agent/vault_sdk)及 [SDK API 参考](https://docs.clinkbill.com/cn/guides/agent/vault_sdk/api_reference.md)。

> [!NOTE]
>
> Vault SDK 不需要终端用户 OAuth，也不连接用户 MCP。平台服务端使用 Clink 应用凭据通过 OAuth `client_credentials` 调用 Vault；用户只在托管绑卡或商户验证需要时参与操作。

## 配置服务端接入

SDK 运行在可信的平台服务或结账 worker 中，由它控制 Playwright 或 CDP 浏览器。Agent 和商户页面只能看到 SDK 提供的占位卡。

| 项目 | 平台要求 |
| - | - |
| Package | 使用 Playwright `Page` 时安装 `@clink-ai/agent-vault` 和 Playwright。 |
| 凭据 | Clink 签发的 Vault `client_id` 和 `client_secret`。SDK 通过 `client_credentials` 自动取得和刷新应用 Token。 |
| 环境 | UAT 使用 `sandbox`，生产使用 `production`。凭据和卡关联分别保存。 |
| 浏览器边界 | 平台负责导航、选商品、收货信息和读取商户结果；SDK 接入结账页和支持的 PSP 请求。 |
| 覆盖范围 | `vault.getCoverage()` 只列出当前环境已开通的 PSP，不包含商户。目标商户用 Clink 提供的覆盖清单确认；具体结账页可用 `checkout.getPaymentMethods()` 检查。 |

Client Secret、`cardToken` 和服务端卡片关联只保存在平台可信服务端。不要把它们暴露给模型、Agent 工具、浏览器存储、日志或用户消息。

## 建模用户和卡片状态

使用平台稳定用户 ID 作为 `externalUserId`。不要把邮箱当作主键；用户改邮箱时不能静默地把卡关联到另一个平台用户。

| 数据 | 平台职责 |
| - | - |
| `externalUserId` | 传给 Vault 的稳定平台用户标识。每个租户和环境分别关联。 |
| `email` | 没有可用卡时创建托管绑卡 session 所需的已验证邮箱。它是可变用户数据。 |
| `customerId` | Vault 客户关联，由 `sessions.create()` 和 `onCardBound` 返回。绑卡完成后按平台用户和环境保存。 |
| `cardToken` | 服务端保存的卡令牌，传回 `attach()` 使用；不能发送给 Agent 或商户页面。 |
| `sessionId` 和 `url` | 短期托管绑卡上下文。创建响应不是可用卡凭据；`url` 应原样交给用户。 |
| `window.expiresAt` | 可用卡返回的支付窗口截止时间。过期后按新的用户操作分支处理。 |

用户改邮箱、删卡、冻结卡或合并账户时，`externalUserId`、邮箱和 `customerId` 的匹配规则需要向 Clink 确认。不能从 SDK 方法名称自行推断。

## 绑卡或复用已有卡

回访用户从服务端读取已保存的 `customerId` 和 `cardToken`。首次使用或支付窗口过期时，在 `attach()` 中传入 `email`：`pay()` 会返回带托管绑卡链接的 `wait_user` / `bind_card`，绑卡完成后由 `onCardBound` 返回卡片关联。也可以在结账前创建绑卡 session，等用户完成后再读取结果：

```typescript theme={null}
const session = await vault.sessions.create({
  email: currentUser.email,
  externalUserId: currentUser.id,
});
await deliverUserAction(currentUser.id, { type: 'bind_card', url: session.url }); // url 原样投递

// 用户通过链接完成绑卡；轮询到 session 结束
let result = await vault.sessions.retrieve(session.sessionId);
while (result.status === 'pending' || result.status === 'opened') {
  await new Promise(resolve => setTimeout(resolve, 5_000));
  result = await vault.sessions.retrieve(session.sessionId);
}
if (result.status === 'completed' && result.cardToken) {
  await saveVaultCard(currentUser.id, {
    customerId: session.customerId, // 由 sessions.create() 返回
    cardToken: result.cardToken,
  });
}
```

创建时返回的 `sessionId` 和 `url` 不是卡凭据。只有 session 完成后才保存 `customerId` 和 `cardToken`：`customerId` 来自 `sessions.create()`，`cardToken` 来自已完成的 session。判断已保存关联能否复用时，以 SDK 返回的最新 `expiresAt` 和卡状态为准。

## 接入平台浏览器

一个 checkout 对应一个由平台控制的浏览器任务和 checkout controller。传入平台用户关联，并实现投递用户操作和读取商户结果的回调：

```typescript theme={null}
const checkout = await vault.attach(page, {
  customerId: savedCard?.customerId,
  cardToken: savedCard?.cardToken,
  email: currentUser.email,
  externalUserId: currentUser.id,
  onUserAction: action => deliverUserAction(currentUser.id, action),
  onCardBound: card => saveVaultCard(currentUser.id, card),
  resolveMerchantResult: () => readMerchantOrderResult(page),
});
```

使用原生 CDP 时调用 `vault.attachToCdp()`。不要在平台用户之间共享 controller、page session、`customerId` 或 `cardToken`。结账结束后关闭或 detach 当前 controller。

`checkout.pay()` 中，首次绑卡和支付窗口过期都会返回相同的 `wait_user` / `bind_card` 操作。平台需要：

* 将托管绑卡链接原样交给用户；
* 等待用户添加卡或重新输入 CVV；
* 保存 `onCardBound` 返回的 `customerId` / `cardToken`；
* 使用 SDK 占位卡继续同一次 checkout。

3DS、二维码和跳转操作属于当前商户浏览器或用户设备。平台按 SDK 返回内容投递，并按 [Agent Vault SDK 指南](https://docs.clinkbill.com/cn/guides/agent/vault_sdk)继续和 reconcile 各种支付方式。

## 保存和恢复商户结果

平台通过 `resolveMerchantResult` 读取商户订单结果；PSP Token 化响应或 SDK 状态本身不能证明订单已完成。使用 `reconcile()`，并按 [Agent Vault SDK 指南](https://docs.clinkbill.com/cn/guides/agent/vault_sdk)区分完成、失败、等待、用户操作和未知结果。

商户结果等待中或需要用户操作时，保留同一个 checkout controller，等商户页面出现结果后再次调用 `reconcile()`。结果未知时，先核实原订单再做其他操作，不要换卡或开始第二笔支付。只有确认没有扣款后才调用 `retry()`，并且要在 `detach()` 之前调用。结账结束时调用 `detach()`；它释放拦截资源，不会撤销已经发出的请求。

商户订单号与 Vault 标识分开保存。向用户展示的结果应以商户结果为准，不能只根据 PSP 响应或 Clink 上报结果判断。

## 上线前验证

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

* 两位平台用户可以并发支付，且不共享浏览器 session、卡关联、支付窗口或结果上下文。
* 新用户可以完成托管绑卡，收到 `onCardBound`，并在后续结账复用已保存关联。
* 支付窗口过期后会返回新的 `bind_card` 操作，不会自动提交卡片。
* 目标 PSP、卡品牌、二维码和 PayPal 行为与当前环境的开通覆盖一致。
* 3DS、跳转、二维码、商户等待、明确失败和未知结果都有明确的用户交互和恢复处理。
* 等待中和需要用户操作的结果在同一个 controller 上再次 reconcile；未知结果在核实原订单前不会发起新的支付；每次结账结束时都调用 `detach()`。

## 开通时向 Clink 确认

上线前确认 Vault Client 凭据、目标商户和 PSP 覆盖、窗口过期或卡变更后的 `customerId` / `cardToken` 复用规则，以及 `externalUserId`、邮箱和客户记录的匹配规则。将这些内容写入部署和数据模型文档。

## 相关文档

* [Agent Vault SDK 指南](https://docs.clinkbill.com/cn/guides/agent/vault_sdk)：SDK 行为、支付状态、PSP 覆盖和完整示例。
* [SDK API 参考](https://docs.clinkbill.com/cn/guides/agent/vault_sdk/api_reference.md)：方法、选项、返回值和错误码。
* [Browser Pay 平台配置](https://docs.clinkbill.com/cn/guides/agent/platform_integration/browser_pay.md)：另一条 OAuth + MCP 平台路径。


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