我们来看看,电商订单支付的简要流程:
订单钱包支付流程
从下单/计算开始:
京东PC端支付页
APP端
: 在国内,购物大部分都是在APP端,产品经理会想法设法把用户带到APP,为什么我的示例图都用京东,不用淘宝呢?因为我拿UC打开淘宝,会直接跳转APP。
APP端的钱包支付,我们应该都非常熟悉,一般是拉起钱包,支付。
APP支付
WAP端
:手机的网页站,WAP端的支付一般是直接拉起对应的钱包,如果拉起钱包失败,就跳转界面
京东支付WAP端
PC端
:PC端,通常是打开收银台,展示一个二维码,通过钱包扫码支付,下面是京东的微信支付扫码页
5 . 支付回调:用户完成支付后,三方支付平台,会回调商户,通知支付结果。
6 . 同步订单状态:支付服务在确认支付完成后,会向订单服务同步支付的结果,订单服务变更订单的状态,由未支付-》待发货,客户端通过轮询、长连接,或者服务端主动推送的方式,在界面上变更订单状态。 我们再从支付流水的角度看一下支付状态的变化:
支付状态变化
支付中
支付中
为什么要花这么多篇幅来讲支付的业务流程、交互过程呢?因为我认为,防止订单的重复支付,不止是技术上的问题,也是业务和产品上的问题。
我们可以看到PC端支付,是扫描二维码,这些二维码,就是对应相应的支付流水,假如用户重复点击支付,如果不做防重的的话,会生成两笔支付流水,也就是两个不同的二维码,要是用户分别扫了两个不同的支付码,那么毫无疑问,就会产生重复支付。
“我明明付款了,为什么我的订单还没支付呢?”
黑我钱是吧
这就是所谓的“掉单”:
用户一看,自己付了款,结果商城里订单还未付款,但是又特别想要,可能就会再下一单,这样就重复支付了。
我们国内支付的体验还是非常快捷的,大家可能没有感觉,如果了解过海外支付的可能了解,很多支付的渠道,消耗的时间非常长。
比如用户保罗选择了一种支付方式Boleto
,结果支付的网点离保罗他们村太远了,保罗又选择了Paypal
支付,保罗去赶集的时候,又顺手去网点把Boleto
的这一笔支付了,结果就重复支付了。
这种情况大家可能很少遇到,我们可以用美团
下一个单,先打开微信支付,不要支付啊,接着回到美团,打开支付宝,用支付宝支付完成后,用微信接着支付,大家猜猜,两笔支付是不是都能成功?答案是可以。
美团多渠道支付
不管是3.申请支付
、还是5.支付回调
,都应该以订单维度加锁,防止并发下的重复操作。
加锁,毫无疑问,也是分布式锁,通常我们会选择Redis分布式锁。
加锁
申请支付成功,支付回调成功,都应该缓存结果。
再申请支付,收到成功回调的时候,都应该先去检查支付的状态。
在这里插入图片描述
假如说,用户重复支付了,再次申请支付的时候,如果已经申请支付成功了,那么这笔支付肯定是要拒绝的。
但是,要是已经存在的这笔流水还在支付中
呢?——我们不确定它是成功还是失败,肯定是不能拒绝支付的,因为可能用户支付失败了,但是状态还没同步,这样肯定是不行的。
所以,我们可以取消掉正在支付中的流水,再进行支付。
支付中流水取消
现在又有新的问题了,假如发起支付的时候,有流水正在支付中,如果第三方支付平台不支持取消支付,或者用户新的支付是通过不同的渠道,我们希望尽可能提高用户的支付成功率,怎么办呢?
我们可以在发起支付的时候,订单还在支付中的情况下,允许用户发起多笔支付,在支付回调的时候,检查用户是否已经有成功流水,对后来的流水进行退款处理。
支付回调
当然,退款是个很危险的操作,毕竟钱退了,可就很难追回来,一定要做好风险的控制。
如果因为故障没有收到回调,或者没有及时收到回调,就可能会发生所谓的外部掉单。
防止外部掉单的关键,就在于,不能傻傻地只等三方的回调通知,而要主动去查询,用户发起支付的3s之后,就可以发起轮询了,直到拿到支付流水的最终状态,主动轮询,一般可以这么实现:
轮询
定时任务轮询
使用定时任务,扫描表中支付中的流水,主动查询支付的状态,定时任务的实现方式有很多,线程池、调度框架、分布式调度框架等等。
定时任务轮询的缺点有两个:
1 . 对数据库有一些压力,观察监控,会发现定时任务扫表的时候,有时候会造成数据库的一些“峰刺”
2 . 不便调整频率,实际上,用户发起一笔支付之后,一般都会在10s-1min中完成支付,越往后,用户完成支付,所以轮询梯度进行,会更合理一些,轮询的间隔可以设置成类似这种:3s,10s,30s,3min……
3 . 延时消息轮询
另外一种方式就是使用延时消息,用户发起支付之后,发送一个延时消息,消费到延时消息之后,查询流水支付状态,没有拿到最终状态,就再发一个延时消息。延时消息的好处是对数据库的压力没有那么大,轮询的梯度也可以进行控制,缺点是实现起来复杂一些,而且要维护消息队列。
支付服务在收到异步通知回调、或者主动轮询到流水的最终状态后,要通知订单服务支付流水的变化,订单服务同步更新订单的状态,这个过程要尽可能保证通知成功,可以采用同步+异步的方式。
这里还有一个问题,客户端如何同步这个状态?因为可能服务端更新了订单状态,但是客户端的界面上还是未支付,得用户主动刷新一下,才能拿到最新的状态,这样明显是不太合适的。
服务端、客户端的状态同步,无非就拉
和推
:
不管从产品的角度,还是技术的角度,客户端发起支付这一步,其实应该尽可能地不要外跳,PC端使用支付服务生成的支付码,而不是跳转;移动端网页、APP在应用内展示支付页,当然这个是由第三方支付平台决定的。
在UC内内嵌支付宝
不知道大家留意到了没有,现在的支付宝,已经做到了不用拉起钱包,在应用内就可以完成支付,这个对于商家的意义还是比较大的,对用户体验、支付成功率,都有正面的作用,相信以国内的内卷程度,其它支付供应商,一定会“跟进”的。
好了,关于如何防止重复支付,就讲到这里。对于支付,老三也只是初窥门径,希望各位大佬不吝指教。
参考:
[1]. 服务端如何防止重复支付
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8