安卓 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。不要长期缓存上一次使用的签名字符串,也不要只在前端修改订单号、金额等字段。
通常应按以下流程处理:
- H5 请求业务服务端创建支付订单;
- 服务端按照支付宝要求生成并签名
orderInfo; - H5 通过 JS 桥将完整的
orderInfo交给 Android; - Android 在工作线程中调用
PayTask.payV2(); - Android 将支付结果回传给 H5;
- 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"));
排查时重点确认:
- 第二次点击后,JS 是否确实调用了
AndroidPay.pay(); - 是否进入了原生桥方法;
paying等防重复标志是否已经恢复;- 第二次调用是否拿到了有效的
orderInfo; - 当前
Activity是否仍然有效; - 每次调用是否都重新创建并执行了
PayTask; - 是否存在按钮未启用、Promise 未结束或遮罩层未关闭的问题;
- 支付宝 SDK 返回的完整结果中,
resultStatus是否确实为6001。
如果第二次调用没有进入原生桥,问题通常在 WebView 或前端状态管理。进入原生桥后没有执行 payV2(),一般要检查防重逻辑或 Activity 状态拦截。如果 payV2() 已经执行,却立即返回 6001,则要继续检查是否重复使用了订单参数,以及当前的支付宝 SDK 接入方式是否符合对应版本的官方文档。
备注:内容仅供参考。
