跳转到主要内容
Agent Vault SDK 让 Agent 在商户结账页完成支付,全程接触不到用户的真实卡。Agent 只填写公开的占位卡;SDK 拦截页面发往商户支付服务商(PSP)的请求,由 Clink Vault 在 PCI 边界内换成真实卡后再调用 PSP。 把真实卡号交给 LLM 或脚本会带来严重的合规与泄露风险。使用 SDK 后,页面操作权留在 Agent,卡数据与授权校验留在 Clink。全部方法和参数见 SDK API 参考。

工作原理

  1. 拦截:SDK 挂载到 Agent 驱动的浏览器(Playwright 或原生 CDP),拦截页面及所有跨域 iframe 的请求。
  2. 占位卡:SDK 返回一张与用户真实卡同品牌的公开测试卡(如 Visa 4111 1111 1111 1111,有效期取真实卡),Agent 把它填进结账表单。
  3. 识别:页面向 PSP 发令牌化请求时,SDK 按审核过的 PSP 规则(host + path + 字段)匹配,且卡字段必须等于本次下发的占位卡,才会暂停该请求。
  4. 中继:SDK 只把请求模板、占位卡标记、card_token 和白名单请求头发给 Clink Vault。
  5. 换卡:Clink Vault 校验 30 分钟支付窗口和 PSP 规则后,在 PCI 边界内把占位卡替换为真实卡,直接调用 PSP。
  6. 回放:SDK 把 PSP 的原始状态码、响应头和响应体回放给页面,页面像正常支付一样继续下单。

Agent / 浏览器能看到

  • 卡品牌和后四位
  • 同品牌占位测试卡
  • 绑卡链接、支付状态
  • PSP 返回给页面的原生响应

只在 Clink PCI 边界内

  • 真实卡号 PAN 与 CVV(KMS 加密存储)
  • 支付窗口与 PSP 规则校验
  • 携带真实卡号发往 PSP 的请求
  • Adyen 等客户端加密场景下的卡密文
支付走的是商户自己的 PSP,Clink 只能看到 PSP 的 HTTP 响应。令牌化接口返回 2xx 只代表“卡已令牌化”,不等于“订单已扣款成功”。最终结果必须以商户页面的订单结果为准,并通过 reconcile() 上报。

快速接入

SDK 运行在你的应用服务端或可信的结账 Worker 中,不要运行在用户浏览器里。
1

安装 SDK

2

挂载到结账页

创建客户端,然后挂载到 Agent 驱动的页面。传入 email,用户没有可用的卡时 SDK 才能发起绑卡。
vault.attach(page) 适用于 Playwright(本地,或通过 connectOverCDP 连云浏览器),天然覆盖跨域 iframe。不用 Playwright 时(如云浏览器 WebSocket),改用 vault.attachToCdp(cdp, sessionId, options)。两者返回同一个 CheckoutController。
3

循环调用 pay() 直到终态

每次调用 pay() 都会在 next_action 里返回 Agent 下一步要做的事。执行完再调 pay()。fillCard、clickPayButton 是你自己实现的页面操作函数,见核心代码示例。
4

上报结果并卸载

waitForOrderResult 是你自己实现的页面函数:轮询 readOrderResult,直到页面出现结果或超时,见核心代码示例中的 card-form.ts。循环停在 wait_user 时,它等的是用户扫码、完成 3DS 或跳转;返回 succeeded 时,等的是订单页加载出来。reconcile() 通过 resolveMerchantResult 读取商户订单结果并上报 Clink Vault。结账结束时必须调用 detach()。如果还没有卡请求发到 PSP,或没有设置 resolveMerchantResult,reconcile() 直接返回当前状态。见处理结果。
付款前可以先调 checkout.getPaymentMethods() 看页面能用哪些方式:卡的状态是 ready、needs_bind 或 unsupported;页面上的扫码钱包和 PayPal 是 needs_user,问过用户后再用 pay({ method }) 发起,见 PSP 覆盖范围。

需要用户参与时

Vault 不需要 Passkey、短信验证码或逐笔确认链接。用户只需要打开一次绑卡链接:添加新卡,或选择已有的卡重新输入 CVV。完成后开启 30 分钟支付窗口,窗口内 Agent 无需用户参与即可完成 fill_card → submit。绑卡页由 Clink 托管,免登录、一次性。 next_action.type 为 wait_user 时,reason 说明用户要做什么。同一个动作也会传给 onUserAction。
通知边界: Clink 不会自动向用户发送短信、邮件或支付通知。绑卡链接由开发者代码通过自己的 UI、消息或通知渠道下发;链接内容不要交给 LLM 拼接或改写。
你也可以不等 pay(),主动发起绑卡:结账中用 checkout.bind(),结账外用 vault.sessions.create({ email })。新卡和已有卡重新输入 CVV 都用这个链接。vault.cards.list({ customerId }) 可以查看卡和窗口到期时间。

处理结果

failed 的常见 reason:
  • card_not_bound:没有可用的卡,且没传 email,SDK 无法发起绑卡。
  • window_expired:30 分钟支付窗口已到期,且没传 email。传了 email 时 pay() 直接返回 wait_user(bind_card),用户选择这张卡重新输入 CVV 即可。
  • card_frozen / token_revoked:卡已冻结,或卡 token 已被撤销。请用户通过 checkout.bind() 绑定其他卡。
  • placeholder_mismatch:页面请求里出现了非占位卡的卡号或 CVV,SDK 拒绝放行。
  • checkout_request_not_seen:submit 后 30 秒内没拦截到 PSP 请求,检查是否真的点了支付按钮。
  • session_expired:绑卡 session 失败或被锁定,调 retry() 后再 pay() 生成新链接。链接只是过期时不会报错,pay() 会自动生成新链接。
  • card_token_missing / card_token_not_found:绑卡已完成,但 session 没带卡 token,或卡列表里找不到这个 token。SDK 不会再发起绑卡,需要排查服务端数据。
  • merchant_declined:PSP 已授权,但 resolveMerchantResult 返回 failed,reconcile() 已上报。可调 retry() 重新发起。
  • paypal_redirect_url_not_found:点击 PayPal 支付后 30 秒(submitTimeoutMs)内没找到 PayPal 批准链接。
  • cancelled:支付拿到结果前调用了 cancel() 或 detach()。
  • api_error / network_error:调用 Clink Vault 被拒绝或无法连接,例如 Client 凭证无效。检查凭证和网络后重试。
unsupported 的 reason:
  • rule_not_matched / psp_unsupported:页面的 PSP 请求没有匹配到启用的规则,见 PSP 覆盖范围。
  • unsupported_card_brand:用户的卡品牌没有对应的占位卡。
  • card_encrypted:页面把卡字段加密后发往没有规则覆盖的地址,例如商户自己的服务器。
  • cse_request_unsupported:SDK 不中继的 Adyen 请求,例如保存卡或使用已存的卡支付。
outcome_unknown 的 reason:
  • pay_qr_unavailable:提交后没取到可用的二维码。商户可能已受理,不能自动换卡。
  • psp_timeout / missing_response / merchant_result_unknown:PSP 没有及时响应、Clink Vault 没有返回 PSP 响应,或 resolveMerchantResult 返回 unknown 或抛错。先核实订单,再做其他操作。

PSP 覆盖范围

SDK 在 attach 时从服务端同步识别规则,新增标准 PSP 只需下发规则,不用发 SDK 版本。规则同步失败(网络错误、超时、429、5xx)或返回格式不正确时,回退到内置规则。Clink 返回其他错误(如 401 / 403)时不会回退:attach 仍会成功,但不加载任何规则,页面的卡请求会返回 unsupported(rule_not_matched)。Client 凭证无效时,pay() 会更早返回 failed(api_error)。 SDK 只支持在页面上填写占位卡的一次性支付。请关闭商户页面上保存卡的选项。如果页面使用商户已保存的卡支付,请求中没有占位卡,不会经过 Clink Vault。
  • Token 模式:页面明文发出占位卡,Clink Vault 换成真实卡。覆盖 18 家 PSP、80 条内置规则:Stripe、Checkout.com、Airwallex、Rapyd、Global Payments、Square、Authorize.net、Nuvei、EBANX、Mercado Pago、PayU、Razorpay、Mollie、Paysafe、Recurly、Bambora、Spreedly、Shopify。
  • CSE 模式:Adyen Sessions 支付接口(checkoutshopper-*.adyen.com),已启用。识别 adyen-web 的 JWE 密文和旧版 securedFields 3.x 的 adyenjs_0_1_* 密文。服务端用 Adyen 公钥加密真实卡后代发,页面和 Agent 都看不到密文。
  • 暂不支持:Braintree、Worldpay、dLocal、Stripe Checkout Session confirm、Hosted Form、商户自托管的 Adyen。返回 unsupported,加密后的占位卡不会被发出。
占位卡支持 Visa、Mastercard、American Express、Discover、JCB、UnionPay。其他卡品牌 pay() 返回 unsupported(unsupported_card_brand)。 除银行卡外,SDK 还支持两类只在浏览器侧完成的支付方式:不走 Clink Vault 授权,也不发送卡 token。页面必须已经提供这种支付方式,SDK 不创建订单、不生成二维码。
  • 扫码支付(paynow、wechat_pay、upi_qr、pix、bcmc_mobile):从页面或 iframe 取出该钱包自己的二维码(跳过 logo 和其他钱包的码),统一转成 PNG 返回。PayNow 的过期时间取二维码里写的到期时间,其他默认 10 分钟。付款结果以 reconcile() 为准。
  • PayPal 链接(paypal):Agent 选中 PayPal 并点击支付后,调一次 pay({ method: 'paypal' }),返回 paypal.com 批准链接。之后用户在 PayPal 的操作不归 SDK 管。
fallback: 'card' 只用于扫码:resolveMerchantResult 明确返回 { status: 'failed', payment_confirmed_unpaid: true } 时才回退到卡;结果未知、待定或二维码本地过期都不会自动回退。

核心代码示例

以下示例都运行在你的服务端。不同商户的结账页结构不同,请按 Agent 实际操作的结账页调整选择器和页面判断。

完整结账流程

checkout.ts 从挂载到上报,完成一次银行卡支付。card-form.ts 负责填写占位卡、点击支付和读取商户订单结果。loadVaultCard、saveVaultCard、notifyUser、showQrCode 是你应用自己的函数。

把支付步骤封装为 Agent 工具

由 LLM 决定每一步时,给它推进支付的工具,而不是卡数据。模型只能看到状态和下一步;卡由你的代码填写,订单结果由 resolveMerchantResult 从页面读取,模型无法上报商户页面没有展示的成功结果。
agent-tools.ts

使用云浏览器

SDK 不负责启动或管理浏览器。使用云浏览器时,挂载到 Agent 已经在操作的页面,然后执行同样的 pay() 循环。
云浏览器的 API Key 和 Clink Client Secret 只放在服务端,不要交给 Agent。遇到 3DS 时,把云浏览器的 live view 展示给用户完成验证。