移动端支付系统如何设计有效地防重失效机制?



导读html

“目前在互联网应用的大部分支付场景中,对接支付宝、微信移动支付产品这样须要用户参与支付流程的支付方式已经变得很是广泛,相似的还有PC端银行网银支付;而经过绑定用户银行卡、对接银行卡快捷支付通道直接扣款的支付方式,虽然还在电商、保险、互联网金融、租房等行业被普遍应用,可是随着微信钱包、支付宝钱包这类移动互联网支付方式的兴起,用户规模的迅速增加,再加上用户银行卡信息安全、直连银行通道关闭等因素用户市场份额正在逐步减小”。前端


实际上,这种须要客户端参与支付流程的方式相比银行卡快捷支付直接扣款这类支付方式,在支付系统的流程及订单结构等设计上是存在较大差别的,其中订单的防重失效机制的设计更是一个比较棘手的问题。数据库


参与过支付系统开发或在业务系统中开发支付功能的同窗可能会遇到相似这样的业务需求:安全


用户在外卖网站或App上购买了点了一份外卖,并经过微信支付进行付款,系统在收到用户支付完成的消息后,提示用户付款成功并派单给餐馆?微信


初看这个问题,可能不少同窗都会有疑问,这不是一个很简单的支付流程吗?大部分支付场景不都是这样的么?网络


实际上是这样的,做为正常的支付流程来说,上述场景并无什么问题,在整个系统链运行稳定的状况下,可能大部分参与者并不会有什么感受;可是,做为一个具有专业精神的小码农来讲,仍是有不少异常场景须要考虑的,否则就可能会由于系统流程上设计的缺陷而给公司和用户体验形成比较大的伤害。并发


那么上述需求中,会有什么样的异常场景呢?与支付系统防重失效机制的设计有什么关联?异步


咱们能够先来看一下以上场景在系统流程中的运行状况(须要放大查看):
微信支付



在上面的流程中,虽然从用户角度看可能只是几秒钟的事情,但实际上整个系统链是经历了一个比较长的调用过程。具体以下:优化

  • 用户在点外卖的过程当中选择微信支付后,App会将支付请求发送给外卖后台系统,若是在整个外卖平台中,支付系统是一个独立的系统,则外卖业务后台服务会在生成业务订单后将支付请求发送给独立的“支付系统”进行处理;

  • 此时支付系统做为独立的中间系统会处理外卖平台业务后台发送过来的支付请求,记录其业务订单号并生成对应支付系统自身的支付流水号,并对支付流水进行状态初始化(这里涉及一个业务订单号&支付订单号如何匹配问题,会在后面的讨论中阐述);

  • 支付系统调用微信统一下单接口进行预支付(这里的操做方式就是类网银式的支付方式,先进行预支付而后由用户跳到站外进行支付),并同步获得微信支付返回的预支付订单信息,支付系统此时须要更新支付订单为pending状态表示处于预支付状态;

  • 而后支付系统将预支付信息同步给调用方—外卖业务后台,外卖业务后台再同步给外卖App;

  • 外卖App会根据预支付订单信息经过客户端支付SDK唤起微信支付客户端,由用户操做微信支付客户端直接向微信支付发起付款动做,须要注意的是,此时调用链已经转移到了站外,实际上此时用户是否支付或是否支付成功,不管是支付系统仍是外卖系统及App自己都是没法直接感知到支付结果的,须要逐层回调;

  • 微信支付会经过循环调用的方式主动将支付结果回调给支付系统,再由支付系统回调给外卖业务系统,最后在用户直接感知前由App主动查询外卖业务系统订单支付状态,同时提示用户支付成功或支付处理中这样的信息;


从上面的流程能够了解到,实际上你们平时在经过App购物时支付的一瞬间是经历了很复杂的流程。那么,不知道在支付的过程当中有没有这样的体验?


在点外卖后付款了,微信也提示支付成功了,可是外卖App却始终不显示点餐成功?即便选择从新支付也提示支付中,不容许重复支付?或者选择从新支付之后外卖App显示也显示点餐成功了,可是以前支付的钱却不见了,只能打客服投诉,各类麻烦?


上述问题,在目前支付流程的设计上是必然会发生的,目前做者所在的公司也有相似的问题,虽然这种问题发生的几率可能不是特别高,可是绝对是破坏用户体验以及增长了客服的工做量,处理得是否得当是衡量一套支付系统是否强大的核心指标之一。


那么怎样的设计才能很好地解决此类问题呢?


从流程上看用户选择微信支付并唤起微信钱包付款后,实际上外卖平台支付系统已经感知不到系统的状态了,也就说此时用户是否完成了支付,平台是没法同步感知的,只能依赖于微信的主动通知回调,通常来讲目前主流的支付公司都有一套完整的商户通知逻辑,会在支付完成后实时通知到商户。


可是不少时候会有多种因素致使这种通知被延迟,比较常见的因素主要有网络、自身平台系统服务宕机、第三方渠道通知服务故障等。


也就说会有用户支付了点外卖的钱,系统却没有实时显示支付成功的问题,也就是咱们常说的短时掉单问题;或者用户没有及时支付,从新付款时却会被提示“支付中请勿重复提交”,也就是支付防重问题


对于掉单问题的处理,能够根据业务场景进行考虑,但不管是哪一种方案,越快速补偿业务越可以有效地提高用户体验,减小系统异常处理流程,让防重机制更加灵敏,在避免重复支付问题的同时提升支付成功率。


另外,是否容许用户重复支付,如何利用冲正机制有效提升用户体验的同时快速保障用户权益,也是须要在总体方案中进行考虑的方面。


根据业务时效性要求不一样,大体有两种方案:


异步补偿机制。

具体来讲,就是支付流程按照正常的流程走,经过采用旁挂定时的方式扫描系统中必定时间策略范围内的pengding状态的订单,经过微信提供的订单查询接口主动轮询,一旦支付状态查询到终态即刻触发系统回调,完成支付订单及业务逻辑的补偿;

另外一方面,若是pengding状态订单经过轮询方式没有查询到最终状态则须要设置必定的重复轮询策略,例如5分钟、10分钟、20分钟、1小时、3小时、8小时、24小时这样,并在超过策略规定的时间及轮询次数后将支付流水更新为失效终态,并提供订单查询接口供业务平台完成自身业务订单逻辑的更新。


系统示意图以下:

经过旁挂式的方式,支付主流程会变的相对简单,只须要考虑正常的收单场景,对于不少业务实时性不过高的支付场景,这种方式也够用。但对于业务实时性要求很是高,而且对用户体验有极致要求的场景来讲,这种方式显然也是存在明显问题的。


咱们仍是拿点外卖这件事来讲,外卖后台在接收到用户经过App发送的点餐支付请求后生成外卖订单并将支付请求发送给平台支付系统,支付系统通常来讲会首先进行订单防重判断,即已经发起过的成功支付/支付中的请求不被容许发起第二次,支付成功的交易不容许重复发起。


可是等待支付或支付失败的交易不少公司内部支付系统都会被要求容许发起二次付款,在外卖点餐环节,若是用户点餐了可是并无马上进行支付或者支付因为某种缘由失败了,是能够从新发起付款的,在这种状况下,支付系统就会面临一个问题,因为不知道在进行预支付后用户是否完成了支付,对因而否应该继续让用户发起支付请求,防重逻辑就会变的迟钝,若是容许用户支付则可能出现重复扣款的问题,不容许则会影响用户体验,为了让整个机制变得合理,因此须要依赖于上述系统的补偿机制来进行回盘或失效处理。


这里会遇到如下三种状况:

一、用户最终未支付,则系统安装必定的轮询机制进行后续的订单失效处理便可;


二、用户完成了支付,支付系统迟迟收不到微信的回调,经过逐步轮询的方式系统也会进行后续的订单回调补偿;但这里的问题是,若是异步补偿系统对订单的轮询不够及时(在支付订单量比较大的状况下,经过定时轮询的方式在时效性上较差),那么就会致使一个比较尴尬的状况,用户付完了钱,可是外卖订单很长时间显示未支付,没法进行派单,在轮询补偿系统完成回调后触发派单操做,但每每极可能已通过了饭点,而且极可能用户已经触发了申诉流程,外卖平台须要进行退款操做(增长客服工做量);


三、则是用户当时并未及时支付,在订单失效前的某个时间,用户可能会选择从新付款,由于此时支付系统订单并未失效,会处于支付中状态,触发防重机制,没法再次发起付款;


对于二、3两种状况,若是须要很好的知足业务要求,就要提升支付系统时效性,提升订单防重失效、快速回盘的处理时间。要达到这样的效果,每每单纯的依赖旁挂式的处理方案是很难达到的,而是须要让实时支付流程的设计变得更加智能和灵敏。


实时支付流程优化设计

为解决上面的问题咱们须要在实时支付流程中加入异常优化机制,从整个流程的设计上去解决,让整个支付系统变得更加智能和灵敏,虽然这种方式看似让支付主流程变得复杂了不少,但从优化用户体验、提升系统灵敏度的角度看,这种复杂度是值得的而且是能够经过技术细节屏蔽的。


那么具体应该怎样去设计这样的流程?


详细如图所示(需点击放大):




如上图所示,支付系统接收到前端发起的支付请求,系统首先须要进行防重判断,这里为了有效地防止并发请求,采用Codis锁的方式,即一笔业务支付订单请求发送到支付系统后首先获取Codis全局锁,若是存在锁则说明订单正常被处理/未被正常处理,此时咱们须要进行锁更新时间判断,若是锁的更新时间与系统当前时间差<=10s(可根据业务场景进行动态调整,即10s内同一笔商户订单号的支付请求不容许被屡次发起),则颇有可能此时系统正在处理这笔支付请求,应该正常进行防重处理;


相反,若是获取的Codis锁的更新时间与系统当前时间差>10s,则此时会存在两种状况,一种就是这笔支付订单没有被正常支付,是应该被容许从新发起支付的;另外一种可能则是用户可能支付成功了,只是渠道在支付结果回调的过程当中出了问题致使系统掉单。这两种状况混在一块儿,系统并不能马上识别出到底属于何种状况。


这个问题是一个很是广泛和典型的问题,几乎不少公司都会遇到。


此时,支付系统有两种选择,一种选择是执行严格的防重策略,即要求全部对接支付平台的业务系统每次调用支付请求都必须生成不一样的商户订单号,支付系统对于同一个订单号不管支付成功与否都不容许将此商户支付单号重复发送给支付平台,这种方案与第三方支付公司的接口约定一致。


这种防重策略粗暴简单,本质上是将逻辑的复杂性传到给了业务系统,也会让业务变的难受,若是支付平台在后期经历太重建,须要推进业务线切换的话,也每每会招致业务系统的反对。


那么如何让支付平台自己来屏蔽这种复杂的细节,让业务尽量无感知?


两个订单号

  • 商户订单号:业务系统发起的向支付系统发起支付请求是生成的在商户系统中惟一标识一笔订单的标记。

  • 支付订单号:业务系统向支付系统发起支付请求后,支付平台自己生成的系统惟一标识一笔支付流水的标记,而且是支付系统与第三方支付渠道交互的惟一流水标识。


为了达到以上目标须要在支付系统内部采用1:3(举例)的订单模型,即1笔业务订单号能够对应支付系统3笔支付订单流水,而且每笔支付流水容许被发起的条件是上笔支付流水数据库订单状态是未支付成功,而且须要在当前这笔支付流水从新生成后将上笔支付流水放入订单动态实效队列,进行快速失效处理。


之因此采用以上方式,缘由在于超过3次时间间隔超过30s(策略能够根据业务实践进行动态调整)还未完成支付的状况,系统基本能够认定属于恶意点击行为,能够直接拒绝此笔业务订单从新发起支付了。


须要动态将上笔支付订单快速置为实效的缘由在于,咱们须要在内部设定一个逻辑:“若是支付订单处于实效状态并在后面接收到了第三方支付成功的回调,则须要系统自动发起该笔支付订单的原路退款逻辑,并确保该笔订单不会被通知到商户侧”。这种现象之因此出现,在于咱们为了提升系统的实时性容许了少许重复扣款的状况发生,并进行了自动冲正逻辑。


固然,在细节的处理上咱们是在当前流水发起前对上笔流水已经进行了一轮订单实时查询,若是结果为支付成功,则这次请求会直接返回支付成功(或者,也能够提示已经支付成功,App主动查询支付系统的订单状态来完成回盘)。


若是当前订单再次预支付成功,在同步返回预支付结果前须要更新Codis中订单锁的时间及发起次数。同时,在接受到第三方正常的支付成功回调后完成订单状态更新及商户通知后消除Codis锁。


上述策略,为解决防重&二次支付问题提供了一种方案,固然还有不少细节的代码逻辑是须要考虑完善的,例如,实时查询超时的策略、退款的触发时间、用户提示等。


此外,若是用户再也不选择再次发起付款,系统中的存量订单也须要经过文中早些时候介绍过的异步补偿机制逐步将进行失效处理(具体策略机制可参考图示及以前的概述),只是若是在异步补偿机制过程当中发现掉单的订单,是否正常回盘或自动给用户退款,就须要具体状况具体分析了。


以上就是本文的所有内容了,在整个支付系统的搭建的过程当中还有不少细节逻辑是能够优化的,须要根据具体业务进行实践与处理。鉴于经验和水平有限,不足之处,还请批评指正(能够直接在公众号进行评论交流哦)。


若是以为小哥在认真写文章,能够关注下公众号,支持下哦

本文分享自微信公众号 - 无敌码农(jiangqiaodege)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索