PAYATHON 2026

安卓 WebView JS 桥调用支付宝支付取消后报错 6001

支付小周

结论

支付宝返回 6001,表示用户取消了本次支付。取消后无法再次唤起支付宝,通常不是支付宝禁止继续支付,而是 App 没有正确恢复支付状态。常见原因包括:

  • 防重复点击标志一直停留在“支付中”;
  • JS 桥回调没有结束,后续调用被拦截;
  • 支付任务复用了上一次的 PayTask、线程或订单参数;
  • WebView 返回前台后仍持有已经失效的 Activity;
  • 在主线程调用支付,造成生命周期或线程调度异常。

每次发起支付时,都应使用当前有效的 Activity、新建的支付任务,以及服务端重新生成的订单信息。无论支付成功、失败、取消还是发生异常,都要清理“支付中”状态,并将结果回传给 WebView。

6001 的含义

支付宝支付结果中的 resultStatus 常见取值如下:

  • 9000:支付成功;
  • 8000:支付结果确认中;
  • 6001:用户取消支付;
  • 6002:网络连接异常;
  • 其他状态:按照当前接入版本的支付宝 SDK 文档处理。

第一次取消支付时收到 6001 是正常的。需要排查的是,收到 6001 后,第二次调用为什么没有重新走完支付流程。

不要将 6001 视为不可恢复的错误,也不要因为用户取消了一次支付,就永久禁用支付按钮。

正确处理支付状态

如果项目通过变量防止重复支付,就要在所有结束路径中恢复该变量。仅在支付成功时重置状态,会导致用户取消后再也无法发起支付。

下面是一个 Java 示例:

public class AlipayBridge {

    private final Activity activity;
    private final WebView webView;
    private final AtomicBoolean paying = new AtomicBoolean(false);

    public AlipayBridge(Activity activity, WebView webView) {
        this.activity = activity;
        this.webView = webView;
    }

    @JavascriptInterface
    public void pay(String orderInfo) {
        if (orderInfo == null || orderInfo.trim().isEmpty()) {
            notifyJs("{\"resultStatus\":\"PARAM_ERROR\",\"memo\":\"orderInfo is empty\"}");
            return;
        }

        // 防止同一时刻重复发起,但不能在取消后一直保持 true。
        if (!paying.compareAndSet(false, true)) {
            notifyJs("{\"resultStatus\":\"PAYING\",\"memo\":\"payment is already running\"}");
            return;
        }

        new Thread(() -> {
            Map<String, String> result;

            try {
                if (activity.isFinishing() || activity.isDestroyed()) {
                    result = new HashMap<>();
                    result.put("resultStatus", "ACTIVITY_INVALID");
                    result.put("memo", "Activity is unavailable");
                } else {
                    // 每次调用重新创建 PayTask。
                    PayTask payTask = new PayTask(activity);
                    result = payTask.payV2(orderInfo, true);
                }
            } catch (Exception e) {
                result = new HashMap<>();
                result.put("resultStatus", "CLIENT_ERROR");
                result.put("memo", e.getMessage() == null ? "unknown error" : e.getMessage());
            } finally {
                // 成功、失败、取消和异常都必须恢复状态。
                paying.set(false);
            }

            JSONObject json = new JSONObject(result);
            notifyJs(json.toString());
        }).start();
    }

    private void notifyJs(String resultJson) {
        activity.runOnUiThread(() -> {
            if (activity.isFinishing() || activity.isDestroyed()) {
                return;
            }

            String script = "window.onAlipayResult(" +
                    JSONObject.quote(resultJson) +
                    ")";

            webView.evaluateJavascript(script, null);
        });
    }
}

注册 JS 桥时,要确保桥对象对应当前页面使用的 Activity 和 WebView:

webView.getSettings().setJavaScriptEnabled(true);
webView.addJavascriptInterface(
        new AlipayBridge(this, webView),
        "AndroidPay"
);

前端可以按下面的方式发起支付,并处理取消结果:

let paymentPending = false;

function startAlipay(orderInfo) {
  if (paymentPending) {
    return;
  }

  paymentPending = true;

  try {
    window.AndroidPay.pay(orderInfo);
  } catch (error) {
    paymentPending = false;
    console.error("Failed to invoke AndroidPay:", error);
  }
}

window.onAlipayResult = function (resultText) {
  paymentPending = false;

  let result;

  try {
    result = JSON.parse(resultText);
  } catch (error) {
    console.error("Invalid payment result:", resultText);
    return;
  }

  switch (result.resultStatus) {
    case "9000":
      // 不要只依赖客户端结果,继续向业务服务端查询订单状态。
      queryOrderStatus();
      break;

    case "6001":
      // 用户取消,只恢复页面状态,允许再次支付。
      showMessage("支付已取消");
      break;

    case "8000":
      showMessage("支付结果确认中");
      queryOrderStatus();
      break;

    default:
      showMessage(result.memo || "支付未完成");
      break;
  }
};

每次重试应重新获取订单信息

用户取消后再次发起支付时,建议让业务服务端重新创建或确认支付订单,并返回新的 orderInfo。不要长期缓存上一次使用的签名字符串,也不要只在前端修改订单号、金额等字段。

通常应按以下流程处理:

  1. H5 请求业务服务端创建支付订单;
  2. 服务端按照支付宝要求生成并签名 orderInfo;
  3. H5 通过 JS 桥将完整的 orderInfo 交给 Android;
  4. Android 在工作线程中调用 PayTask.payV2();
  5. Android 将支付结果回传给 H5;
  6. H5 或 App 向业务服务端查询订单的最终状态。

即使客户端返回 9000,也不能直接据此发货或记账。最终状态应以服务端验签后的异步通知或主动查询结果为准。

检查 Activity 和 WebView 生命周期

从支付宝返回 App 时,原来的 Activity 可能会经历 onPause() 和 onStop()。在内存紧张或使用特定启动模式时,它甚至可能被重建。如果 JS 桥保存的还是旧 Activity,第二次支付就可能无法正常启动。

需要确认以下条件成立:

!activity.isFinishing() && !activity.isDestroyed()

还要检查这些问题:

  • 不要将 Activity 长期保存在静态变量中;
  • Activity 重建后,要为新的 WebView 重新注册 JS 桥;
  • 不要让桥对象继续引用已经销毁的页面;
  • 如果页面会在 onResume() 中恢复按钮状态,注意不要错误地再次触发支付;
  • 检查 singleTask、singleTop 等启动模式是否改变了页面的恢复逻辑。

如果 WebView 页面可能重新加载,前端也要正确初始化 paymentPending。不要只依赖一段可能已经失效的 JavaScript 回调来恢复状态。

不要在主线程执行 payV2()

PayTask.payV2() 是同步调用,应在工作线程中执行。拿到结果后,再切换到主线程,通过 evaluateJavascript() 更新页面。

错误示例:

@JavascriptInterface
public void pay(String orderInfo) {
    PayTask payTask = new PayTask(activity);
    Map<String, String> result = payTask.payV2(orderInfo, true);
}

这种写法可能阻塞 WebView 或主线程,导致页面返回后的状态恢复和桥调用出现异常。

排查重点

如果修正状态清理逻辑后仍然无法再次唤起支付宝,可以记录以下信息。不要在日志中输出完整签名、用户隐私或敏感订单参数:

Log.d("Alipay", "pay invoked");
Log.d("Alipay", "activity destroyed: " + activity.isDestroyed());
Log.d("Alipay", "activity finishing: " + activity.isFinishing());
Log.d("Alipay", "paying flag: " + paying.get());
Log.d("Alipay", "resultStatus: " + result.get("resultStatus"));
Log.d("Alipay", "memo: " + result.get("memo"));

排查时重点确认:

  1. 第二次点击后,JS 是否确实调用了 AndroidPay.pay();
  2. 是否进入了原生桥方法;
  3. paying 等防重复标志是否已经恢复;
  4. 第二次调用是否拿到了有效的 orderInfo;
  5. 当前 Activity 是否仍然有效;
  6. 每次调用是否都重新创建并执行了 PayTask;
  7. 是否存在按钮未启用、Promise 未结束或遮罩层未关闭的问题;
  8. 支付宝 SDK 返回的完整结果中,resultStatus 是否确实为 6001。

如果第二次调用没有进入原生桥,问题通常在 WebView 或前端状态管理。进入原生桥后没有执行 payV2(),一般要检查防重逻辑或 Activity 状态拦截。如果 payV2() 已经执行,却立即返回 6001,则要继续检查是否重复使用了订单参数,以及当前的支付宝 SDK 接入方式是否符合对应版本的官方文档。

备注:内容仅供参考。