一般情况下,要实现应用内支付接入 App 支付 SDK 即可满足业务需求,不过考虑到对于一些类似游戏中心的场景,更多是需要支持
H5 支付。相对微信来说,支付宝的对接简单完善很多,所以本篇文章主要说说接入微信 H5 支付的流程和一些问题。
申请流程
按照微信支付官网 H5 支付说明 ,目前是没有直接申请接入 H5 支付的入口,只能在微信商户平台中去另行开通。然鹅~微信商户平台账户也不支持直接注册申请,只能先注册微信开放平台后接入微信 App 支付后才会有商户账号分配。
1. 注册微信开放平台账户
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;
}
}
商家参数格式有误,请联系商家解决
你以为这样就完了是吧,然鹅并没有,实际开发中很大机率会出现微信提示商家参数格式有误,请联系商家解决的问题,而且更尴尬的是:这个问题并不会 iOS 中出现,也不会 Android 浏览器中出现,偏偏就是在应用的 WebView 中出现了。
查看官方文档出错问题介绍,说是当前调起 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;
}
// 比如我们申请时填写的是经常用来测试网络连通性的 http://www.baidu.com
HashMap<String, String> map = new HashMap<String, String>();
// 指定申请微信 H5 支付时填写的域名,
map.put("Referer", "http://www.baidu.com");
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 的使用方式有关,有兴趣的同学可以参考这篇文章:关于shouldOverrideUrlLoading方法的一些考证
关于 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 (url.contains("wx.tenpay.com")){
// 申请微信 H5 支付时填写的域名
// 比如经常用来测试网络连通性的 http://www.baidu.com
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;
}
}