Skip to main content
这一页假设你已经选好了接入方式。如果还没有,先看 选择接入方式 下面的例子用 Hosted Checkout + Node.js/Express。换成别的语言或框架,结构是一样的。
这一页讲的订单模型、Session 创建和 Webhook 处理,是所有付款共用的基础——一次性支付、订阅、带优惠的支付都建在它上面。示例本身走的是一次性支付。周期收费另见 订阅支付,折扣另见 优惠与促销码

你至少要写三个接口

商户订单要存什么

至少这些字段,不然出问题时排查不了:
商品快照要存下单那一刻的值。商品涨价之后,你还得知道这个客户当时买的是多少钱。 支付状态和发货状态分开存,是因为它们会不同步:钱收到了但发货失败,需要能查出来重试。

1. 创建订单和 Session

服务端也可以用 @clink-ai/clink-typescript-sdk,它替你处理认证请求头和类型定义。上面直接调 API 的写法是为了让你看清每个字段的位置。
几个容易踩的点:
  • 金额从数据库取。前端传过来的价格一律不信,否则客户改个请求就能一块钱买走会员。
  • 金额是主货币单位,不是分。19.99 美元传 19.99。传 1999 会被当成 1999 美元,扣款差 100 倍。日元、韩元、印尼盾这类零小数位币种只能传整数。
  • 别用浮点直接乘金额。JavaScript 里 0.1 * 3 等于 0.30000000000000004,而 Clink 会精确比对商品明细之和与总额,这点误差就会被拒。价格按整数分存,乘完数量再转回主单位;金额结构复杂时用 decimal 类库。
  • merchantReferenceId 填你自己的订单号。Clink 不拿它做幂等——同一个值调两次会得到两个 Session。防重复下单是你自己的事。
  • referenceCustomerId 填你系统里的用户 ID。以后再给同一个客户创建 Session,Clink 能自动关联到同一个 Clink 客户。

2. 回跳页面

客户付完款跳回 successUrl。这个页面要做的事只有一件:查你自己后端的订单状态,把结果显示出来。
刚跳回来时 Webhook 可能还没到,订单状态还是 pending。这时候显示「支付确认中」,隔几秒再查一次,别直接显示失败。 如果一直是 pending,你的后端可以主动查一次 GET /checkout/session/{id} 补上状态。

3. 接收 Webhook

这是整个接入里最要紧的一段。付款结果以这里为准。

验签

Clink 用 HMAC SHA-256 签名,签的是 时间戳 + "." + 原始请求体 Node 环境直接用 SDK 就行,不用自己写:
必须用原始请求体验签。JSON 解析之后再序列化回去,字段顺序和空格都可能变,算出来的签名一定对不上。Express 里要用 express.raw(),不能用 express.json()
签名规则很简单,任何语言都好实现:
三个请求头:X-Clink-Timestamp 是时间戳,X-Clink-Signature 是签名,X-Clink-SignType 目前固定为 SHA256

完整的处理器

匹配订单、终态保护、幂等发货这三件事要对所有 order.* 事件生效,不能只写在成功分支里。常见的写法是成功事件做了完整校验,失败事件却直接按订单号改成失败——一个迟到的 order.failed 就能把已经收到的钱标记成失败,货也退了。
退款事件要单独处理,因为它的结构不一样:refund.* 的对象里没有 merchantReferenceId,只有 orderIdrefundMerchantOrderId。所以要用付款时存下的 clinkOrderId 反查本地订单。

这五件事一件都不能少

1

验签

用原始请求体算 HMAC SHA-256,比对 X-Clink-Signature。签名对不上就返回 401。
2

按事件 ID 去重

投递失败 Clink 会重试,同一个事件你会收到多遍。用 event.id 去重,重复的直接返回 200。
3

双重匹配订单

同时校验 merchantReferenceIdsessionId。只对上一个就当异常处理,别更新状态。
4

容忍乱序

Clink 不保证事件按发生顺序到达。已经是 paidrefunded 的订单,不能被后到的旧事件改回 pending
5

写库成功后再返回 2xx

返回 200 表示”我处理完了”。还没落库就返回 200,这个事件就永远丢了。

订阅哪些事件

一次性支付至少订阅这几个: 做订阅业务的话,订阅和账单事件另有一套,见 订阅支付。完整事件列表见 Webhook 参考,也可以调 GET /webhook/events 查当前支持的事件名。
订阅时要写完整的事件名。subscription.* 这类通配符不是能提交给 API 的值。

本地怎么调

Webhook 地址必须是公网可达的 HTTPS,localhost、回环地址和内网 IP 都会被拒绝。 如果你已经有可用的公网地址(预览环境、Vercel/Netlify 之类的部署地址、自己的域名),直接用那个。纯本地开发才需要隧道:
如果 QUIC 连不上,加 --protocol http2 重试。 隧道地址每次重启都会变,换了地址记得回后台更新 Webhook 端点。

不刷卡也能调验签

隧道加真实付款能验证端到端,但反复调验签逻辑时,每次都刷一遍卡太慢。自己造一个带签名的请求打到本地就行:
改几个字段就能把该测的都测一遍:

谁负责什么

常见错误

在回跳页面发货。 successUrl 是个普通地址,客户手动敲一遍也能打开。发货只能由验过签的 Webhook 触发。 Webhook 收到就发货,不去重。 Clink 会重试,同一个事件你会收到好几遍。不去重的话客户下单一次收到三件货。 pending 当成失败,让客户重新付。 pending 是”还不知道”。这时候重新扣款,很可能扣两次。等 Webhook,或者查 GET /order/{id} express.json() 解析 Webhook 请求。 解析过的 body 再序列化回去,签名对不上,所有事件都会验签失败。 Secret Key 出现在前端代码里。 拿到它的人可以用你的账户发起收款和退款。前端只能拿 Publishable Key。

接下来

密钥与 Webhook 配置

密钥轮换、IP 限制、投递重试规则。

上线检查

切生产前要验的场景清单。