PAYATHON 2026

沙箱环境使用orderStr唤起APP支付提示系统繁忙

支付阿杰

结论

成功生成 orderStr,只说明服务端完成了支付参数的组装和签名,不代表支付宝沙箱已经受理订单。遇到“系统繁忙,请稍后再试”,通常需要检查以下问题:

  1. 客户端使用的是正式版支付宝,而非沙箱配套客户端。
  2. app_id、应用私钥、支付宝公钥或证书并非来自同一个沙箱应用。
  3. 服务端生成 orderStr 后又做了 URL 编码、解码、拼接或转义,导致签名失效。
  4. biz_content 中的订单号、金额、产品码等参数不符合 APP 支付要求。
  5. 客户端没有将完整的 orderStr 原样传给支付宝支付 SDK。
  6. 沙箱 APP 支付链路或测试工具出现异常。

电脑网站支付正常,只能说明网页支付的相关配置基本可用,无法证明 APP 支付的客户端、签名和产品权限配置正确。建议先通过官方服务端 SDK 生成订单串,再使用真实移动端支付 SDK 验证。API Explorer 的链路测试结果不能作为判断 APP 支付是否可用的唯一依据。

第一步:确认客户端和环境一致

服务端请求、客户端钱包和登录账号必须全部使用沙箱环境。逐项确认:

  • app_id 是沙箱应用的 App ID。
  • 签名使用该沙箱应用对应的应用私钥。
  • 验签使用沙箱控制台提供的支付宝公钥,或同一应用配置下的证书。
  • 手机端安装并使用沙箱要求的支付宝客户端。
  • 登录沙箱买家账号,而不是正式支付宝账号。
  • 服务端没有混用正式环境的网关、密钥、证书或应用配置。

使用开放平台 SDK 时,网关应明确指向沙箱地址:

https://openapi-sandbox.dl.alipaydev.com/gateway.do

不能只切换网关,却继续使用正式应用的 app_id、密钥或证书。

第二步:检查 APP 支付的服务端调用方式

APP 支付应由服务端调用 alipay.trade.app.pay,再通过 SDK 的 sdkExecute 一类方法生成客户端所需的订单串。生成后,应将 orderStr 作为普通字符串直接交给移动端,服务端不要再修改其内容。

以下是 Java SDK 的典型写法。不同 SDK 版本或证书模式使用的类名和构造参数可能略有差异:

AlipayClient alipayClient = new DefaultAlipayClient(
    "https://openapi-sandbox.dl.alipaydev.com/gateway.do",
    sandboxAppId,
    appPrivateKey,
    "json",
    "UTF-8",
    alipayPublicKey,
    "RSA2"
);

AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
request.setNotifyUrl("https://example.com/alipay/notify");

AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setOutTradeNo(outTradeNo);
model.setTotalAmount("0.01");
model.setSubject("沙箱APP支付测试");
model.setProductCode("QUICK_MSECURITY_PAY");

request.setBizModel(model);

AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);

if (!response.isSuccess()) {
    throw new IllegalStateException(
        "生成支付订单失败,code=" + response.getCode()
        + ", subCode=" + response.getSubCode()
        + ", msg=" + response.getMsg()
        + ", subMsg=" + response.getSubMsg()
    );
}

String orderStr = response.getBody();

还需要检查:

  • product_code 使用 QUICK_MSECURITY_PAY。
  • out_trade_no 在商户侧保持唯一,每次重复测试时都重新生成。
  • total_amount 是字符串形式的合法金额,例如 "0.01"。
  • subject 不为空。
  • charset、sign_type 与实际签名配置一致。
  • notify_url 是支付宝可以访问的公网 HTTPS 地址。不过,异步通知地址不可达通常不会阻止客户端拉起支付页。
  • 完整返回 response.getBody(),不能只返回其中的 biz_content 或 sign。

如果应用使用证书模式,就应采用对应的证书客户端初始化方式,不能混用公钥模式和证书模式的参数。

第三步:保证 orderStr 原样传输

orderStr 已经包含编码和签名后的参数。生成后再次调用 URLEncoder.encode(),或者由客户端解析后重新拼接,都会带来问题。

错误示例:

String orderStr = response.getBody();
String encodedOrderStr = URLEncoder.encode(orderStr, StandardCharsets.UTF_8);
return encodedOrderStr;

应直接返回原始订单串:

String orderStr = response.getBody();
return orderStr;

通过 JSON 接口下发时,可以这样封装:

{
  "orderStr": "app_id=...&biz_content=...&charset=UTF-8&..."
}

JSON 序列化时出现转义不一定有问题,但客户端取出字段后,必须还原为原始字符串。尤其要检查以下字符是否被替换、丢失或重复编码:

&
=
%
+
/

可以分别在服务端生成订单串后、客户端调用支付 SDK 前记录:

  • 字符串长度。
  • SHA-256 摘要。
  • 脱敏后的首尾内容。

如果两端记录的长度或摘要不同,说明订单串在传输过程中发生了变化。

第四步:检查移动端 SDK 调用

Android 端应将完整的 orderStr 直接传给支付 SDK:

final String orderInfo = orderStr;

Runnable payRunnable = () -> {
    PayTask alipay = new PayTask(activity);
    Map<String, String> result = alipay.payV2(orderInfo, true);

    activity.runOnUiThread(() -> {
        String resultStatus = result.get("resultStatus");
        String memo = result.get("memo");
        String resultData = result.get("result");

        Log.d("Alipay", "resultStatus=" + resultStatus);
        Log.d("Alipay", "memo=" + memo);
        Log.d("Alipay", "result=" + resultData);
    });
};

Thread payThread = new Thread(payRunnable);
payThread.start();

除了记录支付页面上的“系统繁忙”,还要保存 SDK 返回的以下字段:

  • resultStatus
  • memo
  • result

定位客户端支付结果时,resultStatus 更有参考价值。页面提示往往是统一的错误文案,无法直接判断问题来自签名、账号、网络还是沙箱服务。

同时确认项目集成的是官方支付 SDK,并排查这些情况:

  • SDK 版本过旧。
  • 项目中混入多个不同版本的支付 SDK。
  • 客户端截断了较长的 orderStr。
  • 前端框架或跨端插件对字符串进行了二次编码。
  • 使用 WebView 打开 orderStr,而不是调用原生支付 SDK。
  • Android 包配置或 iOS 回跳配置不完整。

第五步:核对密钥和签名配置

密钥错误是“可以得到订单串,但拉起后失败”的常见原因。sdkExecute 能在本地完成参数组装和签名,因此即使私钥配置错误,也可能生成一个格式完整的 orderStr。

需要逐项确认:

  1. 应用私钥确实与当前沙箱应用上传的应用公钥对应。
  2. alipayPublicKey 填写的是支付宝公钥,而不是应用公钥。
  3. 沙箱配置中没有复制正式环境的密钥。
  4. sign_type 设置为 RSA2 时,代码实际使用的也是 RSA2 签名。
  5. 私钥格式符合当前 SDK 的要求,没有缺行、多余空格或复制不完整的问题。
  6. 如果近期更换过密钥,运行中的服务已经重新加载配置。
  7. 多实例部署时,所有实例使用相同且正确的沙箱配置。

排查时可以创建一个全新的 out_trade_no,再由服务端生成新的 orderStr。不要反复使用历史订单串,否则订单状态、订单号重复或订单超时可能干扰判断。

第六步:正确看待 API Explorer 和链路测试

API Explorer 更适合验证接口参数和服务端请求,不能代替真实的移动端支付 SDK 环境。对于 alipay.trade.app.pay,即使工具已经返回响应,仍需将完整订单串交给移动端 SDK,才能验证后续支付流程。

如果 API Explorer 显示“没有数据”,或链路测试一直没有响应,可以按下面的方法区分问题:

  • 在浏览器开发者工具中确认请求是否真正发出。
  • 查看 HTTP 状态码和接口原始响应。
  • 检查响应中是否有 code、sub_code、msg、sub_msg。
  • 更换全新的订单号后再次测试。
  • 确认 API Explorer 当前选择的是沙箱应用和沙箱环境。
  • 关闭可能拦截请求的浏览器扩展、代理或企业网络限制,然后重试。

如果 sdkExecute 能返回订单串,但 API Explorer 或沙箱链路测试页面始终没有反应,同时电脑网站支付测试正常,不能直接认定是业务代码错误。链路测试页面本身也可能存在兼容性问题或临时服务异常。

推荐的最小化排查方法

搭建一条绕过业务前端和中间网关的最小测试链路:

  1. 使用官方服务端 SDK。
  2. 配置确认无误的沙箱 app_id 和对应密钥。
  3. 将金额固定为 "0.01"。
  4. 每次生成新的 out_trade_no。
  5. 直接获取 response.getBody()。
  6. 通过 USB 调试或本地测试接口,把订单串交给原生移动端 Demo。
  7. 使用沙箱客户端和沙箱买家账号支付。
  8. 保存服务端响应,以及移动端返回的 resultStatus、memo。

如果这条最小链路可以完成支付,问题通常出在原业务系统的订单串传输、客户端封装或环境配置中。如果最小链路仍然提示系统繁忙,而且已经确认应用、密钥、账号和客户端都属于同一个沙箱环境,那么沙箱侧异常的可能性更大。

仍无法解决时需要提交的信息

向支付宝开放平台技术支持反馈时,不要只提交“系统繁忙”的截图。建议同时提供:

  • 沙箱 app_id。
  • 接口名称 alipay.trade.app.pay。
  • 问题发生的准确时间和时区。
  • out_trade_no。
  • SDK 返回的 code、sub_code、msg、sub_msg。
  • 移动端返回的 resultStatus 和 memo。
  • 支付页面的错误截图。
  • 服务端使用的语言及 SDK 版本。
  • 手机系统、支付 SDK 版本和沙箱客户端版本。
  • 当前使用的是公钥模式还是证书模式。
  • 脱敏后的完整请求参数。

不要提交应用私钥、完整的证书私钥或未经脱敏的敏感数据。

注意事项

  • 生成 orderStr 不代表支付宝已经创建交易。
  • APP 支付和电脑网站支付使用不同的产品链路。网页支付正常,仍不能排除 APP 支付的配置问题。
  • 不要手动修改、排序或二次编码 SDK 生成的 orderStr。
  • 支付结果应以服务端异步通知和主动查询结果为准,不能只看客户端显示。
  • 沙箱环境可能临时不稳定。确认配置无误后,应记录问题发生时间、订单号和返回信息,再提交平台排查,不要不断使用同一个订单重复测试。

备注:内容仅供参考。