Android webview 中 H5 支付无法跳转至支付宝
结论
表单通常已经提交,问题多半出在支付宝收银台后续跳转到了 alipays://、intent:// 等 WebView 无法识别的协议。普通浏览器会唤起支付宝 App,而 Android WebView 需要宿主应用通过 WebViewClient 拦截并处理这些 URL。
也不建议直接替换整个 body.innerHTML。先确认后端返回了完整、有效的支付表单,再将表单插入独立容器并提交。
先确认问题发生在哪一步
通过 WebViewClient 记录跳转 URL,查看表单提交后的实际情况:
- 如果出现
alipays://或intent://,需要由 Android 端唤起支付宝。 - 如果出现
net::ERR_UNKNOWN_URL_SCHEME,说明 WebView 无法处理该自定义协议。 - 如果页面一直没有变化,应检查表单内容、JavaScript 配置和控制台错误。
- 如果已经进入支付宝网页收银台,但无法继续,应检查 WebView 的 Cookie、网络请求和 HTTPS 配置。
后端返回的表单至少要有有效的 action、method 和支付参数,例如:
<form id="alipayForm"
action="https://example-alipay-cashier-url"
method="post">
<input type="hidden" name="biz_content" value="...">
</form>
不要在客户端自行拼接或修改支付宝签名参数,否则可能导致验签失败。
改进 H5 的表单提交方式
原代码会替换整个页面的 body:
document.getElementsByTagName('body')[0].innerHTML =
res.data.benefitsInfo.form;
document.forms[0].submit();
这样可能破坏当前页面的 DOM、事件绑定和运行环境。可以改为创建独立容器:
const formHtml = res.data.benefitsInfo.form;
const container = document.createElement('div');
container.style.display = 'none';
container.innerHTML = formHtml;
document.body.appendChild(container);
const form = container.querySelector('form');
if (!form) {
throw new Error('支付接口未返回有效的 form');
}
form.submit();
如果返回内容包含 <script>,使用 innerHTML 插入后,脚本通常不会像正常加载页面时那样自动执行。不过,支付宝支付表单一般只需找到 form 并调用 submit(),不应依赖返回字符串中的脚本。
还可以检查:
console.log(form.action);
console.log(form.method);
console.log(new FormData(form));
生产日志中不要输出签名、订单信息或用户敏感数据。
Android 端处理支付宝协议
给 WebView 设置 WebViewClient,遇到支付宝协议时通过 Intent 打开:
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(
WebView view,
WebResourceRequest request
) {
return handleUrl(view, request.getUrl().toString());
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return handleUrl(view, url);
}
private boolean handleUrl(WebView view, String url) {
Uri uri = Uri.parse(url);
String scheme = uri.getScheme();
if ("http".equalsIgnoreCase(scheme)
|| "https".equalsIgnoreCase(scheme)) {
return false;
}
if ("alipays".equalsIgnoreCase(scheme)) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
view.getContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
// 未安装支付宝,可提示用户或跳转到可信的网页支付地址
}
return true;
}
if ("intent".equalsIgnoreCase(scheme)) {
try {
Intent intent = Intent.parseUri(
url,
Intent.URI_INTENT_SCHEME
);
PackageManager pm = view.getContext().getPackageManager();
if (intent.resolveActivity(pm) != null) {
view.getContext().startActivity(intent);
return true;
}
String fallbackUrl =
intent.getStringExtra("browser_fallback_url");
if (fallbackUrl != null
&& (fallbackUrl.startsWith("https://")
|| fallbackUrl.startsWith("http://"))) {
view.loadUrl(fallbackUrl);
}
} catch (Exception e) {
// 记录异常并给出支付失败提示
}
return true;
}
return true;
}
});
同时确认 WebView 已启用业务需要的 JavaScript:
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setDomStorageEnabled(true);
只在页面确实需要时启用 JavaScript,并确保 WebView 不会加载来源不可信的页面。
兼容未安装支付宝的情况
设备未安装支付宝时,startActivity() 可能抛出 ActivityNotFoundException。可以提示用户安装支付宝,也可以使用支付接口提供的网页收银台降级地址,或者将可信的 HTTPS 支付地址交给系统浏览器打开。
不要将 alipays:// 地址直接传给 webView.loadUrl(),WebView 本身无法处理这种协议。
还需要检查的配置
HTTPS 与混合内容
如果当前 H5 使用 HTTPS,但支付链路中包含 HTTP 资源,Android WebView 可能会拦截混合内容。应优先让整个支付链路都使用 HTTPS,不建议为了兼容而全局放开混合内容。
Cookie
部分网页收银台依赖 Cookie。如果日志显示页面已经进入 HTTPS 收银台,但会话丢失,可以检查 Cookie 是否可用:
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.setAcceptCookie(true);
cookieManager.setAcceptThirdPartyCookies(webView, true);
是否需要第三方 Cookie,要根据实际支付链路、网络请求和支付宝当前的接入文档判断。
WebView 版本
Chrome 62 对应的 WebView 内核已经较旧,可能有兼容性问题。可以更新设备上的 Android System WebView 或 Chrome 后重新测试,但不能只根据版本判断根因。应先查看表单提交后的 URL、WebView 控制台和网络错误。
安全注意事项
不要对所有非 HTTP URL 一律执行 Intent。WebView 页面可能构造恶意协议,因此需要限制处理范围:
- 只允许明确的协议,例如
alipays和业务确实需要的intent。 - 限制 WebView 可以加载的支付域名。
- 校验
browser_fallback_url,只允许可信的 HTTP/HTTPS 地址。 - 不要将后端返回的任意 HTML 插入页面,除非内容来自可信的支付接口。
- 订单金额、商户号和回调结果必须由服务端校验,不能根据 H5 的跳转结果判断支付成功。
建议先在 Android 端记录表单提交后的跳转 URL。如果已经出现 alipays:// 或 intent://,补上协议拦截和 Intent 唤起处理;如果没有发生任何跳转,再检查后端返回的表单,以及 H5 插入和提交表单的逻辑。
备注:内容仅供参考。
