工作原理
- 拦截:SDK 挂载到 Agent 驱动的浏览器(Playwright 或原生 CDP),拦截页面及所有跨域 iframe 的请求。
- 占位卡:SDK 返回一张与用户真实卡同品牌的公开测试卡(如 Visa
4111 1111 1111 1111,有效期取真实卡),Agent 把它填进结账表单。 - 识别:页面向 PSP 发令牌化请求时,SDK 按审核过的 PSP 规则(host + path + 字段)匹配,且卡字段必须等于本次下发的占位卡,才会暂停该请求。
- 中继:SDK 只把请求模板、占位卡标记、
card_token和白名单请求头发给 Clink Vault。 - 换卡:Clink Vault 校验 30 分钟支付窗口和 PSP 规则后,在 PCI 边界内把占位卡替换为真实卡,直接调用 PSP。
- 回放:SDK 把 PSP 的原始状态码、响应头和响应体回放给页面,页面像正常支付一样继续下单。
Agent / 浏览器能看到
- 卡品牌和后四位
- 同品牌占位测试卡
- 绑卡链接、支付状态
- PSP 返回给页面的原生响应
只在 Clink PCI 边界内
- 真实卡号 PAN 与 CVV(KMS 加密存储)
- 支付窗口与 PSP 规则校验
- 携带真实卡号发往 PSP 的请求
- Adyen 等客户端加密场景下的卡密文
快速接入
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
上报结果并卸载
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,加密后的占位卡不会被发出。
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() 循环。