PAYATHON 2026

App支付唤起支付宝出现6001错误如何排查?

支付小周

结论

支付宝 App 支付返回 6001,通常说明用户取消了支付,例如点击返回键、关闭收银台,或支付过程中 App 被中断。6001 不是 alipay.trade.app.pay 的服务端参数错误码,不能据此认定某个业务参数校验失败。

这份请求参数混入了许多示例字段,还存在过期时间、支付渠道配置冲突和编码过于复杂等问题。建议先把 biz_content 精简到 App 支付所需的基本字段,重新生成签名和订单字符串,然后确认客户端将订单字符串原样传给支付宝 SDK。

请求中需要重点检查的问题

1. time_expire 已经过期

请求时间是:

timestamp=2021-08-23 19:25:20

订单的绝对过期时间却是:

time_expire=2016-12-31 10:05:00

如果这些内容不是脱敏示例,而是实际提交的参数,那么订单创建时就已经过期。应删除该字段,或将其改为未来时间。

一般只需保留相对超时时间:

"timeout_express": "90m"

如果必须使用 time_expire,需要在每次创建订单时动态生成,不能直接照搬示例日期。

2. 支付渠道配置互相冲突

请求中同时出现了:

"enable_pay_channels": "pcredit,moneyFund,debitCardExpress",
"disable_pay_channels": "pcredit,moneyFund,debitCardExpress",
"specified_channel": "pcredit"

同一批渠道既被启用,又被禁用,配置本身互相冲突。排查时应先删除这些字段。确实有业务需求时,再根据签约能力选择一种渠道限制方式。

3. biz_content 包含大量非必要参数

请求还携带了花呗分期、行业回流、周期扣款签约、子商户和外部用户信息等字段,例如:

hb_fq_num
industry_reflux_info
agreement_sign_params
period_rule_params
sub_merchant
ext_user_info

这些字段不是普通 App 支付的必需参数,其中一部分还要求签约特定产品,或只适用于特定业务场景。直接复制完整示例,容易出现参数不适用、嵌套 JSON 转义错误或权限校验失败。

排查时先保留以下字段即可:

{
  "out_trade_no": "唯一订单号",
  "total_amount": "9.00",
  "subject": "订单标题",
  "product_code": "QUICK_MSECURITY_PAY",
  "timeout_express": "30m"
}

确认最小请求能够正常支付后,再逐项添加可选字段。

4. 字符集和 URL 编码需要保持一致

当前请求使用:

charset=GBK

与此同时,biz_content 中包含百分号编码后的中文、嵌套 JSON 和多层转义。签名前后的字符集或编码次数只要不一致,就可能导致验签失败。

建议统一改用 UTF-8:

charset=UTF-8

订单字符串应由支付宝服务端 SDK 生成。客户端拿到订单字符串后,必须原样传给支付 SDK,不要执行以下操作:

  • 不要再次进行 URL 编码;
  • 不要先做 URL 解码,再重新编码;
  • 不要手动把 + 替换为空格;
  • 不要重新排列参数;
  • 不要修改 biz_content;
  • 不要在客户端重新拼接签名参数。

URL 编码后的 %2B、+ 和空格含义不同。签名值中的 + 如果经过错误的表单解码,可能会变成空格。

5. 示例字段之间存在数据不一致

订单总金额是:

"total_amount": "9.00"

商品明细中的单价却是:

"price": 2000,
"quantity": 1

如果提交 goods_detail,商品金额与订单总金额应满足当前接口及业务场景的校验要求。排查时可以先删除整个 goods_detail,避免无关字段影响结果。

还要确认手机号、身份证号、子商户号和服务商 PID 等信息均来自真实业务配置,而不是文档中的示例值。

推荐的服务端调用方式

Java 服务端可以通过官方 SDK 生成 App 支付订单字符串。私钥只能保存在服务端,不能放入 App。

AlipayClient alipayClient = new DefaultAlipayClient(
    "https://openapi.alipay.com/gateway.do",
    APP_ID,
    APP_PRIVATE_KEY,
    "json",
    "UTF-8",
    ALIPAY_PUBLIC_KEY,
    "RSA2"
);

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

JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", createUniqueOrderNo());
bizContent.put("total_amount", "9.00");
bizContent.put("subject", "测试订单");
bizContent.put("product_code", "QUICK_MSECURITY_PAY");
bizContent.put("timeout_express", "30m");

request.setBizContent(bizContent.toJSONString());

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

String orderInfo = response.getBody();

response.getBody() 是需要交给客户端的完整订单字符串。不要只传 biz_content,也不要在服务端或客户端再次处理这个字符串。

Android 客户端调用示例

Runnable payRunnable = () -> {
    PayTask payTask = new PayTask(activity);

    // orderInfo 必须是服务端 sdkExecute 返回的完整字符串
    Map<String, String> result = payTask.payV2(orderInfo, true);

    String resultStatus = result.get("resultStatus");
    String memo = result.get("memo");
    String resultData = result.get("result");

    runOnUiThread(() -> {
        if ("9000".equals(resultStatus)) {
            // 这里只表示客户端支付流程返回成功。
            // 最终支付状态仍需由服务端异步通知或主动查询确认。
        } else if ("6001".equals(resultStatus)) {
            // 用户取消或支付流程被中断
        } else {
            // 记录 resultStatus、memo 和服务端订单号,继续排查
        }
    });
};

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

如果客户端每次都立即返回 6001,需要检查:

  1. 是否成功调起了支付宝收银台;
  2. orderInfo 是否为空、被截断或经过 JSON 转义;
  3. 是否将订单字符串放入 URL 后又编码了一次;
  4. Activity 是否在调用后立即关闭或重建;
  5. 是否重复发起支付,覆盖了上一次支付流程;
  6. 是否在支付宝返回前主动关闭了支付页面;
  7. App 的生命周期、路由或登录逻辑是否中断了支付宝回跳。

建议的排查顺序

第一步:使用最小参数重新下单

只提交:

{
  "out_trade_no": "TP202609090001",
  "total_amount": "9.00",
  "subject": "测试订单",
  "product_code": "QUICK_MSECURITY_PAY",
  "timeout_express": "30m"
}

每次测试都必须使用新的 out_trade_no。

第二步:统一使用 UTF-8

服务端 SDK、业务 JSON、HTTP 响应和日志全部使用 UTF-8,然后重新生成签名。修改任何参数后,都不能继续使用原来的 sign。

第三步:对比传输前后的订单字符串

分别记录:

  • 服务端 sdkExecute() 的返回值;
  • App 收到的字符串;
  • 传入 payV2() 前的字符串。

这三处内容应完全一致。记录日志时,需要隐藏签名和用户证件信息等敏感数据。

第四步:记录完整的客户端结果

至少记录以下字段:

resultStatus
memo
result
out_trade_no

不要只记录 6001。如果同时出现 4000、6002 或服务端 sub_code,这些信息通常更有助于判断是参数、网络还是签名出了问题。

第五步:由服务端查询订单状态

客户端返回 6001,不代表订单一定没有支付。网络中断或回跳失败也可能造成客户端结果与实际交易状态不一致。服务端应根据 out_trade_no 调用交易查询接口,或等待支付宝异步通知,以确认最终状态。

注意事项

  • 6001 通常表示支付流程被取消,不能将它视为明确的参数校验错误。
  • 不能直接复用问题中的 sign。任何字符、时间或业务字段发生变化后,都要重新签名。
  • 支付宝私钥必须保存在服务端。
  • 验证异步通知时,应使用支付宝公钥,并校验 app_id、订单号、金额和商户身份。
  • 客户端显示支付成功不能代替服务端确认。发货或记账应以验签通过的异步通知或主动查询结果为准。
  • 如果最小参数可以正常支付,再逐个恢复花呗分期、渠道限制或扩展参数,以确定具体是哪个字段引发了问题。

备注:内容仅供参考。