把通知当消息,而不是直接执行的命令
Stripe 明确说明回调可能重复投递,而且不保证到达顺序。[2] 这是 Stripe 文档描述的行为,不代表其他供应商采用相同重试时间表。但它支持一个通用设计建议:接收到消息不应立即等于发货或加款。先确认消息来源,保存事件,再判断它对应的业务效果是否已经发生。业务正确性不能依赖网络刚好只投递一次,也不能依赖客户只刷新一次页面。
先验签,再接受任务
Stripe 要求使用原始请求体进行签名验证,并建议使用官方库。[2] 接入其他供应商时,应使用对方真实文档中的签名方式,不要编造通用请求头,也不要把 Stripe 的验签代码直接套给 PayIn。无效签名应拒绝;签名密钥保留在服务端,沙盒与生产配置分离。即使签名有效,也只说明消息在该验证机制下的来源和完整性,不说明当前订单仍然适合被发货。
持久化接收之后再确认成功
Stripe 建议异步处理并快速返回成功状态,避免在 HTTP 请求中完成复杂业务。[2] 我们建议采用验签入口、持久化收件箱或队列,以及执行受约束状态转换的工作进程。成功响应应发生在可靠接收之后,而不是刚启动一个内存任务就返回。条件允许时,将事件记录与业务状态更新放在事务中。进程中途崩溃后,应可以重试,而不再次释放库存或增加客户余额。
既去重消息,也去重业务效果
Stripe 建议记录已处理事件 ID,并说明不同事件对象有时可能指向相同对象与事件类型。[2] 因此,供应商事件标识和业务幂等键应该分开。以履约为例,幂等键可以代表某订单的一次特定履约动作,不一定只使用交易哈希,因为一笔链上交易可能还需要更精确的事件标识。建议通过数据库唯一约束或同等原子机制保护结果。这是架构建议,不是一段可直接部署的完整实现。
恢复不能依赖完美投递
PayIn 对账文档列出了“回调投递成功但内部履约失败”以及重复处理等异常。[7] 应建立比较权威支付记录与内部业务结果的复核路径。建议测试重复投递、乱序通知、错误签名、工作进程崩溃和完全漏通知。保留消息引用、处理决定以及最终入账或履约编号。HTTP 返回 200 仅说明接口层面成功,不能据此宣称业务已经修复;还必须核实预期效果存在,并且只存在一次。