支付宝沙箱支付提示“订单信息有错误”是什么原因?
结论
支付宝提示”订单信息有错误,建议重新下单后付款”,说明订单校验没有通过。单凭这个页面,还无法确定具体原因。
项目搁置一段时间后再次测试,常见原因包括:
- 复用了以前的
out_trade_no,而原订单已经支付、关闭或状态异常。 - 订单创建时间太早,或者
timeout_express设置的有效期已经结束。 - 沙箱环境中的
app_id、密钥、证书、网关地址或沙箱账号发生了变化。 - 请求参数不合法,例如金额格式错误、必填参数为空或签名失败。
- 正式环境和沙箱环境的配置混用。
排查时不要只看支付页面。先检查支付宝接口返回的 code、sub_code 和 sub_msg,这些字段通常能说明更具体的失败原因。
优先检查订单号
支付宝商户订单号 out_trade_no 在商户系统中必须唯一。重新运行旧项目时,如果代码写死了订单号,就可能再次提交一个已经存在的订单。
下面这种写法很容易造成订单号重复:
model.setOutTradeNo("202401010001");
测试时应为每笔订单生成新的订单号:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ThreadLocalRandom;
public class OrderNoGenerator {
public static String generate() {
String time = LocalDateTime.now()
.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
int random = ThreadLocalRandom.current().nextInt(1000, 10000);
return time + random;
}
}
创建支付请求时使用新订单号:
AlipayTradePagePayModel model = new AlipayTradePagePayModel();
model.setOutTradeNo(OrderNoGenerator.generate());
model.setTotalAmount("0.01");
model.setSubject("沙箱支付测试");
model.setProductCode("FAST_INSTANT_TRADE_PAY");
model.setTimeoutExpress("15m");
正式项目中的订单号应由业务系统统一生成并持久化,不能只依赖随机数,也不能在用户每次刷新页面时都生成新订单。
检查订单是否已经过期
如果项目保存了以前创建的订单,再次打开支付页面时,该订单可能已经超过允许支付的时间。
需要检查:
- 是否复用了数据库中的旧订单。
timeout_express是否设置得太短。- 服务器时间和时区是否正确。
- 订单是否已被支付宝关闭。
- 本地订单状态是否与支付宝侧一致。
遇到旧订单时,应先调用订单查询接口确认状态,不要直接再次发起支付:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
request.setBizContent("""
{
"out_trade_no": "实际商户订单号"
}
""");
AlipayTradeQueryResponse response = alipayClient.execute(request);
System.out.println("code=" + response.getCode());
System.out.println("msg=" + response.getMsg());
System.out.println("subCode=" + response.getSubCode());
System.out.println("subMsg=" + response.getSubMsg());
System.out.println("tradeStatus=" + response.getTradeStatus());
如果订单已经关闭,应创建新的业务订单,或按照业务规则生成新的支付订单,不能继续使用原来的 out_trade_no。
核对沙箱配置
沙箱环境中的配置必须成套使用,不能只替换其中一部分。至少要核对以下内容:
app_id是否属于当前沙箱应用。- 应用私钥是否与上传到支付宝的应用公钥匹配。
- 使用证书模式时,应用证书、支付宝公钥证书和支付宝根证书是否配套。
- 网关地址是否为沙箱网关。
- 签名算法是否与应用配置一致,通常为
RSA2。 - 支付账号是否为沙箱提供的买家账号。
- 后端是否误读了正式环境的配置文件或环境变量。
普通公钥模式的配置示例如下:
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi-sandbox.dl.alipaydev.com/gateway.do",
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
不同的 SDK 版本或接入模式可能使用不同的构造方法。如果采用证书模式,应使用对应的证书客户端配置,不能把支付宝公钥字符串和证书模式混在一起使用。
检查请求参数
发起网页支付时,重点检查这些参数:
| 参数 | 检查内容 |
|---|---|
out_trade_no | 非空,长度符合接口要求,并且未被其他订单使用 |
total_amount | 使用字符串形式的合法金额,如 "0.01",不能为零或负数 |
subject | 非空,避免包含异常控制字符 |
product_code | 网页支付通常使用 FAST_INSTANT_TRADE_PAY |
timeout_express | 格式正确,如 15m、1h |
notify_url | 支付宝服务器可以访问,不能使用仅本机可见的地址 |
return_url | URL 格式正确 |
charset | 与签名及客户端配置一致 |
sign_type | 与应用密钥配置一致 |
金额最好使用 BigDecimal 处理,以免出现浮点数精度问题:
import java.math.BigDecimal;
import java.math.RoundingMode;
BigDecimal amount = new BigDecimal("0.01")
.setScale(2, RoundingMode.UNNECESSARY);
model.setTotalAmount(amount.toPlainString());
不要用下面这种方式计算支付金额:
double amount = 0.1 + 0.2;
model.setTotalAmount(String.valueOf(amount));
double 可能得到类似 0.30000000000000004 的结果,导致金额或业务校验异常。
输出完整的支付宝响应
使用 alipay.trade.page.pay 时,应记录响应中的诊断字段:
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
System.out.println("code=" + response.getCode());
System.out.println("msg=" + response.getMsg());
System.out.println("subCode=" + response.getSubCode());
System.out.println("subMsg=" + response.getSubMsg());
System.out.println("body=" + response.getBody());
if (!response.isSuccess()) {
throw new IllegalStateException(
"支付宝下单失败,code=" + response.getCode()
+ ", subCode=" + response.getSubCode()
+ ", subMsg=" + response.getSubMsg()
);
}
常见问题包括:
- 签名验证失败。
- 商户订单号重复。
- 参数缺失或格式错误。
- 应用没有接口权限。
- 应用、账号或密钥不匹配。
- 订单已经关闭,无法再次支付。
错误的具体含义应以接口实际返回的 sub_code 和 sub_msg 为准,不能只根据前端提示判断。
建议的排查顺序
- 为本次测试生成全新的
out_trade_no。 - 将支付金额固定为
"0.01",排除金额计算问题。 - 确认程序没有读取以前保存的订单或过期支付链接。
- 核对沙箱的
app_id、网关、密钥或证书,以及买家账号。 - 打印接口返回的
code、sub_code和sub_msg。 - 调用订单查询接口,确认旧订单是否已支付或关闭。
- 根据具体错误码检查签名、权限和请求参数。
如果换用新订单号后恢复正常,通常说明旧订单号被重复使用,或者原订单已经失效。如果新订单仍然报错,应重点检查沙箱配置和接口响应中的具体错误信息。
备注:内容仅供参考。
