建议锁定
@clink-ai/clink-elements 的版本号。完整链路长什么样
Elements 只替换前端那一段。创建 Session 仍然必须在商户服务端完成,因为那一步要用 Secret Key。 第 1 步要提交的是商品标识,不是浏览器算好的最终金额。金额由第 2 步在服务端重新算。四方职责
一、服务端:写一个自己的 Session 接口
这一层不是可选优化。它同时是三条边界:Secret Key 的安全边界、金额校验的边界、本地订单关联的边界。 下面用的clinkRequest 就是 Hosted Checkout 接入 里那个带认证头的 helper,它返回的是响应中的 data。
uiMode传elements,returnUrl必填。URL 里可以放{ELEMENTS_SESSION_ID},Clink 会替换成真实 Session ID- 字段名是
returnUrl。有些旧资料写成redirectUrl,那是错的——redirectUrl是requires_action场景下响应里的跳转地址 merchantReferenceId只用于关联对账,不是幂等键。同一个值调两次会得到两个 Session
Clink 返回什么
上面clinkRequest 拿到的 data,节选如下:
{ "code": 200, "msg": "success", "data": { ... } } 这层信封,clinkRequest 已经剥掉了。
expireTime 当前返回的是 "2026-07-30 12:00:00" 这种格式,不带时区标识,也不是 RFC 3339。别直接丢给 new Date() 或按本地时间解析——不同运行环境的解释会不一致。也不要按”商户账户时区”或浏览器时区去推断。 后端目前用服务进程的默认时区序列化,没有按账户做转换,所以这个字符串代表哪个时区,接口本身没有表达。要精确处理跨时区逻辑,得等后端把输出契约升级成带 offset 的 RFC 3339 或 Unix 时间戳。在那之前,判断 Session 是否还能付款请看 status,不要拿这个字符串自己算。- 响应里没有 Publishable Key,也没有
environment。 这两样来自商户自己的应用配置,见下一节 - 响应里有
url,那是托管收银台地址。Elements 接入不使用它
二、前端:调自己的接口,然后初始化
浏览器只做两件事:向商户后端要一个sessionId,然后用它初始化 SDK。
PUBLIC_CLINK_PUBLISHABLE_KEY 和 environment 是应用的部署配置,来自后台 开发者 > API Keys 里的 Publishable Key(pk_uat_ 开头)。它们可以公开出现在浏览器里,但不来自 Create Session 的响应。
- Vite
- Next.js
SDK 的参数名是
publishKey,正文里说的 Publishable Key 就是它。代码里必须写 publishKey。三、挂载、提交与第三方按钮
结账页上可能出现两类按钮,处理方式完全不同: 商户自己的支付按钮 —— 银行卡这类需要宿主触发提交的流程。点击后调clink.submit()。
SDK 内置的第三方按钮 —— Apple Pay、Google Pay、PayPal 等。它们由 paymentMethod 组件内部渲染,点击也由 SDK 接管。这时 SDK 会发 submit-visible: false,宿主页面要把自己的按钮藏起来。
submit-enabled 报告的是「能不能提交」。按钮写 disabled = !enabled,别把事件值直接塞进 disabled——那样逻辑正好反过来。
配置 SDK 按钮样式
从0.0.3 开始,可以通过 presetOptions.sdkButtons 配置 Apple Pay、Google Pay、Link 和 PayPal 的按钮样式。
- 为兼容当前所有按钮渲染方式,请将高度设置在
40–55像素之间 - 非有限值或小于等于
0的高度会回退到45;其他不受支持的正数不会回退,可能导致对应按钮初始化失败 - 非有限值或负数圆角会回退到
6;超过按钮高度一半时会自动取高度的一半 - 不受支持的主题和类型会回退到对应默认值
- 所有 SDK 按钮都是
100%宽度 - Apple Pay 没有设置主题时,浅色模式用
black,深色模式用white - PayPal 的高度和圆角由 Clink 应用在 PayPal 组件的外层容器上,不会作为 PayPal SDK 参数传入
loadClinkElements()。
获取 SDK 按钮可用状态
在挂载paymentMethod 之前监听 sdk-button-initialized,避免漏掉首次事件。
true表示按钮初始化成功,并且当前设备可以使用false表示设备不支持、配置有误、网关不支持、第三方 SDK 加载失败,或按钮在当前初始化周期内超过 10 秒仍未完成- 只返回当前 Session 中存在的按钮;没有第三方按钮时返回
{}
监听第三方支付弹窗
监听sdk-dialog-open 和 sdk-dialog-close,即可获知第三方支付弹窗打开和关闭。回调参数是触发事件的支付方式。
applePay、googlePay、link 或 paypal 之一。
配置货币选择器样式
从0.0.5 开始,可以通过 presetOptions.currencySelect 自定义货币选择器的外观。所有样式参数都是标准的 CSS 样式对象,写法和 React 的 style 相同。
有多种货币时,各参数控制的区域如下(
[US]、[EU] 表示国旗图标):
oneCurrencyStyle会完全替换默认样式,设置后需要自己指定字号、字重、颜色等switcher下的样式只覆盖你设置的属性,未设置的属性保持默认外观- 选中卡片会在
itemStyle、textStyle的基础上叠加selectedItemStyle、selectedTextStyle,同名属性以选中样式为准 - 在
itemStyle中设置边框色或背景色、在textStyle中设置文字颜色或字重后,默认的选中高亮会被覆盖,请同时设置对应的选中样式 hideIfOneCurrency为true且只有 1 种货币时,oneCurrencyStyle不生效- 标题、国旗图标和汇率说明暂不支持自定义
selectedItemStyle 中写需要变化的属性即可,其余属性沿用 itemStyle:
loadClinkElements()。
使用 Express Checkout 快捷支付
从0.1.0 开始,paymentMethod 支持 Express Checkout 模式。这个模式只展示 Apple Pay、Google Pay、PayPal 和 Link 快捷支付按钮。
Express Checkout 不是新的组件类型。创建 paymentMethod 时传入第二个参数即可开启:
paymentMethod 仍是普通支付模式,行为与之前相同。
两种模式的区别
presetOptions 里的 locale、theme、primaryColor、radius 等通用配置在 Express Checkout 中照常生效。currencySelect 也可以照常创建和挂载。
配置项
完整类型如下:- 高度和圆角只能统一设置,不支持给单个按钮单独设置
- 按钮文案只能通过
buttonType选择第三方 SDK 支持的值 - 按钮纵向排列,间距
8px,宽度占满容器
按钮排序与显示
设置了paymentMethodOrder 时:
- 先按配置的顺序排列(不区分大小写,重复项和不支持的值会被忽略)
- Session 返回了、但配置里没写的方式,按 Session 原顺序追加到末尾
paypal、applePay、googlePay,配置为 ['googlePay'],最终顺序是 googlePay、paypal、applePay。
一个按钮最终是否显示,要同时满足三个条件:
- Session 返回了这种支付方式(取决于商户配置、币种等)
- 对应的
visible不是'never' - 当前浏览器和设备支持,第三方 SDK 初始化成功
sdk-button-initialized(见上文「获取 SDK 按钮可用状态」)获知实际可用的按钮。
Session 里没有任何快捷支付方式时,组件不渲染任何内容(包括地址字段),并在控制台输出 warning。
账单地址与税费
如果 Session 需要计算税费,组件会在按钮上方展示国家、地址、城市、邮编等必要的地址字段。客户修改地址后,Clink 会自动重新计算税费并更新金额,这段时间内按钮暂时不可点击。 金额变化同样通过amount-change 事件通知宿主页面,订单摘要以事件返回的金额为准。
事件
Express Checkout 复用现有事件,不需要额外监听新的事件:submit-enabled 和 submit-visible 针对宿主支付按钮,Express Checkout 中不需要处理。
使用限制
mode在创建时确定,之后不能切换。需要换模式时,销毁当前实例并重新调用loadClinkElements()- 同一个实例只能创建一个
paymentMethod,因此一个实例不能同时提供普通支付表单和 Express 按钮 - 和普通模式一样,
session-success不是付款凭证,最终状态以后端收到的 Webhook 为准
四、事件
按用途分两组。 更新宿主 UI
流程提示
五、优惠码
可选能力。服务端的 Coupon 和 Promotion Code 怎么建,见 优惠与促销码。这里只讲前端。{ amount },金额字段在 amount 里面。写成 (info) => info.enablePromotionCode 拿到的是 undefined,优惠码入口会一直不显示。
折扣和最终应付金额以 amount 返回的为准,不要自己算。
六、错误处理
0.0.5 会抛出三类东西,处理方式不一样。
初始化:loadClinkElements()
它不是只抛 ClinkApiError。至少有三条路径:
响应体不是合法 JSON 时,
await res.json() 还会抛原生 SyntaxError。
所以 catch 参数要按 unknown 处理,并且一定要留兜底分支:
初始化之后:部分错误是同步抛的
不是所有运行期问题都走事件。 下面这些是同步抛出的原生Error,必须用 try/catch 接:
支付流程中的错误:走事件
真正跟支付流程有关的错误由收银台 iframe 发事件转发,不会被包装成异常类。七、实现约束
这几条写错会直接导致接入失败,其余布局细节按自己的设计来就行。上线自查
- 浏览器 Network 和构建产物里没有 Secret Key,也没有 Webhook 签名密钥
- 浏览器只调用商户自己的 Session 包装接口,没有直接调 Clink 的 Create Session
- 后端不相信浏览器传来的最终金额,按商品或购物车重新计算
- 本地订单、
merchantReferenceId和sessionId三者的映射已保存 - Publishable Key 和
environment来自应用公开配置,没有被当成 Create Session 的响应字段 - Apple Pay、Google Pay、PayPal 等按钮出现时,
submit-visible能让宿主页面的按钮隐藏 - 使用 Express Checkout 时,页面没有放宿主支付按钮,并处理了没有可用按钮的情况
-
session-success不直接触发发货,最终状态来自验签 Webhook 和后端查询 - Session 变化或页面卸载时销毁了旧实例
接下来
Hosted Checkout 接入
后端和 Webhook 那两块,Elements 和 Hosted Checkout 完全一样。
上线检查
切生产前要过的清单。