易支付网关接入复盘:从「签名失败」到全链路稳定
🎯 目标与预期
目标:在 Cloudflare Workers 上接入易支付,支持四种通道(支付宝 alipay、微信 wxpay、USDT gmpay、Stripe fiatstripe),美元直接计价收款,支付成功后自动开通会员。
原计划假设:按「标准彩虹易支付」协议接入——&key= 前缀签名、下单金额严格校验、仅监听 POST 回调。预期一次打通、无需返工。
📅 事件时间线
| 时间 | 事件 | 结果 | 关键学习 |
|---|---|---|---|
| 09-05 | 初版接入:四通道、USD+每日汇率表、验签+金额校验+幂等+主动查单 | 本地测试通过 | Workers 运行时无 MD5,需引入 [email protected] 并封装 |
| 09-06 | 用户澄清:美元直收,移除全部 USD→CNY 汇率换算 | 删除汇率表/汇率刷新任务/KV 缓存 | 通道自带汇率换算时,商户侧无需维护汇率 |
| 09-06 | 「配置了商户但还是签名失败」 | 4 种签名变体实测,定位 payone.uk 需 direct 模式 | 不同易支付站点签名协议不同,不能写死 |
| 09-06 | 支付成功但会员状态未更新 | 修复金额校验容差 + 回调多形态兼容 + 主动查单 | 金额校验要兼容通道汇率换算;回调不可靠需双通道 |
| 09-06 | 订单展示与支付页品牌化 | 默认 5 条+查看更多、四通道品牌图标 | — |
| 09-07 | 全项目源码审查 | 支付链路确认无问题 | 兜底设计(查单+幂等+前端轮询)有效 |
🔍 聚类观察:四类坑与修复
① 签名模式不统一(最耗时)
现象:管理后台已配置商户号与密钥,下单仍报「签名失败」。
诊断过程:对签名做了 4 种变体逐一实测(&key= 前缀 / 直接拼接 / 排序差异 / 大小写差异),用 curl 跟随重定向验证收银台是否接受。确认本项目所用的易支付站点 payone.uk 采用 direct 模式:按参数名 ASCII 字典序拼接 k=v&... 后,直接拼接密钥再做 MD5,而非标准彩虹易支付的 &key=密钥 前缀。
修复:epay.ts 增加 sign_mode 字段,epaySign / verifyNotify 支持 standard / direct 双模式;管理后台新增「签名模式」下拉;下单与验签均按所选模式执行。
② 金额校验过严,回调被误拒
现象:支付成功、回调也到达,但会员状态未更新。
根因:订单以 USD 计价(实测订单 4.99 USD),易支付通道在收款时按自身汇率换算成 CNY(同一笔订单通道收款 34.43 CNY)。初版 notify 按 price_usd ±0.02 严格校验,换算后的回调金额必然被拒收。
修复:金额校验放宽为容差窗口校验(按通道换算逻辑允许合理倍率偏差),并辅以主动查单确认真实支付状态。
③ 回调只监听 POST,存在漏单窗口
现象与根因:初版只处理 POST JSON 回调,不同易支付站点可能以 GET 或表单(x-www-form-urlencoded)形式通知。
修复:notify 端点统一兼容 GET / POST / JSON / 表单四种形态;订单保持 pending 超过 30 秒时,主动调用 api.php?act=order 查单兜底。
④ 履约非幂等,重复回调有风险
修复:将「开通会员」抽成 fulfillOrder(),以订单状态为唯一权威:仅 pending → paid 才履约;重复回调直接返回成功,不重复累加会员时长。
回跳闭环:return_url 带回 out_trade_no,前端从 #/billing?pay=... 解析后轮询订单状态,支付成功后自动刷新会员信息。
📖 签名协议对比(保留的核心知识)
| 对比维度 | standard(彩虹易支付) | direct(payone.uk 等) |
|---|---|---|
| 参与签名参数 | 去除 sign、sign_type 与空值 | 同左 |
| 排序 | 参数名 ASCII 字典序 | 同左 |
| 拼接 | k=v&...&key=商户密钥 | k=v&... 后直接拼接密钥(无 &key= 前缀) |
| 摘要与比对 | MD5,转小写比对 | 同左 |
| 适用站点 | 传统彩虹易支付站点 | payone.uk 等新站点 |
💱 金额与计价的取舍
| 版本 | 计价与校验 | 问题 | 结论 |
|---|---|---|---|
| v1 | USD+每日汇率表;回调按 price_usd ±0.02 严格校验 | 汇率刷新任务与 KV 缓存复杂;回调金额按通道汇率换算后必然对不上 | 商户侧不应维护汇率 |
| v2 | USD 直收,money=price_usd 原样下单;回调改容差窗口校验 | 无 | 通道自带汇率换算时直接透传,校验留容差 |
💭 洞见与待验证假设
▸ 洞见:易支付这类「共享协议聚合站」的接入风险不在协议文档,而在各站点的实现差异;把差异参数化(sign_mode、api_url、pay_types 均可配置)比逐站写适配代码更省。
▸ 洞见:支付闭环的可靠性排序为「幂等履约 > 主动查单 > 多渠道回调 > 前端提示」,资源应按这个优先级投入。
▸ 待验证假设:payone.uk 的 direct 模式是否代表该平台所有商户的统一行为——目前仅凭本项目一个商户号的实测,样本为 1;后续接入新商户或新站点时应先跑一次签名自测再上线。
✅ 保留项(这套设计继续沿用)
✓ 管理后台全参数化:api_url / pid / key / 支付方式勾选 / 签名模式,均不进代码,开源分发零改码。
✓ 下单与回调共用同一签名函数,避免两处实现漂移。
✓ 订单表记录 price_usd / out_trade_no / 通道单号,管理后台可按状态筛选。
✓ Workers 无 MD5 的坑已用 [email protected] 补齐并封装在 src/crypto/md5.ts。
🧪 改进实验(下一轮验证)
| 实验 | 动作 | 验证条件 | 跟踪位置 |
|---|---|---|---|
| 新站点接入自测 | 接入第二个易支付商户或站点时,先跑签名自测脚本再上线 | 首笔订单支付+回调全链路通过 | 项目 docs/ 支付复盘文档 |
| 对账告警 | 订单已 paid 但超过 5 分钟未履约时,管理后台标记异常 | 异常单可被后台发现并手动补单 | 管理后台订单状态 |
| 回调失败重试 | notify 处理失败时返回非 2xx,触发通道自动重推 | 观察通道重推日志,最终履约成功 | 同步/履约日志旁路 |
🔁 复查
本轮复盘覆盖 09-05 至 09-07 全部支付相关改动,结论均回链当时沟通记录与线上代码;「改进实验」中的三项均为可验证动作,下一次接入新支付站点时按实验一执行。
评论区
0 条评论 · 评论需审核通过后显示
发表评论