打开网易新闻 查看精彩图片

上周跑对账脚本时,3笔交易全部撞墙——2笔明明扣款成功、回调确认、收据入库,STK查询接口却一口咬定"失败"。这不是代码bug,是非洲最大支付平台沙盒环境的系统性撒谎。

数据不会说谎,但接口会

数据不会说谎,但接口会

开发者Chris在Daraja沙盒跑 reconciliation(对账)时,抓到了一组荒诞的返回:checkoutRequestId 尾号 29173 和 29173 的两笔交易,数据库存着 SUCCESS 状态和收据编号 UCQ5UAQ403、UCQ5UAPYRY,测试账户也确实扣了款。STK 回调当时给的 ResultCode 是 0——M-Pesa 的官方成功信号。

同一套系统,同一个 transaction ID,STK Query API 的回应却是 FAILED。

他翻遍 Stack Overflow、Safaricom 的 GitHub 仓库、所有社区集成方案。零记录。零讨论。零 issue。这个坑似乎从未被人公开报告过。

沙盒的回调和查询接口,根本不在读同一个数据源

沙盒的"模拟"有多敷衍

沙盒的"模拟"有多敷衍

Safaricom 文档承认过一件事:沙盒无法完整模拟 USSD 网络层。这就是第三方工具 Pesa Playground 存在的原因——开发者需要更真实的测试环境。但文档没说的是,沙盒的 STK Query 端点连"确认成功"都做不到。

打开网易新闻 查看精彩图片

它的默认策略是:只要无法明确解析交易状态,一律返回 FAILED。不管回调已经说了什么,不管钱有没有真的扣。

换句话说,沙盒查询接口的设计逻辑是"宁可错杀"——但错杀的是已经完成的支付。

Chris 写的 mpesa-stk@0.1.1 库处理得很克制:matched:0。发现查询响应和库存的 SUCCESS 冲突,选择不覆盖。那笔 orphaned(孤儿)的 PENDING 记录保持原样,而不是被强行改成 FAILED。

这个选择背后是个残酷事实:一个会覆盖 SUCCESS 的对账系统,比什么都不做的系统更危险。

生产环境会不会也这样

生产环境会不会也这样

Chris 没跑过生产环境测试。Safaricom 文档暗示生产端的 STK Query 是准的——问题出在沙盒。但这个"暗示"本身就是问题:开发者被默许用一套撒谎的环境测试核心支付流程,然后被告知"生产应该不一样"。

非洲移动支付的基础设施里,M-Pesa 是事实标准。2.96 亿用户、年交易流水占肯尼亚 GDP 的 87%。它的开发者生态却长期被沙盒的粗糙对待——回调延迟、状态不一致、文档滞后。

这次暴露的是更底层的断裂:同一个平台内部,两个官方接口对同一笔交易给出矛盾结论。回调说成功,查询说失败。开发者该信谁?

打开网易新闻 查看精彩图片

Chris 的代码选择了信回调——因为那是交易发生时的第一手信号。查询是事后补录,只能用来处理那些从未收到回调的 PENDING 记录。这是防御性编程,也是对平台不信任的投票。

给你的对账系统两条铁律

给你的对账系统两条铁律

第一,永远不要仅凭查询响应覆盖终态记录。 SUCCESS 或确认过的 FAILED 都是终态,查询只能动 PENDING。回调的权威性高于查询,这是架构设计问题,不是偏好问题。

第二,别在沙盒里测对账逻辑。 它的 STK Query 不是可靠的测试面。要么上生产环境测,要么接受沙盒这条路径的结果是噪音。

有开发者在这条 issue 下留言:"我们去年就被这个坑过,当时以为是自己的代码问题,重写三遍。"另一条回复更扎心:"生产环境确实没这问题——但发现这一点之前,我们已经浪费了两个月。"

平台方的沉默正在制造重复劳动。一个未被文档化的沙盒行为,让无数开发者陷入自我怀疑。

Chris 最后抛了个问题:有人在生产环境跑过 reconciliation 吗?STK Query 在那边真的可靠吗?

评论区至今没人给出确凿答案。