支付宝沙箱支付提示“操作已超时,请重新发起支付”
结论
支付宝收银台提示“操作已超时,请重新发起支付”,通常不是指 alipay-easysdk 的 HTTP 请求连接超时,而是当前订单或收银台会话已无法继续支付。
在 alipay-easysdk 2.2.3 和支付宝沙箱环境中,可以优先检查以下问题:
- 服务器时间不准,生成请求时的
timestamp与支付宝服务器时间相差过大。 - 支付页面生成后被缓存、重复使用,或隔了太久才打开。
out_trade_no重复,原订单已经关闭或完成,或者本次提交的参数与首次创建时不一致。timeout_express设置过短,进入收银台时订单已经过期。- 沙箱网关配置有误,例如在
gatewayHost中填写了完整 URL。 - 沙箱应用、密钥、签名方式和沙箱买家账号不配套。
- 支付宝沙箱服务暂时异常。
openapi-sandbox.dl.alipaydev.com 是沙箱环境使用的网关主机名之一,单凭这个配置无法判断问题是否来自 SDK。定位时还要查看支付接口的原始响应、订单查询结果和请求时间。
检查服务器时间
支付宝接口请求中包含时间戳。如果运行 Java 服务的主机、容器或虚拟机时间偏差较大,请求可能失效,收银台也可能认为当前支付会话已经过期。
需要确认:
- 操作系统时间和时区是否正确。
- 容器时间是否与宿主机一致。
- Java 进程所在环境是否启用了时间同步。
- 代码是否错误地手动设置了请求时间。
- 日志中的请求时间是否接近实际时间。
服务器使用 UTC 或 Asia/Shanghai 时区都可以,重要的是系统时间所表示的实际时刻必须准确。不要通过固定给时间戳加减 8 小时来处理时区。
正确配置沙箱网关
使用 Easy SDK 时,gatewayHost 通常只填写主机名,不要包含协议、路径或查询参数:
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
public class AlipayConfig {
public static void init() {
Config config = new Config();
config.protocol = "https";
config.gatewayHost = "openapi-sandbox.dl.alipaydev.com";
config.signType = "RSA2";
config.appId = "沙箱应用APPID";
config.merchantPrivateKey = "应用私钥";
config.alipayPublicKey = "沙箱支付宝公钥";
config.notifyUrl = "https://example.com/alipay/notify";
Factory.setOptions(config);
}
}
不要写成:
config.gatewayHost =
"https://openapi-sandbox.dl.alipaydev.com/gateway.do";
Easy SDK 会根据 protocol、gatewayHost 等配置拼接请求地址。如果把完整 URL 填入 gatewayHost,可能导致地址拼接错误。
还要检查下面这些配置是否都来自同一个沙箱应用:
appId是沙箱应用 APPID。merchantPrivateKey对应当前沙箱应用上传的应用公钥。alipayPublicKey是支付宝提供的沙箱支付宝公钥,不是应用公钥。- 签名方式与开放平台配置一致,通常是
RSA2。 - 配置中没有混入生产环境的 APPID、密钥或账号。
如果密钥或 APPID 有误,接口一般会返回明确的验签错误,而不只是显示收银台超时。因此,需要保留并检查接口的原始响应。
每次支付使用新的商户订单号
out_trade_no 是商户订单号,在商户系统中必须唯一。重复提交同一个订单号时,支付宝不会始终创建一笔新交易。
开发和测试时尤其要检查这些情况:
- 订单号被写成了固定值。
- 页面刷新后再次提交了同一个订单号。
- 原订单已经关闭,却仍用原订单号发起支付。
- 第二次提交同一订单号时修改了金额、标题等参数。
- 前端保存并重复打开了之前生成的支付表单。
测试时可以生成新的订单号:
import java.util.UUID;
String outTradeNo = UUID.randomUUID()
.toString()
.replace("-", "");
生产环境不能简单地用随机值代替订单管理。唯一订单号应由业务系统生成并持久化。
检查订单有效期
如果设置了 timeout_express,不要把有效期设得太短。调试期间可以先设为相对充足的值,例如 30m:
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
public class AlipayPagePayment {
public static String createPayment() throws Exception {
String outTradeNo = "ORDER_202609090001";
AlipayTradePagePayResponse response = Factory.Payment.Page()
.optional("timeout_express", "30m")
.pay(
"沙箱支付测试订单",
outTradeNo,
"0.01",
"https://example.com/pay/result"
);
if (response == null) {
throw new IllegalStateException("支付宝支付接口未返回响应");
}
if (!"10000".equals(response.getCode())) {
throw new IllegalStateException(
"创建支付失败,code=" + response.getCode()
+ ", msg=" + response.getMsg()
+ ", subCode=" + response.getSubCode()
+ ", subMsg=" + response.getSubMsg()
);
}
return response.getBody();
}
}
不同 Easy SDK 版本中的模型方法可能不同,应以项目实际依赖提供的方法为准。不要为了让示例通过编译而忽略真实的响应字段。
timeout_express 也不是 Java HTTP 请求的超时时间,它表示交易可以支付的时长,例如:
30m
1h
1d
具体支持哪些格式,应以当前支付宝接口文档为准。
不要缓存支付表单或支付地址
电脑网站支付接口通常会返回一段自动提交的 HTML 表单。浏览器应在表单创建后立即使用,不要长期缓存。
例如:
@GetMapping(value = "/pay", produces = "text/html;charset=UTF-8")
@ResponseBody
public String pay() throws Exception {
String outTradeNo = generateNewOrderNo();
AlipayTradePagePayResponse response = Factory.Payment.Page()
.optional("timeout_express", "30m")
.pay(
"沙箱支付测试",
outTradeNo,
"0.01",
"https://example.com/pay/result"
);
return response.getBody();
}
排查时需要确认:
- CDN、网关或浏览器没有缓存
/pay的响应。 - 后端没有保存支付表单并反复返回。
- 前端没有再次打开旧标签页中的支付页面。
- 创建订单到跳转支付宝之间没有长时间排队或异步等待。
- 返回的 HTML 没有被模板引擎转义。
可以在支付入口的响应中加入禁止缓存的响应头:
@GetMapping(value = "/pay", produces = "text/html;charset=UTF-8")
public ResponseEntity<String> pay() throws Exception {
String form = createPaymentForm();
return ResponseEntity.ok()
.header("Cache-Control", "no-store, no-cache, must-revalidate")
.header("Pragma", "no-cache")
.body(form);
}
查询订单状态
看到超时提示后,不要只看浏览器页面来判断支付结果。应该使用商户订单号主动查询交易状态:
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.payment.common.models.AlipayTradeQueryResponse;
AlipayTradeQueryResponse queryResponse =
Factory.Payment.Common().query(outTradeNo);
System.out.println("code=" + queryResponse.getCode());
System.out.println("msg=" + queryResponse.getMsg());
System.out.println("subCode=" + queryResponse.getSubCode());
System.out.println("subMsg=" + queryResponse.getSubMsg());
System.out.println("tradeStatus=" + queryResponse.getTradeStatus());
可以根据查询结果继续处理:
- 查不到订单:检查创建支付接口是否真正成功,以及 APPID、网关和订单号是否一致。
WAIT_BUYER_PAY:订单已经创建,但尚未支付,可以重新打开新生成的收银台页面。TRADE_CLOSED:订单已经关闭,需要生成新的out_trade_no后重新发起支付。TRADE_SUCCESS或TRADE_FINISHED:支付已经完成,不要再次发起支付。
具体状态字段和可用方法应以当前 SDK 模型及支付宝接口文档为准。
保留完整接口响应
创建支付时,至少记录以下信息:
out_trade_no
请求发起时间
网关主机名
app_id
code
msg
sub_code
sub_msg
支付宝返回的 request_id 或 trace 信息
应用私钥、完整签名值、买家密码等敏感内容不要写入日志。
如果 code 不是 10000,先根据接口返回的 sub_code 和 sub_msg 处理问题。如果接口返回成功,订单查询结果也是 WAIT_BUYER_PAY,但新生成的支付页面仍然立即超时,问题才更可能出在沙箱收银台本身或沙箱账号状态上。
检查沙箱账号和浏览器状态
沙箱支付必须使用沙箱环境提供的买家账号,不能直接用真实支付宝账号完成交易。
可以按以下步骤进行一次干净测试:
- 退出浏览器中登录的真实支付宝账号。
- 使用无痕窗口打开支付页面。
- 生成全新的
out_trade_no。 - 创建订单后立即进入收银台。
- 使用沙箱买家账号登录并支付。
- 支付失败后立即查询订单状态,同时保存完整响应。
旧 Cookie、多个支付宝账号同时登录,或者重复打开旧支付页面,都可能影响排查结果。
关于 SDK 版本
alipay-easysdk 2.2.3 的版本较旧,确实是需要留意的风险,但不能只凭版本号就认定它导致了超时。升级前,先确认项目使用的 Java 版本、依赖冲突和现有接口兼容性。
还可以检查依赖树,看看是否同时引入了多套支付宝 SDK、JSON 库或 HTTP 客户端版本。如果基础配置、服务器时间和订单状态都正常,再使用支付宝官方当前推荐版本进行对照测试。升级后仍要重新验证签名、网关、回调和支付查询,不能只确认是否能跳转到收银台。
推荐排查顺序
按以下顺序检查通常更快:
- 校准服务器、容器和 Java 运行环境的系统时间。
- 确认
gatewayHost中只填写openapi-sandbox.dl.alipaydev.com。 - 确认 APPID、应用私钥和支付宝公钥都属于同一个沙箱应用。
- 使用新的
out_trade_no,并暂时把timeout_express设为30m。 - 创建支付后立即打开返回的表单,不要使用历史页面或缓存内容。
- 记录接口返回的
code、sub_code、sub_msg和订单号。 - 通过交易查询接口确认订单是否已经创建、关闭或完成支付。
- 使用无痕窗口和沙箱买家账号重新测试。
- 如果以上项目均正常,再考虑 SDK 兼容问题或支付宝沙箱暂时异常。
如果准备把同一套代码切换到生产网关验证,必须同时更换生产 APPID、生产密钥和对应的账号配置,不能只修改网关地址。即使沙箱测试正常,生产环境中的签名、回调验签和订单状态校验也不能省略。
备注:内容仅供参考。
