PAYATHON 2026

支付宝沙箱手机网站支付提示“操作已超时”如何排查?

支付阿杰

结论

这个提示通常说明支付请求或订单已经超过有效时间。题目中的请求明确设置了:

timestamp=2024-05-13 15:27:48
time_expire=2024-05-13 21:27:12

这笔订单只能在 2024-05-13 21:27:12 之前支付。超过这个时间后再次打开原支付链接,支付宝沙箱 App 显示”操作已超时”是正常现象。

如果尚在有效期内就出现这个提示,需要检查服务器时间和时区、支付链接是否被缓存或重复使用,以及沙箱环境与账号是否匹配。仅凭 App 页面上的提示无法确定具体原因,还应结合 alipay.trade.query 的查询结果和支付宝接口响应日志判断。

原因分析

1. time_expire 已过期

请求中的 biz_content 解码后包含:

{
  "out_trade_no": "kp2024051300004",
  "total_amount": "10.00",
  "subject": "测试商品",
  "product_code": "QUICK_WAP_WAY",
  "time_expire": "2024-05-13 21:27:12"
}

time_expire 表示订单的绝对失效时间,并不是从当前时间开始计算的有效时长。支付宝侧的当前时间一旦超过这个值,原支付请求就不能继续使用。

重新打开浏览器或再次唤起支付宝 App 都无法恢复这笔订单。需要使用新的商户订单号重新发起支付。

2. 重复使用旧支付链接

alipay.trade.wap.pay 的请求地址不适合保存为长期有效的静态链接。常见情况包括:

  • 前端缓存了之前生成的支付地址;
  • 用户返回页面后再次打开同一地址;
  • 服务端缓存了支付页面;
  • 二维码或跳转按钮一直指向旧请求;
  • 支付失败后仍使用原来的 out_trade_no 和 time_expire 重试。

重新发起支付时,服务端应实时生成请求,并先检查原订单状态。只刷新旧链接,或者直接修改浏览器地址中的时间参数,都不能解决问题。参与签名的参数发生变化后,原签名也会失效。

3. 服务器时间或时区不正确

如果代码根据本机时间计算 time_expire,而服务器系统时间、容器时区或应用时区配置有误,生成的失效时间在支付宝看来可能已经过期。

例如,业务原本想创建一笔”30 分钟后过期”的订单,但服务器实际使用 UTC,代码又按北京时间格式化,最终时间可能相差 8 小时。

排查时应同时记录:

服务器当前时间
服务器时区
请求 timestamp
订单 time_expire
实际唤起支付宝 App 的时间

在中国大陆业务场景中,通常应按照支付宝接口要求传递 yyyy-MM-dd HH:mm:ss 格式的时间,并确保它所代表的时区与应用配置一致。

4. 沙箱环境或账号不匹配

还要确认整条支付链路都在沙箱环境中:

  • 网关使用 https://openapi-sandbox.dl.alipaydev.com/gateway.do;
  • 手机安装并登录的是沙箱支付宝 App;
  • 付款时使用沙箱买家账号;
  • app_id、应用私钥、支付宝公钥或应用证书属于同一个沙箱应用;
  • 请求中没有混入正式环境的账号、证书或网关。

环境不一致时,不一定每次都显示”操作已超时”,也可能出现签名、权限或账号异常。具体原因应以网关响应和交易查询结果为准。

排查步骤

第一步:不要复用题目中的链接

该链接包含固定的 timestamp、time_expire、out_trade_no 和签名,现在继续使用它已经没有测试意义。请创建新的商户订单号,并实时生成支付请求。

例如:

out_trade_no=kp2024051300005

商户订单号必须在当前应用和商户维度下保持唯一。

第二步:暂时移除 time_expire 做对照测试

可以暂时不传 time_expire,重新创建订单后立即支付,以确认问题是否与失效时间有关。

如果移除后能够正常唤起并完成付款,问题基本集中在时间计算、时区配置或旧链接复用上。查明原因后,再根据业务需要恢复这个参数。

第三步:查询支付宝侧的交易状态

使用相同的沙箱应用配置调用 alipay.trade.query,通过 out_trade_no 查询订单:

{
  "out_trade_no": "kp2024051300004"
}

重点检查以下结果:

  • 查询不到交易:支付请求可能没有成功进入支付宝交易流程;
  • WAIT_BUYER_PAY:订单已经创建,正在等待付款;
  • TRADE_CLOSED:订单已经关闭,可能是因为超时;
  • TRADE_SUCCESS:支付已经成功,不应再创建相同订单;
  • 接口返回签名、权限或应用错误:继续检查沙箱配置。

交易状态和错误字段应以接口的实际返回结果为准,不能只根据 App 页面上的文案判断。

第四步:检查服务端日志

生成支付请求时,建议记录以下信息,但不要记录应用私钥等敏感数据:

app_id
out_trade_no
timestamp
time_expire
网关地址
SDK 返回的完整响应或表单
支付宝错误码
支付宝错误描述

还要确认 timestamp 和 time_expire 是本次请求实时生成的,而不是来自缓存、数据库中的旧字段或页面隐藏域。

第五步:检查 App 跳转方式

服务端调用 alipay.trade.wap.pay 后,通常应把 SDK 生成的支付表单或跳转内容返回给浏览器,再由浏览器提交到支付宝网关。

不要对已经签名的请求地址执行以下操作:

  • 手动修改查询参数;
  • 再次对整个地址进行 URL 编码;
  • 重复编码 biz_content 或 sign;
  • 在网关地址前拼接代理跳转参数;
  • 长时间缓存支付表单。

这些操作可能破坏签名,或者导致支付宝实际收到的参数与签名原文不一致。

C# 示例

下面的示例会重新生成订单,并把失效时间设置为当前时间之后 30 分钟。客户端的具体初始化方式,应与项目使用的支付宝 .NET SDK、密钥模式或证书模式保持一致。

using System;
using Aop.Api.Request;
using Newtonsoft.Json;

var outTradeNo = $"kp{DateTime.Now:yyyyMMddHHmmssfff}";
var expireTime = DateTime.Now.AddMinutes(30);

var bizContent = new
{
    out_trade_no = outTradeNo,
    total_amount = "10.00",
    subject = "测试商品",
    product_code = "QUICK_WAP_WAY",
    time_expire = expireTime.ToString("yyyy-MM-dd HH:mm:ss")
};

var request = new AlipayTradeWapPayRequest
{
    BizContent = JsonConvert.SerializeObject(bizContent),
    ReturnUrl = "https://example.com/alipay/return",
    NotifyUrl = "https://example.com/alipay/notify"
};

// client 必须使用沙箱网关,并加载当前沙箱应用对应的密钥或证书。
var response = client.PageExecute(request);

// 将 response.Body 作为支付表单返回给浏览器。
string paymentForm = response.Body;

如果部署环境可能使用 UTC,建议显式生成北京时间,不要依赖服务器的默认时区:

var chinaTimeZone = TimeZoneInfo.FindSystemTimeZoneById(
    OperatingSystem.IsWindows()
        ? "China Standard Time"
        : "Asia/Shanghai"
);

var now = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, chinaTimeZone);
var expireTime = now.AddMinutes(30);

string timestamp = now.ToString("yyyy-MM-dd HH:mm:ss");
string timeExpire = expireTime.ToString("yyyy-MM-dd HH:mm:ss");

查询订单状态的示意代码如下:

using Aop.Api.Request;
using Newtonsoft.Json;

var queryRequest = new AlipayTradeQueryRequest
{
    BizContent = JsonConvert.SerializeObject(new
    {
        out_trade_no = "kp2024051300004"
    })
};

var queryResponse = client.Execute(queryRequest);

Console.WriteLine(queryResponse.Body);

注意事项

  • timestamp 是接口请求时间,time_expire 是订单失效时间,两者含义不同。
  • time_expire 应晚于实际发起支付的时间,并为用户留出足够的操作时间。
  • 订单过期后,应使用新的 out_trade_no 重新创建,不能靠修改旧链接恢复。
  • 修改任何参与签名的参数后,都必须重新计算签名。
  • return_url 只负责支付完成后的页面跳转,不能作为判断支付成功的唯一依据。
  • 商户系统应对 notify_url 的异步通知进行验签和处理,必要时主动调用 alipay.trade.query 核实交易状态。
  • 示例中的 notify_url 使用了 HTTP 和 IP 地址。这通常不是 App 显示超时的直接原因,但异步通知地址必须确保支付宝能够通过公网访问。生产环境建议使用有效的 HTTPS 域名。
  • 手机时间异常也可能影响页面表现,但排查时应优先核对服务端生成的时间参数和支付宝侧的交易状态。

备注:内容仅供参考。