手机网站支付提示订单参数异常,如何解决?
结论
出现“订单参数异常,请重新下单后再发起付款”,一般说明支付平台没有通过订单参数校验。此时需要重新生成订单,并检查必填参数、金额格式、订单号、时间、签名和请求编码。
不要反复提交原支付链接。旧链接可能已经过期,参数可能被修改,对应订单也可能已经关闭。具体原因要结合支付接口的同步响应、异步通知和平台日志中的错误码判断,不能只看页面提示。
常见原因
订单参数缺失或格式错误
手机网站支付要求业务参数完整,格式也要符合接口规范。以支付宝手机网站支付接口 alipay.trade.wap.pay 为例,常见参数包括:
out_trade_no:商户订单号,在同一商户下应保持唯一。total_amount:订单金额,应使用合法的十进制金额格式。subject:订单标题,不能为空。product_code:手机网站支付通常填写QUICK_WAP_WAY。
接口的必填项和字段长度限制可能不同,请以当前接入接口的官方文档为准。
金额不合法
常见情况有:
- 金额为空、为零或小于零。
- 金额带有千位分隔符,例如
1,000.00。 - 使用全角小数点或混入其他非数字字符。
- 浮点数计算造成精度误差。
- 前端展示的金额与服务端提交的金额不同。
金额应由服务端计算,并使用十进制类型处理。累计商品金额时,不要直接使用二进制浮点数。
商户订单号重复或订单状态异常
如果 out_trade_no 已对应一笔支付成功、已关闭或参数不同的订单,再次使用可能产生冲突。重新发起付款前,应先查询原订单状态:
- 已支付:不要再次创建支付。
- 支付中:让用户等待,或继续查询支付结果。
- 已关闭或已失效:生成新的商户订单号。
- 未创建成功:修正参数后重新发起。
不要只在前端刷新页面,然后重复提交同一订单。
签名或字符编码不一致
签名问题有时会直接显示为验签错误,有时只表现为支付页面无法识别订单。排查时应检查:
- 参与签名的参数是否与实际发送的参数完全一致。
- 参数是否在签名完成后又被修改。
- 应用私钥和支付平台公钥是否正确。
sign_type是否与 SDK 配置一致。- 字符编码是否统一为 UTF-8。
- 参数是否被重复进行 URL 编码。
- JSON 序列化结果是否发生二次转义。
建议通过官方 SDK 生成请求,避免手动拼接签名字符串和支付 URL。
时间或请求已过期
订单设置了超时时间,或者支付链接经过缓存、转发后隔了很久才打开,都可能导致支付平台拒绝继续付款。服务器时间严重不准,也可能使请求无法通过时间校验。
重新下单时,需要重新生成请求参数,不要复用之前生成的完整支付表单或 URL。
应用与商户配置不匹配
以下配置必须属于同一套环境和账号:
app_id- 商户账号或签约主体
- 应用私钥
- 支付平台公钥
- 网关地址
- 沙箱或正式环境
- 已签约的支付产品
例如,将正式环境的 app_id 用于沙箱网关,或者误用了另一应用的密钥,都会造成请求异常。
排查步骤
1. 获取接口的原始响应
先记录支付请求返回的这些字段:
code
msg
sub_code
sub_msg
out_trade_no
trade_no
页面上的中文提示通常经过统一处理,能提供的信息不多。定位具体字段时,sub_code 和 sub_msg 更有用。
日志中不要写入完整私钥、用户敏感信息或未经脱敏的支付凭证。
2. 打印服务端实际提交的参数
应检查服务端最终发送的值,而不只是前端传入的值。至少要确认:
method
app_id
charset
sign_type
timestamp
biz_content.out_trade_no
biz_content.total_amount
biz_content.subject
biz_content.product_code
使用其他支付渠道时,应按照相应接口文档核对必填字段。
3. 校验订单数据
服务端应在调用支付接口前校验订单数据,例如:
import java.math.BigDecimal;
public void validateOrder(String outTradeNo, BigDecimal totalAmount, String subject) {
if (outTradeNo == null || outTradeNo.isBlank()) {
throw new IllegalArgumentException("out_trade_no 不能为空");
}
if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("total_amount 必须大于 0");
}
if (subject == null || subject.isBlank()) {
throw new IllegalArgumentException("subject 不能为空");
}
}
提交金额时,可以统一保留两位小数:
String amount = totalAmount
.setScale(2, java.math.RoundingMode.HALF_UP)
.toPlainString();
金额是否必须保留两位小数,以及允许的金额范围和舍入规则,都应以所接支付接口的要求为准。
4. 使用官方 SDK 构造请求
下面的示例只展示支付宝手机网站支付中常见的参数结构。SDK 类名和初始化方式可能随版本变化,请以当前版本的官方 SDK 文档为准:
AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
Map<String, Object> bizContent = new HashMap<>();
bizContent.put("out_trade_no", orderNo);
bizContent.put("total_amount", amount);
bizContent.put("subject", subject);
bizContent.put("product_code", "QUICK_WAP_WAY");
request.setBizContent(objectMapper.writeValueAsString(bizContent));
AlipayTradeWapPayResponse response = alipayClient.pageExecute(request);
if (!response.isSuccess()) {
log.error(
"创建支付失败,code={}, msg={}, subCode={}, subMsg={}, orderNo={}",
response.getCode(),
response.getMsg(),
response.getSubCode(),
response.getSubMsg(),
orderNo
);
throw new IllegalStateException("创建支付失败");
}
return response.getBody();
接口返回的表单内容应直接输出到浏览器,不要在前端拆开后重新拼接参数。
5. 检查 URL 与编码
如果必须手动拼接跳转地址,应避免重复编码。每个参数只能按照接口规范编码一次,例如:
String encodedReturnUrl = URLEncoder.encode(returnUrl, StandardCharsets.UTF_8);
如果官方 SDK 已经完成参数编码,就不要再对整个请求结果调用 URLEncoder.encode(),否则可能破坏签名或回跳地址。
同时检查反向代理、网关和前端框架是否改写了 +、%、&、= 等字符。
6. 重新生成订单并发起支付
确认原订单尚未支付后,创建新的商户订单号,并重新生成支付请求和跳转页面。不要使用浏览器历史记录、缓存页面或旧二维码中的支付地址。
注意事项
- 支付金额、订单号和商品信息应以服务端数据为准,不能直接采用前端传值。
- 同一个
out_trade_no不能对应不同金额或不同商品。 - 支付结果应以服务端异步通知和主动查询为准,不能只根据前端跳转结果判断。
- 收到异步通知后必须验签,同时校验商户号、应用 ID、订单号、金额和订单状态。
- 排查生产环境问题时,应保留请求时间、订单号、接口错误码和平台请求流水号,以便与支付平台技术支持核对。
- 如果参数均符合文档要求但仍然报错,请携带脱敏后的请求参数、
code、sub_code、请求时间和订单号联系支付平台技术支持。不要提供应用私钥或完整签名密钥。
备注:内容仅供参考。
