文章 2026/10/23

接入易支付时,为什么拿到付款返回页还不能发货?

从下单、签名到通知幂等,梳理个人收款 API 接入时容易混淆的状态。附可离线运行的 MD5 测试向量,避免把浏览器跳转当作付款凭据。

接入易支付时,为什么拿到付款返回页还不能发货?

做收款接入时,一个容易漏掉的问题是:用户回到了“支付完成”页面,后台却还没有可靠的付款结果。这时就发货,会把浏览器跳转当成收款凭据。

我是 8码分享团队成员。这篇结合我们当前公开的易支付兼容接口说明,整理接入时的几个判断点。8码分享支持个人微信、支付宝收款,付款确认后自动发货;外部应用接入仍以账号接口权限、实际可用通道为准。下文使用演示参数,只验证签名计算,没有执行真实下单或付款。

先把三个结果分开

创建订单成功,只说明平台接收了订单,并给出了收银页地址。

买家回到 return_url,只说明浏览器完成了一次跳转。不能因为访问了这个地址,就把订单改成已付款。

真正用于交付的结果来自服务端:收到通知后完成验签和订单核对,或通过服务端查单确认付款。无论走哪条路,都要和创建订单时保存的数据比对。

下单时保存什么

8码分享的 /mapi.php 接受 POST 表单,Content-Type 是 application/x-www-form-urlencoded,不是 JSON。创建订单后,保存自己的 out_trade_no、平台 trade_no、原始金额、付款方式和通知地址,再打开返回的 payurl。

金额建议以整数“分”存储,发送时转换成两位小数字符串,避免浮点计算把 10.01 变成另一笔金额。个人收款要求买家填写收银页显示的精确金额,一分钱也不能有差异;接入方也不能为了避开订单冲突而自行增减金额。

请求超时不等于下单失败。重试时保留原订单号和原始参数,不要每重试一次就生成一笔新订单。

一个可以离线跑的签名例子

签名前移除 sign、sign_type 和空值,按参数名 ASCII 升序排列,用 & 连接,再直接追加密钥。先签名,再做表单编码。字符串 "0" 不能当作空值删除。

下面只演示签名函数,输入是已经解析、且拒绝了同名重复参数的单值字符串字典。secret 是公开测试密钥,不是任何账号的商户密钥。

import hashlib

def sign(params, key):
    pairs = [
        f"{k}={v}" for k, v in sorted(params.items())
        if k not in ("sign", "sign_type") and v not in (None, "")
    ]
    raw = "&".join(pairs) + key
    return hashlib.md5(raw.encode("utf-8")).hexdigest()

params = {
    "money": "10.01", "pid": "10000", "name": "测试商品",
    "empty": "", "sign_type": "MD5"
}
assert sign(params, "secret") == "2b2da921f78d3e3e8fbe2631eb0df3ae"

这组参数的待签名原文是 money=10.01&name=测试商品&pid=10000secret。若先把中文做 URL 编码再签名,结果会不同。实际接入请从服务端配置读取密钥,不要放在网页、截图或公开日志中。

通知到达后,验签只是第一步

平台通过 GET 请求 notify_url。接收时拒绝同名重复参数,按文档验签,再用原始订单核对商户号、商户订单号、平台订单号、付款方式、金额及 TRADE_SUCCESS 状态。

不能把通知里的金额同时当成“收到的金额”和“预期金额”。预期值必须来自你自己的订单记录。

如果 notify_url 自带查询参数,先按保存的原始通知地址核对这些字段,再按文档从平台签名字段中移除;也可以在初次接入时使用没有额外查询参数的通知地址,减少歧义。

验签失败、金额不符或找不到订单,都不能发货。通知只提供了收款线索、平台尚未确认付款时,同样不能自行标记成功。

重复通知要能安全处理

通知可能重试。交付不能写成“每来一个请求就发一张卡”。先以订单号建立唯一处理约束,事务内更新订单状态、分配库存并记录交付任务。若交付依赖邮件或其他外部服务,再用唯一任务号重试外部动作,避免重复发送。

处理已可靠提交,或确认同一笔合法通知已经处理过后,返回 HTTP 200,正文只写 success。不要返回 JSON、HTML 或登录页。处理失败则不要提前回复 success,否则平台可能认为通知已成功接收。

查单也要区分两个成功

/api.php 的订单查询建议由后端 POST 表单调用。这里的 key 是凭据,不要放到浏览器地址栏或公开日志。

返回 code=1 表示查询请求成功,不表示买家已经付款。还要检查 status=1,并核对原始订单信息;status=0 时不能交付。

上线前至少试这些情况

  • 创建订单后,买家没有付款:不发货。
  • 直接访问返回页:不发货。
  • 签名错误或金额不符:拒绝确认。
  • 同一条合法付款通知连续到达两次:只交付一次,两次都能正常应答。
  • 通知处理暂时失败:后续重试仍能完成处理。
  • 下单响应超时:用原订单号与原参数重试,不重复创建业务订单。

本文只完成了公开测试向量的离线核验。正式启用前,仍需要在自己的接口权限和通道条件下完成真实付款、通知重试及交付验收。

完整字段、通知校验示例和接入规则见 8码分享易支付接入文档。文档公开可读,不包含商户密钥。参数含义和后续变化以当前文档为准。

文中提到的产品

暂无关联产品