您的当前位置:首页正文

Android 应用内微信 H5 支付

来源:华拓网

一般情况下,要实现应用内支付接入 App 支付 SDK 即可满足业务需求,不过考虑到对于一些类似游戏中心的场景,更多是需要支持
H5 支付。相对微信来说,支付宝的对接简单完善很多,所以本篇文章主要说说接入微信 H5 支付的流程和一些问题。

申请流程

2. 申请开放平台开发者认证

3. 创建一个应用提交申核

需要应用相关资质,主要是为了开通支付功能。

4. 为应用申请微信 App 支付,开通微信支付功能

5. 登录商户平台申请开通 H5 支付

这里只是简单介绍下申请流程的主要环节,具体操作起来有多麻烦我也不想去体会。

应用内接入

说到这里可能有些人想笑了,既然叫 H5 支付那不是应该跟应用本身没多大关系才对,不就是一个支付链接跳转而已吗。

话是这么说没错,但是具体操作起来还是有些坑需要去踩。由于微信 H5 支付本身就是浏览器网页支付场景下的产物,所以微信官方并不推荐在应用中使用 H5 支付。

跟浏览器不一样,在 WebView 中我们还需要自己处理一些问题。比如为了实现调起微信支付,需要对支付链接进行拦截后才能进行处理,下面就来看看这个流程。

 WebViewClient webViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
         // 判断 url 的 scheme 进行相应的处理
            if (url.startsWith("weixin://")){ 
               try{
                    startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                    return true;
                }catch (Exception e) {
                    //防止手机没有安装处理某个 scheme 开头的 url 的APP导致crash
                    AlertDialog.Builder builder;
                    builder = new AlertDialog.Builder(mActivity);
                    builder.setTitle("支付中心").setMessage("该手机没有安装微信客户端,请安装微信后重新完成支付,或换用支付宝进行支付").setPositiveButton("确定", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            dialogInterface.dismiss();
                        }
                    }).create().show();
                    return true;
                }
            }else if (url.startsWith("alipays://") || url.startsWith("alipay")){ 
                try{
                     startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                    return true;
                }catch (Exception e) {
                    // 启动支付宝失败,换成网页支付
                    return true;
                }
            }

            if (!(url.startsWith("http") || url.startsWith("https"))) {
                return true;
            }

            view.loadUrl(url, map);
            return true;
        }
}

商家参数格式有误,请联系商家解决

查看官方文档出错问题介绍,说是当前调起 H5支付的 referer 为空导致,WTF? 难道 Android WebView 打开一个链接的 referer 不知指向当前页面的域名?都说实践是检验真理的唯一标准,抓包看看好像还真的是,很好,再一次感觉到了 Android 系统咖喱味代码。

没办法,这锅也不能甩给微信,只能按照文档说的解决方法自己来背。然而这文档说的也是不明不白的,只是说域名设置要一致,废话不多说,直接动手更简单,下面给出示例代码(已自行检验过,真实可用的)

直接在原有的代码基础上进行更改

 WebViewClient webViewClient = new WebViewClient() {
        @Override
        public boolean shouldOverrideUrlLoading(WebView view, String url) {
          
           ...

            if (!(url.startsWith("http") || url.startsWith("https"))) {
                return true;
            }

            // 比如我们申请时填写的是经常用来测试网络连通性的 
            HashMap<String, String> map = new HashMap<String, String>();
            // 指定申请微信 H5 支付时填写的域名,
            map.put("Referer", 
            view.loadUrl(url, map);
            return true;
        }
}

But... 测试过程中发现一个兼容性问题:在4.4.4、4.4.3的设备上,下单时问题还是复现了,通过抓包发现设置的 Referer 并没有生效

if (("4.4.3".equals(android.os.Build.VERSION.RELEASE))
                || ("4.4.4".equals(android.os.Build.VERSION.RELEASE))) {
     //兼容这两个版本设置referer无效的问题
     view.loadDataWithBaseURL("商户申请H5时提交的授权域名",
                    "<script>window.location.href=\"" + targetUrl + "\";</script>",
                    "text/html", "utf-8", null);
 } else {
      Map<String, String> extraHeaders = new HashMap<>();
      extraHeaders.put("Referer", "商户申请H5时提交的授权域名");
      view.loadUrl(targetUrl, extraHeaders);
 }

关于 shouldOverrideUrlLoading 方法返回值的说明:

  • 若没有设置 WebViewClient 则由系统(Activity Manager)处理该 url,通常是使用浏览器打开或弹出浏览器选择对话框。
  • 若设置 WebViewClient 且该方法返回 true ,则说明由应用的代码处理该 url,WebView 不处理,也就是程序员自己做处理。
  • 若设置 WebViewClient 且该方法返回 false,则说明由 WebView 处理该 url,即用 WebView 加载该 url。

下面给出最终修改完成以后的完整代码:

完整解决方案


// 设置微信 H5 支付调用 loadDataWithBaseURL 的标记位,避免循环调用,
// 再次进入微信 H5 支付流程时记得重置此标记位状态 
boolean firstVisitWXH5PayUrl = true;

WebViewClient webViewClient = new WebViewClient() {

    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {

        if (url.startsWith("weixin://")) {
            try {
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                return true;
            } catch (Exception e) {
                // 防止手机没有安装处理某个 scheme 开头的 url 的 APP 导致 crash
                showToast("该手机没有安装微信");
                return true;
            }
        } else if (url.startsWith("alipays://") || url.startsWith("alipay")) {
            try{
                startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
                return true;
            } catch (Exception e) {
                // 防止手机没有安装处理某个 scheme 开头的 url 的 APP 导致 crash
                // 启动支付宝 App 失败,会自行跳转支付宝网页支付
                return true;
            }
        }

        // 处理普通 http 请求跳转 
        if (!(url.startsWith("http") || url.startsWith("https"))) {
            return true;
        }

        // 处理微信 H5 支付跳转时验证请求头 referer 失效
        // 验证不通过会出现“商家参数格式有误,请联系商家解决”
        if 

            // 申请微信 H5 支付时填写的域名
            // 比如经常用来测试网络连通性的 
            String referer = GameConfig.WX_H5_PAY_HOST;

            // 兼容 Android 4.4.3 和 4.4.4 两个系统版本设置 referer 无效的问题
            if (("4.4.3".equals(android.os.Build.VERSION.RELEASE))
                    || ("4.4.4".equals(android.os.Build.VERSION.RELEASE))) {
                 if (firstVisitWXH5PayUrl){
                     view.loadDataWithBaseURL(referer, "<script>window.location.href=\"" + url + "\";</script>",
                               "text/html", "utf-8", null);
                     // 修改标记位状态,避免循环调用
                     // 再次进入微信H5支付流程时记得重置状态 firstVisitWXH5PayUrl = true
                     firstVisitWXH5PayUrl = false;
                 }
                 // 返回 false 由系统 WebView 自己处理该 url
                return false;
            } else {
                // HashMap 指定容量初始化,避免不必要的内存消耗
                HashMap<String, String> map = new HashMap<>(1);
                map.put("Referer", referer);
                view.loadUrl(url, map);
                return true;
            }
        }
        return false;
    }
}

Over...