为什么沙箱提示订单信息有误并建议重新下单付款?
结论
沙箱提示“订单信息有错误,建议重新下单后付款”,通常不是余额不足,而是订单参数、签名配置或沙箱账号未通过校验。建议重点检查:
- 是否使用了沙箱网关、沙箱应用的
app_id和对应密钥 - 支付参数是否完整、格式是否正确
out_trade_no是否重复或已经使用- 签名算法、公私钥和字符集是否一致
- 付款账号是否为平台提供的沙箱买家账号
- 订单金额、商品信息及支付产品参数是否合法
排查时,先生成一个从未使用过的商户订单号,再结合完整日志核对实际发送的请求参数。
常见原因
沙箱与正式环境配置混用
沙箱应用和正式应用通常使用不同的应用标识、密钥、账号和网关地址。如果请求发往沙箱网关,传入的却是正式环境的 app_id 或密钥,平台将无法正确验证请求。
以支付宝开放平台的常见配置为例,沙箱请求中的各项配置应来自同一套沙箱环境:
String serverUrl = "沙箱网关地址";
String appId = "沙箱应用的 APPID";
String privateKey = "与沙箱应用对应的应用私钥";
String alipayPublicKey = "沙箱环境中配置或提供的支付宝公钥";
String charset = "UTF-8";
String signType = "RSA2";
具体网关地址和密钥配置应以当前支付平台控制台显示的信息为准,不要直接照搬其他项目或正式环境的配置。
商户订单号重复
out_trade_no 是商户侧订单的唯一标识。订单号提交过支付请求后,平台可能已经记录了对应的金额、商品信息或订单状态。
如果再次使用同一个 out_trade_no,同时修改了金额或其他重要参数,平台可能会判定订单信息冲突。排查时,可以先生成一个全新的订单号:
String outTradeNo =
"TEST_" + System.currentTimeMillis();
实际项目还需要保证并发场景下订单号不会重复,可以使用数据库序列、雪花算法或经过处理的 UUID。
不要只刷新支付页面,然后反复提交原订单。如果原订单已经关闭、支付失败或参数发生变化,应在业务系统中创建新订单,并为其分配新的 out_trade_no。
必填参数缺失或格式错误
不同支付接口要求的参数并不相同,常见参数如下:
{
"out_trade_no": "TEST_202609150001",
"total_amount": "0.01",
"subject": "沙箱测试订单",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
排查时应确认:
out_trade_no是否为空、过长或含有不允许的字符total_amount是否为合法的金额字符串subject是否为空或超过长度限制product_code是否与当前调用的支付产品一致- JSON 字段名是否拼写正确
- 请求参数是否经过了重复编码
- 订单金额是否处于沙箱允许的范围内
金额应使用十进制定点类型处理,不要直接用 double 参与业务计算:
BigDecimal amount = new BigDecimal("0.01");
String totalAmount = amount.setScale(2, RoundingMode.HALF_UP).toPlainString();
签名配置不一致
签名校验出现异常时,前端不一定直接显示“验签失败”,有时只会提示笼统的订单错误。常见问题包括:
- 应用私钥和应用公钥不属于同一密钥对
- 将支付宝公钥误当成应用公钥
- 沙箱环境使用了正式环境的密钥
- 配置中使用
RSA2,实际却按其他算法签名 - 签名前后的字符集不一致
- 私钥内容缺失、换行损坏或格式不符合 SDK 要求
- 参数签名后又被修改
初始化客户端时,各项配置必须与控制台一致:
AlipayClient alipayClient = new DefaultAlipayClient(
serverUrl,
appId,
privateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
这段代码只展示典型结构。构造函数及其参数顺序可能随 SDK 版本变化,应以项目当前使用的 SDK 文档为准。
使用了非沙箱买家账号
有些支付沙箱只允许使用平台生成的测试买家账号。如果使用真实账号、商户账号,或其他沙箱应用下的账号登录,付款可能无法完成。
可以在沙箱控制台检查:
- 买家账号是否属于当前沙箱环境
- 是否混淆了登录密码和支付密码
- 测试账号当前是否可用
- 买家账号是否有足够的沙箱余额
不要向沙箱账号充值真实资金,也不要在正式支付页面输入沙箱账号。
支付接口与场景不匹配
电脑网站支付、手机网站支付、App 支付和当面付通常使用不同的接口和参数。如果调用电脑网站支付接口,却传入其他支付产品的 product_code,订单校验也可能失败。
例如,支付宝电脑网站支付常见的调用形式如下:
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
request.setBizContent("""
{
"out_trade_no": "TEST_202609150001",
"total_amount": "0.01",
"subject": "沙箱测试订单",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
""");
AlipayTradePagePayResponse response =
alipayClient.pageExecute(request);
if (response.isSuccess()) {
String paymentForm = response.getBody();
} else {
System.err.println("code: " + response.getCode());
System.err.println("subCode: " + response.getSubCode());
System.err.println("subMsg: " + response.getSubMsg());
}
不要只根据浏览器页面上的提示判断问题。SDK 返回的 code、sub_code 和 sub_msg 通常更适合定位原因。
建议的排查顺序
1. 固定为一套沙箱配置
确认网关、app_id、应用私钥、平台公钥和沙箱账号都来自同一个沙箱应用,同时检查正式环境配置是否通过环境变量或配置中心覆盖了沙箱配置。
启动时可以输出不敏感的配置:
log.info(
"payment env=sandbox, appId={}, gateway={}, signType={}",
appId,
serverUrl,
signType
);
私钥、支付密码和完整签名不得写入日志。
2. 使用全新的订单号
创建新的业务订单并生成新的 out_trade_no,不要修改旧订单的金额后再次提交。
还需要检查本地数据库中是否存在重复订单:
SELECT out_trade_no, amount, status
FROM payment_order
WHERE out_trade_no = 'TEST_202609150001';
如果查到多条记录,说明商户侧的订单号生成逻辑或唯一性约束存在问题。可以根据业务设计增加唯一索引:
CREATE UNIQUE INDEX uk_payment_order_out_trade_no
ON payment_order (out_trade_no);
变更数据库结构前,应先确认现有数据中没有重复值。
3. 记录平台返回的完整错误信息
日志中至少应包含:
- 接口名称
- 商户订单号
codesub_codesub_msg- 平台返回的请求标识
- 请求时间
- 实际使用的环境
如果字段中含有密钥、令牌、身份证号、手机号或支付凭证,必须先脱敏再记录。
4. 对照接口文档逐项检查参数
除了检查业务代码构造的对象,还要核对 SDK 最终发送的参数,包括:
- 参数是否在序列化过程中变成
null - 金额是否被转换成科学计数法
- 中文内容是否出现乱码
notify_url和return_url是否为合法 URLbiz_content是否为有效 JSON- 参数是否在完成签名后又被修改
5. 查询原订单状态
如果平台提供订单查询接口,应先查询旧订单的状态。已经支付、关闭或等待支付的订单,不一定允许使用新参数重新创建。
确认旧订单无法继续使用后,再创建新的商户订单。不要为了绕过报错不断生成订单,否则商户系统中可能留下大量状态不明的记录。
注意事项
- 沙箱环境偶尔可能不稳定,但在判断为平台问题前,应先排除参数、签名和订单号方面的问题。
- 浏览器显示的错误文案通常比较笼统,具体原因应以接口响应和服务端日志为准。
- 不能只根据同步跳转结果判断支付是否成功,应以异步通知或主动查询结果为准。
- 收到异步通知后必须验签,并校验
app_id、订单号、金额和商户身份等重要字段。 - 不要在前端保存应用私钥,也不要将私钥、支付密码或完整请求签名提交到公开渠道。
- 不同支付平台和支付产品的字段要求并不完全一致。如果错误发生在支付宝以外的平台,可以沿用上述排查思路,但具体判断应以对应平台的沙箱文档和实际错误码为准。
备注:内容仅供参考。
