PAYATHON 2026

alipay.trade.wap.pay 订单创建时机及查询验签失败原因

支付小周

明确结论

alipay.trade.wap.pay 并不是等用户输入支付密码后才创建订单。支付宝在收到并受理支付请求时,就会创建交易。

这里需要分清两个动作:

  1. 服务端调用 SDK 的 pageExecute 等方法,生成并返回 HTML form。
  2. 浏览器提交该 form,跳转到支付宝收银台。

服务端生成 form,通常只说明商户已经完成支付请求的组装和签名。此时请求未必已经送达支付宝,因此不能确认支付宝交易已经创建。浏览器提交表单并由支付宝成功接收请求后,才可能通过 alipay.trade.query 查到这笔交易。

输入支付密码是后续的付款步骤。即使用户最后没有付款,支付宝侧也可能已经创建了一笔状态为 WAIT_BUYER_PAY 的待支付交易。

过期时间为什么能够生效

alipay.trade.wap.pay 常用 timeout_express 设置超时时间,例如:

{
  "out_trade_no": "ORDER_202609090001",
  "total_amount": "99.00",
  "subject": "测试商品",
  "product_code": "QUICK_WAP_WAY",
  "timeout_express": "15m"
}

这里的 15m 表示交易创建后,如果没有在约定时间内完成付款,交易就会关闭。计时应从支付宝实际受理请求并创建交易时开始,而不是从商户服务器生成 HTML 表单时开始。

如果服务端已经生成表单,但用户一直没有打开页面,或者浏览器根本没有提交表单,支付宝可能还没有收到支付请求。此时支付宝侧没有可供查询和计时的交易。

商户自己的订单和支付宝交易也属于两个不同层次:

  • 商户订单通常在调用支付接口前创建,用于保存 out_trade_no、金额、商品信息和业务状态。
  • 支付请求到达支付宝并被受理后,支付宝才会创建交易。
  • 用户付款成功后,支付宝交易状态才会变为 TRADE_SUCCESS。

商户系统应先保存本地订单,再生成支付表单,不要等付款成功后才创建本地订单。

为什么生成了 form,却立即查询不到交易

常见的支付流程如下:

商户创建本地订单
        ↓
服务端生成支付 form
        ↓
将 form 返回给浏览器
        ↓
浏览器自动提交 form
        ↓
支付宝受理并创建交易
        ↓
用户付款或放弃付款

如果在服务端生成支付 form 后、浏览器提交之前立即调用 alipay.trade.query,支付宝可能返回交易不存在。这通常是查询时机不对,不应表现为”验签失败”。

因此,“交易不存在”和”验签失败”需要分别排查:

  • ACQ.TRADE_NOT_EXIST 或类似结果:订单可能尚未到达支付宝,也可能是订单号错误或查询环境不一致。
  • SDK 抛出验签失败:通常与密钥、证书、环境或响应处理方式有关,与订单是否付款没有直接关系。

查询接口验签失败的常见原因

1. 把应用公钥当成支付宝公钥

商户发送请求时使用应用私钥签名,接收支付宝响应时则使用支付宝公钥验签。这两种公钥不能混用。

对应关系如下:

商户发送请求:应用私钥签名
支付宝验证请求:应用公钥验签

支付宝返回响应:支付宝私钥签名
商户验证响应:支付宝公钥验签

SDK 客户端中的公钥参数应该配置支付宝公钥,不能填写商户自己生成的应用公钥。

2. 普通公钥模式与证书模式混用

支付宝 SDK 通常支持普通公钥模式和证书模式,两种初始化方式不能混用:

  • 普通公钥模式:配置应用私钥和支付宝公钥。
  • 证书模式:配置应用私钥证书、应用公钥证书和支付宝根证书。

如果应用后台采用证书模式,代码却按普通公钥模式初始化,或者情况正好相反,都可能导致签名或验签失败。

3. 沙箱与正式环境配置混用

支付请求和查询请求必须使用同一套环境配置,包括:

  • 网关地址
  • app_id
  • 应用私钥
  • 支付宝公钥或证书
  • 商户订单数据

例如,支付请求发往沙箱,查询请求却发往正式环境。即使两次使用的 out_trade_no 相同,正式环境中也没有这笔交易。如果两套环境的公钥也发生混用,还可能直接出现验签失败。

4. sign_type、字符集不一致

通常应统一使用:

sign_type = RSA2
charset   = UTF-8

如果后台上传的是与 RSA2 对应的应用公钥,代码却使用其他签名方式,或者生成签名时使用的字符集与发送请求时不一致,就可能出现签名验证异常。

5. 私钥格式或内容错误

常见问题包括:

  • 使用了公钥,而不是私钥。
  • 私钥与当前 app_id 不匹配。
  • 复制密钥时缺少字符或带入了多余空格。
  • PKCS#1、PKCS#8 格式不符合 SDK 的要求。
  • 已经在开放平台重新生成密钥,但程序仍在使用旧密钥。
  • 多套环境共用配置文件,程序读取了错误的配置。

6. 手工修改了待验签响应

如果没有使用 SDK,而是自行验签,必须以支付宝返回的原始响应内容为准。以下处理都可能破坏验签:

  • 先将 JSON 反序列化,再重新序列化后验签。
  • 修改字段顺序或转义字符。
  • 对响应重复执行 URL Decode。
  • 错误地将 + 转为空格。
  • 改变响应字符集。
  • 只截取部分响应,或者对错误的 JSON 节点验签。

应优先使用官方 SDK 完成请求、响应解析和验签,避免自行拼接签名原文。

7. 实际收到的不是支付宝 API JSON

如果网关地址错误,或者请求被代理、网络认证页面或异常跳转拦截,程序收到的可能是 HTML 页面。SDK 随后把这段内容当作支付宝响应解析,就可能产生大量解析和验签异常。

排查时应记录:

  • HTTP 状态码
  • 响应 Content-Type
  • 原始响应是否为 JSON
  • 实际请求的网关域名
  • 支付请求与查询请求使用的 app_id

日志中不要输出完整私钥、支付密码或其他敏感数据。

Java SDK 调用示例

下面以普通公钥模式为例。类名和方法可能因 SDK 版本不同而略有差异,具体应以项目实际使用的支付宝 Java SDK 为准。

import com.alipay.api.AlipayClient;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeQueryResponse;

AlipayClient alipayClient = new DefaultAlipayClient(
    "https://openapi.alipay.com/gateway.do",
    APP_ID,
    APP_PRIVATE_KEY,
    "json",
    "UTF-8",
    ALIPAY_PUBLIC_KEY,
    "RSA2"
);

AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();

AlipayTradeQueryModel model = new AlipayTradeQueryModel();
model.setOutTradeNo("ORDER_202609090001");
request.setBizModel(model);

AlipayTradeQueryResponse response = alipayClient.execute(request);

if (response.isSuccess()) {
    System.out.println("tradeNo: " + response.getTradeNo());
    System.out.println("outTradeNo: " + response.getOutTradeNo());
    System.out.println("tradeStatus: " + response.getTradeStatus());
} else {
    System.out.println("code: " + response.getCode());
    System.out.println("msg: " + response.getMsg());
    System.out.println("subCode: " + response.getSubCode());
    System.out.println("subMsg: " + response.getSubMsg());
}

生成手机网站支付表单时,可以这样写:

import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeWapPayRequest;

AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();

AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
model.setOutTradeNo("ORDER_202609090001");
model.setTotalAmount("99.00");
model.setSubject("测试商品");
model.setProductCode("QUICK_WAP_WAY");
model.setTimeoutExpress("15m");

request.setBizModel(model);
request.setReturnUrl(RETURN_URL);
request.setNotifyUrl(NOTIFY_URL);

String form = alipayClient.pageExecute(request).getBody();

生成的 form 需要返回给浏览器并提交。页面中可以包含自动提交脚本,但表单是否真正提交,仍取决于浏览器能否成功加载并执行该页面。

建议的排查顺序

  1. 确认浏览器是否真正提交了支付表单,而不只是服务端生成了 form 字符串。
  2. 确认浏览器是否已经跳转到支付宝收银台。
  3. 确认查询使用的 out_trade_no 与支付请求完全一致,注意前后空格、大小写和重复生成等问题。
  4. 确认支付与查询使用同一个 app_id 和同一个网关环境。
  5. 确认应用私钥与开放平台配置的应用公钥成对。
  6. 确认验签使用的是支付宝公钥,而不是应用公钥。
  7. 确认没有混用普通公钥模式和证书模式。
  8. 确认 sign_type 和 charset 配置一致。
  9. 分清异常属于”请求签名失败”、“响应验签失败”,还是支付宝返回的业务错误。
  10. 记录响应中的 code、msg、sub_code 和 sub_msg,不要只查看最外层异常堆栈。

注意事项

return_url 只是用户付款后的同步跳转地址,不能单独作为支付成功的判断依据。用户可能关闭浏览器页面,也可能因为其他原因没有完成跳转。

支付结果应主要以支付宝异步通知为准。接收异步通知时需要验签,并校验以下字段:

  • app_id
  • out_trade_no
  • total_amount
  • seller_id
  • trade_status

必要时可以再调用 alipay.trade.query,主动核对交易状态。

此外,out_trade_no 在商户系统中应保持唯一。同一订单需要重新发起支付时,应按业务规则复用原订单或生成新的订单号,不能只更换商品参数来覆盖支付宝侧已经存在的交易。

备注:内容仅供参考。