PAYATHON 2026

Android WebView 如何获取第三方支付按钮的 onclick 回调?

支付阿杰

结论

不要直接监听第三方按钮的 onclick。在 Android WebView 中,更稳妥的方式是监听页面跳转,识别支付网关约定的回调 URL,再通知原生应用。

如果页面和按钮代码都由你控制,可以使用 addJavascriptInterface() 建立 JavaScript 与 Android 之间的通信。对于无法修改的第三方支付页面,不建议通过注入 JavaScript 劫持按钮事件。这种做法容易受到跨域限制、页面结构调整和安全策略的影响,还可能违反支付网关的接入要求。

推荐方案:拦截支付回调 URL

先在支付网关后台或下单参数中配置返回地址,例如:

https://example.com/payment/result

再通过 WebViewClient.shouldOverrideUrlLoading() 判断当前页面是否跳转到了该地址:

class PaymentActivity : AppCompatActivity() {

    private lateinit var webView: WebView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        webView = WebView(this)
        setContentView(webView)

        webView.settings.javaScriptEnabled = true
        webView.settings.domStorageEnabled = true

        webView.webViewClient = object : WebViewClient() {

            override fun shouldOverrideUrlLoading(
                view: WebView,
                request: WebResourceRequest
            ): Boolean {
                // 避免把 iframe、图片等子资源误判为支付回调
                if (!request.isForMainFrame) {
                    return false
                }

                return handlePaymentUrl(request.url)
            }

            @Suppress("DEPRECATION")
            override fun shouldOverrideUrlLoading(
                view: WebView,
                url: String
            ): Boolean {
                return handlePaymentUrl(Uri.parse(url))
            }
        }

        webView.loadUrl("https://payment.example.com/checkout")
    }

    private fun handlePaymentUrl(uri: Uri): Boolean {
        val isPaymentCallback =
            uri.scheme == "https" &&
            uri.host == "example.com" &&
            uri.path == "/payment/result"

        if (!isPaymentCallback) {
            return false
        }

        val orderId = uri.getQueryParameter("order_id")
        val status = uri.getQueryParameter("status")

        onPaymentPageReturned(orderId, status)
        return true
    }

    private fun onPaymentPageReturned(orderId: String?, status: String?) {
        // 这里只表示 WebView 已返回,不代表支付结果可信。
        // 应使用 orderId 请求业务服务器查询最终支付状态。
    }
}

返回 true 表示应用已经处理该 URL,WebView 不再继续加载。普通网页地址应返回 false。

如果支付网关支持自定义 URL Scheme,也可以配置类似下面的地址:

myapp://payment/callback?order_id=123

对应的拦截逻辑可以写成:

private fun handlePaymentUrl(uri: Uri): Boolean {
    if (uri.scheme != "myapp" ||
        uri.host != "payment" ||
        uri.path != "/callback"
    ) {
        return false
    }

    val orderId = uri.getQueryParameter("order_id")
    onPaymentPageReturned(orderId, null)
    return true
}

回调地址能采用什么格式,取决于支付网关是否允许配置返回 URL,Android 端不能单方面决定。

页面由自己控制时:使用 JavaScript Bridge

如果 Submit 按钮在你自己的页面中,可以在用户点击时调用 Android 暴露的方法:

class PaymentBridge(
    private val onSubmit: (String) -> Unit
) {
    @JavascriptInterface
    fun onSubmit(orderId: String) {
        onSubmit.invoke(orderId)
    }
}

注册通信接口:

webView.settings.javaScriptEnabled = true

webView.addJavascriptInterface(
    PaymentBridge { orderId ->
        runOnUiThread {
            // 收到页面提交事件
        }
    },
    "AndroidPayment"
)

页面中的 JavaScript:

document.querySelector('#submitButton').addEventListener('click', function () {
    if (window.AndroidPayment) {
        window.AndroidPayment.onSubmit(currentOrderId);
    }
});

这种方式只能说明用户点击了按钮,不能证明付款已经成功。用户可能重复点击按钮,也可能取消支付,支付还可能失败或仍在处理中。

为什么不建议直接注入 onclick

使用 evaluateJavascript() 给第三方按钮追加监听器虽然简单,但并不可靠:

webView.evaluateJavascript(
    """
    document.querySelector('#submitButton')?.addEventListener('click', function () {
        AndroidPayment.onSubmit('');
    });
    """.trimIndent(),
    null
)

主要问题包括:

  • 第三方页面可能使用 iframe,外层页面无法直接访问跨域内容。
  • 按钮可能动态生成,注入代码执行时对应元素还不存在。
  • 第三方一旦修改 DOM、按钮 ID 或提交流程,代码就会失效。
  • CSP、重定向或原生表单提交都可能改变页面行为。
  • 向不受信任的页面暴露 addJavascriptInterface() 会增加安全风险。

JavaScript 注入只适合完全由自己控制的页面,不能作为第三方支付结果的回调方案。

支付结果必须由服务器确认

WebView 跳转、JavaScript 回调和 URL 参数都可能被伪造。应用收到回调后,应将订单号发送给自己的服务器查询:

WebView 返回应用
      ↓
应用把 orderId 发送给业务服务器
      ↓
业务服务器查询支付平台,或读取支付平台的异步通知结果
      ↓
服务器返回最终订单状态

发货、充值、开通会员等操作,只能依据服务端验签后的异步通知,或支付平台服务端接口返回的查询结果。

其他注意事项

  • 优先使用支付网关提供的 Android SDK。如果官方要求使用浏览器、Custom Tabs 或外部支付应用,应按其接入流程处理。
  • 校验回调 URL 时,必须同时检查 scheme、host 和 path,不能只用 contains("success") 判断。
  • 不要通过 URL 传递支付密钥、令牌或完整的敏感信息。
  • addJavascriptInterface() 只能向使用 HTTPS 的可信页面开放,并且只暴露必要的方法。
  • 页面显示“支付成功”不代表服务端订单已经支付。还需要处理失败、取消、超时、重复回调和支付处理中等状态。
  • 如果第三方网关没有提供返回 URL 或 SDK,也不允许修改页面,就无法稳定获取按钮原始的 onclick 回调。此时需要联系支付服务商,确认其正式支持的移动端回调机制。

备注:内容仅供参考。