H5支付踩坑记

在工作中对于 h5 支付这个,遇到的问题很多,所以想记录一下,以后说不定就忘记了,没对接过的,看一下也可以避免很多坑。

背景

因需要更灵活的运营场景,在 app 内需要接入第三方(微信、支付宝)h5 支付

基本信息文档

微信 h5 支付开发及常见问题:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_4

项目

基本的接入方式不做过多的说明,下面以项目的维度简单说明一下在使用 h5 支付的过程中碰到的问题,以及处理方式;

星座牌小游戏

概述:

星座小游戏是一个卡片类抽金币的小游戏,用户通过选择对应的牌来抽取大于支付金额等值的金币;

基本流程:

luckdraw.png

关键逻辑及处理方式

一、星座牌小游戏调起微信 h5 支付

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 打开微信支付
let payParams = JSON.parse(ret.data.payParams);
let openWxUrl =
payParams.payUrl + "&redirect_url=" + encodeURIComponent(redirectUrl);
let hideFrame = document.createElement("iframe");
hideFrame.setAttribute("src", openWxUrl);
hideFrame.setAttribute(
"sandbox",
"allow-scripts allow-top-navigation allow-same-origin"
);
document.body.appendChild(hideFrame);
/*
这里更加优雅的可以用iframe onload 去处理,因为用户加载iframe的时间是受网络环境影响,直接2s后移除不可控
setTimeout(function() {
hideFrame.parentNode.removeChild(hideFrame);
}, 2000);
*/
hideFrame.onload = function () {
//其他逻辑
setTimeout(function () {
hideFrame.parentNode.removeChild(hideFrame);
}, 100);
};

上面的代码,有几个处逻辑处理,对应的,通过下面的几个问题解释;

  1. 为什么通过 iframe 的形式加载 payUrl?
    由星座牌小游戏的交互决定,因为用户在支付完之后,还需要在当前页面开奖,因此,通过 iframe 的形式处理,还能解决微信 h5 支付对于 payUrl 加载的 referer 验证问题;

  2. iframe 为什么要设置 sandbox?,为什么要设定 setTimeout 去移除 iframe?

    • 在 ios13 发布的时候,ios 出现了问题,微信 h5 支付没有调起成功;
    • app 在安卓的某个版本之后也出现了同样的问题,对应那个版本的 app,app 打包的安卓 sdk 版本做了更新

对应的异常 log 如下

1
2
message:Uncaught SecurityError: Failed to set the 'href' property on 'Location': The current window does not have permission to navigate the target frame to 'weixin://wap/pay?prepayid%3Dwx241530497040689dd1394a4f1296524900&package=3510274145&noncestr=1563953450&sign=7867a2dd7feb974db9285725cb2822d0'.
从log看到的代码error,其实不是业务本身的代码,而是payUrl加载后,支付页里面的"top.location.href"异常,关键代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//payUrl
var is_postmsg = "";
if (is_postmsg == "1") {
parent.postMessage(
JSON.stringify({
action: "send_deeplink",
data: {
deeplink:
"weixin://wap/pay?prepayid%3Dwx28141344091494d0c83d3c3cd472e50000&package=3738825662&noncestr=1609136025&sign=02d138bef13beb9222479014a4a4ea85",
},
}),
""
);
} else {
var url =
"weixin://wap/pay?prepayid%3Dwx28141344091494d0c83d3c3cd472e50000&package=3738825662&noncestr=1609136025&sign=02d138bef13beb9222479014a4a4ea85";
var redirect_url = "https://ulink.com/ulink/lucky/index.html";
top.location.href = url;

if (redirect_url) {
setTimeout(function () {
top.location.href = redirect_url;
}, 5000);
} else {
setTimeout(function () {
window.history.back();
}, 5000);
}
}

从上面的代码块可以看到 payUrl 调起微信的逻辑

  1. 调起微信走的 else 的逻辑(这里的 postmsg 的逻辑暂时未从微信官方文档内找到对于的配置方式),通过 top.loaction.href= url 的形式 加载 scheme 调起;

  2. 如果有 redirect_url,会设置个定时器, 5 秒后重定向到 redirect_url — 这也是为什么星座牌小游戏需要添加一个 remove iframe 的逻辑的原因,

整体梳理下来,问题的原因就是 pp 在用新的 android sdk 打包之后,webview 的内核版本的提升,对应的内容安全策略(csp)调整(默认设置调整),阻止了 payUrl 通过 top 的方式直接访问父页面的 api;那么这个问题是通过 iframe 设置 sandbox 属性解决:

1
2
3
4
5
6
7
8
9
10
11
12
/* sandbox
allow-forms 允许进行提交表单
allow-scripts 运行执行脚本
allow-same-origin 允许同域请求,比如ajax,storage
allow-top-navigation 允许iframe能够主导window.top进行页面跳转
allow-popups 允许iframe中弹出新窗口,比如,window.open,target="_blank"
allow-pointer-lock 在iframe中可以锁定鼠标,主要和鼠标锁定有关
*/
hideFrame.setAttribute(
"sandbox",
"allow-scripts allow-top-navigation allow-same-origin"
);

3、redirectUrl 是回调地址,微信是怎么跳回指定的地址的?redirectUrl 为什么配置 ulink 的地址?
微信处理 redirectUrl 的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
if(redirect_url)
{
setTimeout(
function(){
top.location.href=redirect_url;
},
5000);
}
else
{
setTimeout(
function(){
window.history.back();
},
5000);
}
redirectUrl为什么配置ulink的地址?

android 在跳到微信支付完成后,点击支付成功页面的”完成”按钮,会回到调起微信支付的 app,ios 点击完成不会做这部分操作,在有配置 redirectUrl 的情况下会用 safari 打开 redirectUrl;且 redirectUrl 也有域名的验证;

配置成 ulink 的地址,是为了解决 ios 在支付完成后回到 app 内的处理方式;

注:ios 也有不能直接回到 app 的情况,而是通过 safari 打开了 ulink 的页面,这种情况,经过跟客户端同事的一同排查的结果是因为用户在安装 app 的时候,没有下载到 apple-app-site-association 这个文件导致;

app 充值(内嵌、外部)

概述:

内嵌页 h5 充值页面,提供方便用户充值的页面,并且支持微信支付,支付宝支付,对应 app 内的充值;

基本逻辑:

同星座牌小游戏类似,不同的地方是开奖变成了用户余额查询;

一、微信 h5 支付

避免了星座牌小游戏上喷到的问题之后,暂时(这个在子 app 的充值弹窗上又遇到了新的问题)没其他的问题出现;
加载 payUrl 后交互时序图(非官方):
wxpayUrl.png

二、支付宝 h5 支付(支付宝 h5 支付有两种方式:1. 纯 h5 页面支付;2.h5 调起支付宝 app 支付),以下为 h5 调起支付宝 app 支付的流程

支付宝 h5 支付,返回的不是 payUrl,而是一段 formDomString;

提交 payForm 后交互时序图(非官方):
aliPayForm.png

1
2
<!-- 支付宝h5支付,接口返回的form信息 -->
<form name="punchout_form" method="post" action="https://openapi.alipay.com/gateway.do?charset=UTF-8&method=alipay.trade.wap.pay&sign=HfcRgFeT%2FSVj1soSQrBQYCV%2BaoQzrBVupczUmmjM0sQ2FqXlFHMqOti4EexmhSh3Ap%2FRAAG8MXlo%2FTbzVquR59bXe3deuTXc30S5cgsV9l00jaKPOKXdSfJah2r%2FR5onafKys9caXLaaQmVwtrSrWr5hMFz%2FmtfZvZWwch%2FFvJuVS0wlGT128GBG0KSiUue0g2Bs%2BVg%2B3WKhIiQLCBMKB7BuuyFCnvwnpjeLiGafjIYr6CNBn83uzac1QX9OBuzp91EVLGbBSwAFyyxALhporUh4pDe27SqJbwg15kQd6tDp2f7423M6AoQGkEDMdzaBWRTu2UrMenzaqDOpFpilHA%3D%3D&return_url=https%3A%2F%2Fapp.test.com%2Fstatic%2Fh5Conversion%2Findex.html&notify_url=https%3A%2F%2Fapp.com%2Fcallback%2Fppywforkylin%2Falipay%2Falipay%2F5103092247759423283&version=1.0&app_id=2021001145660238&sign_type=RSA2&timestamp=2020-12-28+15%3A28%3A39&alipay_sdk=alipay-sdk-java-3.4.49.ALL&format=json">\n<input type="hidden" name="biz_content" value="{"body":"10金币","out_trade_no":"1231231233123123123","product_code":"QUICK_WAP_WAY","subject":"10金币","timeout_express":"2m","total_amount":"1"}">\n<inp

1、以上 form 代码段直接通过 innerHtml 插入页面还是不行的,script 内的 submit 并不会被执行
HTML 5 中指定不执行由 innerHTML 插入的