跳至主要内容

加密货币支付 Webhook:每笔订单只交付一次

构建可靠的加密货币支付 Webhook 接收端:验证签名、持久化事件、去重重试请求,防止重复交付订单。

支付通知每单只交付一次

有效的支付通知也不应让同一笔订单交付两次。 加密货币支付 Webhook 需要两道独立的保护:先验证事件来自谁,再确保重复执行不会重复产生业务效果。签名解决第一个问题,幂等性解决第二个问题。

本指南面向将一次性数字商品购买、许可证或账户充值接入 Yolfi 的开发者。本文介绍原生 Webhook 格式及推荐的应用设计,而非现成的订单交付服务。对于订阅,访问权限还取决于订阅生命周期;不要把一次性订单的规则直接用于每次续订。

为什么真实的 Webhook 也会导致重复交付

接收端可能已经成功保存事件,但 HTTP 响应在传输中丢失,发送方于是重试。处理进程也可能在授予权限之后、记录完成之前崩溃。这两种情况都不意味着客户又支付了一次。

Yolfi 的 Webhook 事件参考文档说明了至少投递一次的机制,以及多个接收端点各自独立重试的行为。设计时应考虑同一事件再次到达,甚至第一次投递仍在处理中时就再次到达的情况。不要依赖某个特定的重试时间安排,也不要假设通知总会按创建顺序到达应用。

请区分以下三类标识:

标识 标识的对象 推荐的控制措施
已签名事件的 id 支付服务方的一个事件,包括重复投递 在支付服务方、组织和环境的范围内,建立唯一的事件记录
账单或支付引用 一次支付尝试 保存其与目标商业订单的关联
业务订单引用 客户应获得的内容 为该订单设置唯一的交付操作

仅对事件去重还不够:同一订单的两次成功支付尝试可能具有不同的事件 ID。订单级保护可以阻止再次发放许可证或增加余额,同时将额外收款单独交由审核。

信任字段之前,先验证原始请求

请遵循当前的 Yolfi 签名说明。原生请求使用 X-Yolfi-Signature:以该端点的签名密钥对原始请求正文计算 HMAC-SHA256,再进行 Base64 编码。组织 API 密钥不是 Webhook 签名密钥。

在 JSON 中间件更改正文之前,捕获原始正文。计算签名后,使用恒定时间比较函数进行比对;对于缺失或格式错误的签名,应拒绝请求,不得执行业务操作。将签名密钥保存在密钥管理服务或受保护的环境配置中,切勿写入源代码文件或客服日志。

只有验证通过后,才应解析并校验负载。使用已签名负载中的 id 去重。X-Yolfi-Event-ID 只是便于使用的副本,并不是单独签名的值;如需使用,必须要求它与负载 ID 一致。将接收端点绑定到预期组织,并在负载中存在 orgId 时校验它。不得用用户提交的元数据决定租户授权。

有效签名不会让旧通知变成新通知,仍然必须保留已处理事件的 ID。Yolfi 文档所述的签名并非基于时间戳的时效性检查,因此不能声称仅靠 HMAC 验证就能防止重放。

先保存,再确认接收,随后异步处理

使用持久化收件表:在应用数据库中记录已验证来源的事件及其处理状态。插入事件时应由数据库唯一性约束保障去重,而不是先单独检查“是否存在”,再执行没有保护的插入。并发请求可能同时通过这种检查。

在完成持久化接收后,应尽快返回 200 OK,已验证来源的重复事件已经存储时也一样。如果持久化失败,不要确认事件已被接收。快速响应后再启动内存中的任务并不具备持久性:一次重启就可能丢失应用保留的唯一副本。

之后,后台处理进程可以读取已保存的事件,校验其订单关联,并执行相应的业务状态变更。通过重试次数、下一步操作和告警负责人,让失败任务保持可追踪。事件正文可能包含客户信息,因此只保留必要的负载数据,并将其存放在访问受限的存储中。

确认支付并匹配正确订单后,才允许交付

Yolfi Webhook 指南将 payment.confirmed 列为主要的订单交付触发事件。invoice.created 表示账单已创建,并不表示客户已付款。浏览器跳转或截图不能替代经过验证的服务器端支付确认。

应将事件关联到服务器中保存的订单。在原生流程中,独立的 API Checkout 事件可能包含 checkoutSessionId 和 merchantOrderId,却没有 paylinkId。不要仅因为缺少 Paylink ID,就拒绝有效的独立购买。同样,仅凭可重复使用的 Paylink ID,也无法唯一确定买家的订单。

交付前,检查预期的组织、环境、支付尝试和商业金额上下文。文档中的 amount 是以支付代币计价的商户最终应收金额,并不自动等于以本地货币计价的原始账单总额。不要把这两个数字当成同一单位直接比较。应保留服务器最初批准的订单及其定价上下文。

对于动态购买,使用 API Checkout 流程保留订单与会话之间的关联。对于固定商品,支付链接提供可重复使用的支付入口;是否交付仍由你的应用决定。

不仅要保护事件表,还要保护实际业务操作

如果订单完全在应用数据库内完成交付,应在同一个事务中记录事件处理结果和订单状态变更。强制保证业务操作唯一,例如每笔一次性订单只能授予一次权益。重复事件应返回已有结果,而不是再授予一份权益。

如果交付需要调用其他服务,应使用持久化发件表:将待执行的操作与订单状态变更一起保存,再由后台处理进程投递。当接收方支持幂等键时,为下游操作设置稳定的、基于订单的幂等键。如果远程操作成功后处理进程崩溃,仅靠发件表并不能防止远程服务重复执行。

当目标服务既不支持幂等,也不能查询执行结果时,应将结果不确定的操作留待对账核查,而不是盲目重试不可逆操作。HTTP Webhook 无法承诺跨越所有数据库、队列和外部服务都能“恰好执行一次”。

不要让过时事件把已完成的订单状态回退。invoice.overdue 事件针对某一次支付尝试,不能抹去订单已交付的事实。对于周期性访问权限,支付服务方区分账单事件与 subscription.overdue、subscription.cancelled;应按文档单独实现订阅策略。

正式接单前,先测试故障路径

使用以下验收场景测试接收端和后台处理进程:

  • 将同一已签名事件重复投递两次,并测试并发投递:只存储一个事件,只产生一次业务效果。
  • 在订单状态变更前后中断处理进程:恢复后不得重复授予权限。
  • 为同一订单投递两次已确认的支付尝试:只交付一次,并标记额外收款。
  • 发送无效签名、组织不匹配以及订单未知的请求:均不得交付。
  • 在支付确认后投递较早的账单事件:不得回退已完成的交付。
  • 将测试模式事件发送到生产接收端:不得发放真实许可证、发货或增加账户余额。

Yolfi 控制台的测试事件使用 livemode: false 和以 evt_test_ 开头的 ID。它们用于检验投递和负载处理,但不会创建真实支付记录。必须明确区分测试和生产业务效果;测试通知成功,不代表某笔购买已经完成资金结算。

监控已验证来源但仍待处理的事件、处理进程故障,以及已确认付款却没有交付结果的记录。将这些异常纳入支付对账清单。如果第二笔收款确实是额外付款而非重试,应遵循退款审核流程,而不是删除相关证据。

常见问题

验证签名能防止重复增加余额吗?

不能。签名验证的是来源真实性和内容完整性。真实事件可能到达多次,先前有效的请求也可能被重放。必须持久化事件 ID,并施加订单级业务约束。

应该先返回成功,再保存事件吗?

不应该。应在完成持久化接收后确认,再异步执行耗时操作。否则,响应之后发生的崩溃可能悄无声息地丢失事件。

交易哈希足以作为去重键吗?

对于 Webhook 处理而言,并不足够。应使用文档规定的已签名事件 ID 对事件投递去重,使用自己的订单键保护交付。转账与订单具有不同的标识;一笔交易中可以包含多次转账。

适配器负载能使用同一个处理程序吗?

不能未经核对接口约定就直接复用。本文介绍的是适配器为 NONE 的原生格式。兼容其他支付服务方的适配器可能采用不同的负载结构;复用原生解析及关联规则前,应校验相应文档规定的字段。

结论:确认支付、持久化事件,每单只交付一次

可靠的加密货币支付自动化,需要将已验证来源的事件投递与订单业务效果分开处理。验证原始请求签名、持久化事件、匹配订单,并确保交付在重试时仍然安全。固定商品可从 Yolfi 支付链接开始,动态订单则使用文档所述的服务器端创建会话方式;无论采用哪种方式,都应由应用负责确保每笔购买只交付一次。

立即开始接受加密货币付款

连接钱包,创建第一个链接,几分钟内即可收款。

企业主在笔记本电脑上工作
已收到付款+120.00 USDT