分为如图的六种情况,如果考虑app内的webview支付,那就会更多。。。
其中,微信环境用支付宝支付是行不通的,这里是支付宝官方说明;
从前端的角度看 下单、支付 流程
我们的项目中同时支持支付宝和微信支付,支付前用户自己选择
总体来说分为
- 下单:接口调用成功则进行了库存扣减
- 准备支付:此时调用业务后端的支付接口,获取需要传给第三方信息或者跳转到第三方的 url
- 进行支付:调用第三方 sdk 或者跳转到第三方支付页面
- 返回:支付成功后进行后续流程,跳转进行的支付需要手动检查支付状态
失败情况的处理
到第 2 步失败:
业务后端并没有记录到支付渠道,所以继续支付时需要重新选择支付方式 然后直接进行准备支付流程(第 2 步)
到第 3 步失败:
继续支付时还是需要进入准备支付流程(第 2 步),获取支付需要的信息。
-
上面是下单和支付通用的流程和错误处理。
不同的支付渠道,不同的环境,在细节不尽相同,下面分情况聊聊,代码都是 React 代码片段
浏览器环境-支付宝支付
要成功的调用支付宝支付,需要到支付宝开放平台进行一系列的配置,查看官方文档:接入前准备,虽然这些可能是其他人去配的,但开发必须懂,要不然遇到问题不知道哪里配的不对,真的很麻烦。
支付(第3步)和返回(第4步)的详细流程如图:
和前端相关的在于跳转前的表单提交和跳转回来的检查支付状态,注意图中的5根线:情况1、2、3;路径1、2
模拟表单提交(支付)
前端调用业务后端的支付接口后(第2步),html字符串从支付宝-业务后端-最后到前端,前端模拟表单提交实际上调用到了支付宝的接口,支付宝实现跳转到支付宝的 h5 页面
ts
const h5AliPay = (payHtml: string) => {
const div = document.createElement("div");
div.innerHTML = payHtml;
div.id = "form-wrapper-for-alipay";
document.body.appendChild(div);
const form = div.getElementsByTagName("form")[0];
form.submit();
};
2023 年 6 月支付宝对外的支付方案升级了,原来是业务端通过参数拼接访问 url,现在是模拟表单提交
检查支付状态
回到我们的 h5 页面,分如上面图中标注的三种情况。其中:
- 情况 2 和情况 3 是支付宝跳转的,跳转地址是后端接口通过 return_url 传给支付宝的,一般 return_url 和发起支付的页面是同样的路由,这两种情况是路由继续入栈的;
- 情况 1 是路由出栈返回发起支付的页面\
为了更好的描述,假设在详情页(/detail?id=xxx
)发起下单和支付,return_url
也是详情页。那我们需要考虑的细节有两个
1.详情页有两种状态:购买操作前、购买操作后
详情页需要区分是购买前还是购买后,购买后进入页面就要弹窗检查,购买前不需要
在笔者看来这里只有两个方案,① 路由参数定义一个标识 ② 本地存储,毕竟已经跳出我们的应用了,store 啥的肯定不行,当时笔者采用的是方案 ①
和后端协商,return_url
定义为 /detail?id=xxx&orderNo=xxx
,orderNo
相当于一个标志位,有orderNo
则弹窗
ts
useEffect(() => {
if (getQuery("orderNo") && checkCountRef.current === 0) {
// 因为 useEffect(()=>,[]) 会触发两次,所以这里checkCountRef.current计数一下
H5PayStatusHandler(); //弹窗逻辑
checkCountRef.current++;
}
}, []);
情况 1 也要弹窗呀,所以在跳转到第三方支付平台之前要主动对路由打标志位,只改参数不改路由,能够做到用户无感知,不会re-render等
ts
const beforeJumpTo = (orderNo: number) => {
const { pathname, search } = location;
const newRoute = search ? `${pathname}${search}&orderNo=${orderNo}` : `${pathname}?orderNo=${orderNo}`;
navigate(newRoute, { replace: true });
};
const h5AliPay = (payHtml: string) => {
// ...
const form = div.getElementsByTagName("form")[0];
beforeJumpTo();
form.submit();
};
✨ 这里不直接指定路由而是动态计算路径+参数,是因为考虑多个页面通用,我们的项目中支付行为发生在三个页面,这些逻辑都是写在 hook 中共用的
检查支付状态后要把orderNo
去掉,否则路径 2 到详情页,会再次弹窗。
ts
const afterJumpBackAndCheck = () => {
const { pathname } = location;
searchParams.delete("orderNo");
const search = searchParams.toString();
const newRoute = search ? `${pathname}?${search}` : `${pathname}`;
navigate(newRoute, { replace: true });
};
const H5PayStatusHandler = () => {
// Modal ...
afterJumpBackAndCheck();
};
其实还有个问题,情况 3 会拼接很多支付相关的参数在详情页的路由上,此时表单提交后无法正常跳转到支付宝的h5支付中间页,这些参数也要剔除才行。这个马上就解决!
2.详情页又返回详情页了?
路径 1 就是从详情页返回到详情页,并且会再次弹窗。为了避免这个情况,笔者的思路是情况 3 和 情况 2,自动进行路由出栈。流程图就变成了这样:
同时解决了刚刚的问题!那就再加个标志fromThirdPayPlatform
区分开情况2、3和情况1咯
ts
useEffect(() => {
console.log("======= useEffect [] ======");
if (searchParams.has("fromThirdPayPlatform")) {
console.log("======= fromThirdPayPlatform 从第三方平台回来 ======");
navigate(-1);
return;
}
if (getQuery("orderNo") && checkCountRef.current === 0) {
console.log("======= orderNo 发起支付的页面 ======");
H5PayStatusHandler();
checkCountRef.current++;
}
}, []);
3.华为原生浏览器不弹窗?
表现是情况1回到详情页,不弹窗。路由出栈时,页面不会re-render,不会走到 useEffect(()=>,[])
这段逻辑。
笔者采用的方法是监听这个 visibilitychange 事件,回调中弹窗检查支付状态。注册事件的时机可以是表单提交之前或者页面初始化时,注销事件的时机是弹窗之后。
js
//...
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible" && getQuery("orderNo")) {
H5PayStatusHandler();
}
});
//...
-
支付宝支付,在支付宝浏览器环境,可以看做和不同浏览器中一个流程,只是少了app切换那一步。
❓写文档的时候才一激灵❕不传 return_url,会不会只存在情况1,这样就更简单了,少了第二个细节处理
浏览器环境-微信支付
要成功调用微信支付,要到微信支付平台做一些接入前的准备、开发指引。微信比支付宝要麻烦一些,需要到商户平台(mchid)和公众号平台(appid)同时操作,并进行 appid 和 mchid 的关联以及一系列的其他配置,具体看官方文档吧。
支付(第3步)和返回(第4步)的详细流程如图:\
其实和浏览器环境的支付宝支付差不多,面临的问题和处理方式也和支付宝支付一样,所以这里就只说一下区别点:
- 支付宝返回的是表单字符串,微信返回的是 url。
- redirect_url 携带在路由参数中,需要业务端去拼接(笔者做项目时,是让后端拼好了接口返回的,哈哈哈哈) redirect_url 非必须,默认返回发起支付的页面,❓ 是跳转还是返回
-
微信支付在支付宝浏览器中的场景,待补充,我们项目主要靠微信引流所以考虑的环境就微信和普通浏览器。预估两种情况①不能支付②和浏览器中使用微信支付一样。
微信环境-微信支付
虽然都是微信支付,但在微信环境调用微信支付和其他浏览器内调用微信支付流程上完全不同。对于前端开发来说,
- 利好是不用每次跳到站外去支付,也就不用检查支付状态,不用处理各种路由情况了。
- 但是!重要的永远是后半句 O(∩_∩)O 哈哈~支付前多了一个流程:授权。
所以就先说说授权咯,这个授权除了支付其他情况也可能遇到,所以可以抛开支付独立去看授权流程。
微信授权
授权相关配置参考官方文档这里,先展示一下授权流程:
一般情况下是否授权都是从用户信息接口获取,授权是通过"跳转"行为完成,
- 跳转时通过路由参数 携带的两个重要信息是 appid 和 return_uri , 跳回来微信会通过路由参数 传递给业务层 code
- 业务后端通过 code 置换 openid,并存在用户信息接口。前端需要处理一下路由把标志位清掉,防止从当前页面进入其他页面再返回时再次触发授权流程。
下次识别到用户信息中标记为已授权,就不用再走一次流程了
ts
const toAuth = () => {
const encodeUrl = encodeURIComponent(window.location.href);
window.location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APPID}&redirect_uri=${encodeUrl}&response_type=code&scope=snsapi_base&state=${WX_AUTH_STATE}#wechat_redirect`;
};
const afterWxAuth = async (code: string) => {
await setOpenId({ code }); //code 置换 openid
getUserInfo(); //更新用户信息
// 处理路由:为了用户授权后,路由到别的页面,再返回时,不再重复afterWxAuth流程。
// 也可用 openid 判断,有openid了就return。
const { pathname } = location;
searchParams.delete("code");
searchParams.delete("state");
const search = searchParams.toString();
const newRoute = search ? `${pathname}?${search}` : `${pathname}`;
navigate(newRoute, { replace: true });
// 处理路由 end
};
useEffect(() => {
const code = getQuery("code");
const state = getQuery("state");
if (code && state === WX_AUTH_STATE) {
afterWxAuth(code); //说明是微信授权通过了
}
}, []);
虽然从代码上看是跳转行为,实际表现并不会离开当前页面,而是弹窗的形式,可以理解为和微信的一种通信方式
微信sdk支付
授权过程作为微信sdk支付的前置条件大概是这样的:
可以看到,支付总流程(文中第二个图)的第 3 步进行支付和第 4 步返回都简化了,代码上简洁了许多:
- 引入 sdk,WeixinJSBridge 对象才挂在全局
html
<script src="https://res.wx.qq.com/open/js/jweixin-1.4.0.js"></script>
- 调用微信 sdk
ts
const { appId, channel, nonce, packageNo, prepayId, redirectUrl, sign, timestamp, url } = payData;
//大多数字段都是要后端通过第2步准备支付给前端
const payload = {
appId,//这个是个固定值,可以不从后端接口取
timeStamp: `${timestamp}`,
nonceStr: nonce,
package: `prepay_id=${prepayId}`,
signType: "RSA",
paySign: sign,
};
WeixinJSBridge.invoke("getBrandWCPayRequest", payload,(res: any) => {
if (res.err_msg === "get_brand_wcpay_request:ok") {
//支付成功逻辑
}else if (res.err_msg === "get_brand_wcpay_request:cancel") {
//取消支付
}else if (res.err_msg === "get_brand_wcpay_request:fail") {
//支付失败
}
}
字段都是来源于在准备支付时调业务后端的支付接口所得,其中就appId和signType不是动态值。
支付成功与否的状态是可靠 的,不用检查 巴拉巴拉的流程了,直接展示结果,和调业务后端一样丝滑的流程~
授权发生的时机
从理论上讲,需要微信授权要同时满足两个条件①在微信环境②选择微信支付。所以在支付的总流程图中,授权出现在已经确定了支付方式和下单之后,也就是上一小节的流程那样,具体就如图:
不过,我们产品的逻辑稍微调整了一下,把授权前置了
这个取决于产品逻辑,仅供参考