支付宝沙箱支付失败并提示选择其他方式支付
结论
这是支付宝收银台返回的通用失败提示。仅凭”支付失败,请选择其他方式完成付款”,无法确定具体原因。
如果配置、网络和请求量都正常,而且已经成功进入沙箱收银台,可以重点排查以下问题:
- 使用了真实支付宝账号、错误的沙箱买家账号,或沙箱买家账号状态异常。
- 沙箱买家余额不足,或登录密码、支付密码输入错误。
- 重复使用同一个
out_trade_no,订单已经支付或关闭,或订单参数发生了变化。 - 支付金额、商品信息、超时时间等业务参数不合法。
- 支付产品与调用场景不匹配,例如混用电脑网站支付、手机网站支付和 App 支付。
- 请求签名、应用公钥证书、支付宝公钥或网关所属环境不一致。
- 支付宝沙箱暂时异常,或交易被风控拦截。
排查时不要反复点击付款。先查询订单状态,再检查沙箱账号和请求参数,最后生成新的订单号重新测试。
优先检查沙箱买家账号
支付宝沙箱不能直接使用普通生产账号付款,必须使用沙箱控制台提供的买家账号。请逐项确认:
- 登录收银台的是沙箱买家账号,不是开发者自己的真实支付宝账号。
- 登录时使用了该沙箱账号对应的登录密码。
- 付款时输入的是支付密码,不是登录密码。
- 沙箱买家账户有足够的余额。
- 账号没有因连续输错密码、频繁登录或异常操作而暂时不可用。
如果页面支持扫码,扫码所用的账号也必须符合沙箱环境要求。哪些登录方式可用,应以当前沙箱控制台和收银台页面为准,不能默认生产支付宝客户端一定支持沙箱支付。
可以换一个沙箱买家账号,并使用新的商户订单号重新下单。如果更换账号后能够正常付款,问题一般出在原买家账号,而不是商户应用配置。
检查商户订单号是否被重复使用
out_trade_no 是商户侧订单号,在同一商户应用下应当保持唯一。以下情况很容易造成重复:
- 刷新页面后,再次提交同一个订单号。
- 第一次支付失败后修改了金额,却继续使用原订单号。
- 原订单已经支付成功,但前端没有收到跳转结果,于是再次发起支付。
- 原订单已经关闭或超时,仍用原订单号调起收银台。
重新测试时要生成全新的订单号,例如:
String outTradeNo =
"SBX" +
java.time.LocalDateTime.now()
.format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
不要只使用精确到秒的时间戳。存在并发请求时,还应加入随机数、数据库序列或分布式 ID,以免订单号碰撞。
先查询订单状态
前端显示失败,并不代表订单一定没有支付成功。网络跳转、页面关闭或同步回调异常,都可能让页面状态与支付宝交易状态不一致。
使用 alipay.trade.query,根据 out_trade_no 查询最终状态:
AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
request.setBizContent(bizContent.toJSONString());
AlipayTradeQueryResponse response = alipayClient.execute(request);
System.out.println("code=" + response.getCode());
System.out.println("subCode=" + response.getSubCode());
System.out.println("subMsg=" + response.getSubMsg());
System.out.println("tradeStatus=" + response.getTradeStatus());
System.out.println("body=" + response.getBody());
重点查看:
codesub_codesub_msgtrade_status- 完整响应
body
常见的交易状态有:
WAIT_BUYER_PAY:订单已创建,正在等待买家付款。TRADE_SUCCESS:支付成功。TRADE_FINISHED:交易结束。TRADE_CLOSED:交易已关闭。
如果查不到订单,请检查交易是否成功创建,以及查询时使用的应用、网关和商户订单号是否正确。
具体错误应以接口实际返回的 sub_code 和 sub_msg 为准。收银台上的通用提示不能代替接口错误信息。
检查支付请求参数
签名和网络正常,也不代表业务参数没有问题。付款阶段失败时,至少要检查以下字段:
{
"out_trade_no": "SBX20260909143000123",
"total_amount": "0.01",
"subject": "沙箱支付测试订单",
"product_code": "FAST_INSTANT_TRADE_PAY"
}
检查时注意:
total_amount应是十进制字符串,不能是负数、零或格式异常的值。- 金额计算不要直接使用
double,否则可能出现精度误差。 subject不能为空,也不能包含异常控制字符。timeout_express设置得太短时,用户进入收银台后订单可能已经超时。product_code必须与所用支付产品一致。return_url用于页面跳转,notify_url用于服务器异步通知,两者用途不同。
金额建议使用 BigDecimal 处理:
BigDecimal amount = new BigDecimal("0.01")
.setScale(2, RoundingMode.HALF_UP);
bizContent.put("total_amount", amount.toPlainString());
不要这样构造金额:
double amount = 0.1 + 0.2;
bizContent.put("total_amount", String.valueOf(amount));
这种写法可能得到 0.30000000000000004 一类不符合预期的值。
确认支付产品与接口匹配
不同支付场景对应不同的接口和 product_code,不要混用。例如:
- 电脑网站支付通常使用
alipay.trade.page.pay。 - 手机网站支付通常使用
alipay.trade.wap.pay。 - App 支付通常使用
alipay.trade.app.pay。
电脑网站支付的示例:
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", outTradeNo);
bizContent.put("total_amount", "0.01");
bizContent.put("subject", "沙箱支付测试订单");
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(bizContent.toJSONString());
AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
String form = response.getBody();
如果前端运行在手机浏览器中,却调用了电脑网站支付接口,或者把 App 支付返回的订单字符串当成网页表单处理,收银台流程可能出现异常。
排除生产环境与沙箱环境混用
沙箱测试使用的整套配置都必须来自同一个沙箱应用,包括:
- 沙箱应用的
app_id - 商户应用私钥
- 与私钥对应的应用公钥或应用公钥证书
- 沙箱支付宝公钥或支付宝公钥证书
- 沙箱网关地址
- 沙箱买家账号
常见错误是 app_id 使用了沙箱值,支付宝公钥却来自生产应用。也可能在创建订单时访问沙箱网关,查询订单时又访问生产网关。
初始化客户端时,最好集中管理环境配置,不要在代码各处零散拼接:
AlipayClient alipayClient = new DefaultAlipayClient(
gatewayUrl,
appId,
merchantPrivateKey,
"json",
"UTF-8",
alipayPublicKey,
"RSA2"
);
这些变量必须属于同一个环境。网关地址、密钥模式和 SDK 初始化方式会因接入方案而异,应根据当前应用采用的普通公钥模式或证书模式配置。
检查签名、时间和编码
能够进入收银台,通常说明签名已经通过初步校验,但仍需检查以下项目:
- 签名算法是否符合应用配置要求,例如
RSA2。 - 私钥是否与上传到对应沙箱应用的公钥配对。
- 字符集是否统一使用
UTF-8。 - 服务器时间是否存在明显偏差。
- 请求参数是否在签名完成后又被修改。
- URL 是否被重复编码。
- 网关返回内容是否被代理服务器或前端框架截断。
日志不能只记录”调用失败”,还应保存脱敏后的请求参数和完整响应:
if (!response.isSuccess()) {
log.error(
"Alipay request failed, outTradeNo={}, code={}, msg={}, subCode={}, subMsg={}, body={}",
outTradeNo,
response.getCode(),
response.getMsg(),
response.getSubCode(),
response.getSubMsg(),
response.getBody()
);
}
不要在日志中记录应用私钥、支付密码、完整身份证号或其他敏感信息。
判断是否为沙箱临时异常
同时出现以下情况时,才更可能是沙箱平台的临时问题:
- 同一套代码此前能够正常支付。
- 多个沙箱买家账号都支付失败。
- 每次测试都使用新的
out_trade_no。 - 金额、产品接口和应用配置已经核对无误。
alipay.trade.query没有返回明确的业务错误。- 生产环境或以往的联调结果正常。
- 失败集中发生在某段时间,稍后重试又恢复正常。
遇到这种情况,应保留以下信息,供后续定位:
- 失败时间,精确到秒。
app_id,但不要提供私钥。out_trade_no- 支付宝交易号
trade_no,如果已经生成。 - 接口响应中的
code、sub_code、sub_msg - 脱敏后的完整响应体。
- 收银台错误页面截图。
- 使用的支付接口和 SDK 版本。
沙箱的稳定性和风控行为不保证与生产环境完全一致。间歇性失败不一定说明业务代码有问题,但在查询交易状态和错误码之前,也不能直接把问题归因于沙箱。
推荐排查顺序
可以按下面的顺序排查:
- 使用沙箱控制台提供的买家账号登录。
- 检查余额、登录密码和支付密码。
- 生成新的
out_trade_no,创建一笔小额订单进行测试。 - 调用
alipay.trade.query查询原订单状态。 - 记录接口返回的
code、sub_code、sub_msg和完整响应。 - 核对支付接口、
product_code、金额及超时时间。 - 确认
app_id、密钥、支付宝公钥和网关都属于沙箱环境。 - 更换另一个沙箱买家账号进行交叉验证。
- 间隔一段时间后重新测试,排除沙箱临时异常。
- 如果问题仍未解决,携带脱敏后的订单信息和错误响应,向支付宝技术支持反馈。
支付结果应以服务端查询和异步通知为准,不能只看同步跳转页面或收银台提示。
备注:内容仅供参考。
