钱被扣走了,可是订单却未成功!支付掉单异常最全解决方案

前言

好了,回归到今天的主题,今天分享一下支付系统中异常一些处理方式。数据库

其实这些处理方式并不仅是局限于支付系统,也能够适用于其余系统,你们能够借鉴,应用到本身系统中,提升本身系统的健壮性。网络

异常是系统运行不可避免会发生的问题,若是一切都正常,咱们的系统设计将会至关简单。架构

可是惋惜没有人能作到这一点,因此为了处理异常可能致使的问题,咱们不得不须要加上不少额外的设计,用来应对这些异常。框架

能够说系统设计中,异常处理须要咱们着重思考,将会占据咱们大部分的精力。异步

下面咱们先来看下支付系统中最多见的异常:掉单分布式

欢迎关注个人公众号:程序通事,得到平常干货推送。若是您对个人专题内容感兴趣,也能够关注个人博客:studyidea.cnide

掉单异常

一个最多见的支付平台架构关系以下所示:性能

上图咱们是站在第三方支付公司支付角度,若是是本身公司的内部支付系统,那么外部商户这一块其实就是公司内部一些系统,好比说订单系统,而外部支付渠道其实就是第三方支付公司idea

咱们以携程为例,在其上面发起一笔订单支付,将会通过三个系统:设计

  1. 携程建立订单,向第三方支付公司发起支付请求
  2. 第三方支付公司建立订单,并向工行发起支付请求
  3. 工行完成扣款操做,返回第三方支付公司
  4. 第三方支付完成订单更新并返回携程
  5. 携程变动订单状态

上面的流程,简单以下图所示:

在这个过程就可能会碰到,用户工行卡已经扣款,可是携程订单却仍是待支付,咱们一般将这种状况称为掉单

上述掉单的场景,多数是由于③、⑤环节信息丢失致使,这种掉单咱们将其称为外部掉单

还有一种极少数的状况,收到 ③、⑤环节返回信息,可是在④、⑥环节内部系统更新订单状态失败,从而致使丢失支付成功的信息,这类掉单因为是内部问题,咱们一般将其称之为内部掉单

外部掉单

外部掉单是由于没有收到对端返回信息,这种状况极有多是网络问题,也有可能对端处理逻辑太慢,致使我方请求超时,直接断开了网络请求。

增长超时时间

对于这种状况,第一个最简单的解决办法,适当的增长超时时间

不过这里须要注意了,在咱们增长网络超时时间以后,咱们可能还须要调整整个链路的超时时间,否则有可能致使整个链路内部差事从而引发内部掉单。

画外音:对接外部渠道,必定要设置网络链接超时时间与读取超时时间

接收异步通知

第二个办法,接收渠道异步回执通知信息。

通常来讲,如今支付渠道接口咱们均可以上送一个异步回调地址,当渠道端处理成功,将会把成功信息通知到这个回调地址上。

这种状况下,咱们只须要接收通知信息,而后解析,再更新内部订单状态。

支付系统异常处理-支付异步通知

这种状况下,咱们须要注意几点:

  1. 对于异步请求信息,必定须要对通知内容进行签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏致使出现“假通知”,形成资金损失。
  2. 异步通知将会发送屡次,因此异步通知处理须要幂等。

掉单查询

有的渠道可能没有提供异步通知的功能,只提供了订单查询的接口,这种状况下,咱们只能使用第三种解决办法,定时掉单查询。

咱们能够将这类超时未知的订单的单独保存到掉单表,而后定时向渠道端查询订单的状态。

若查询成功或者明确失败(好比订单不存在等),能够更新订单状态,而且删除掉单表记录。

若查询依旧未知,这时咱们须要等待下次查询的结果。

支付系统异常处理-定时查询

这里咱们须要注意了,有些状况下,有可能没法查询返回订单的状态,因此咱们须要设置订单查询的最大次数,防止无限查询浪费性能。

对帐

最后,极少数的状况下,订单查询与异步通知都没法获取的支付结果,这就还剩下最后一种兜底的解决办法,对帐。

若是次日渠道端给的对帐文件有这一笔支付结果,那么咱们能够根据这个记录更新直接更新咱们内部支付记录。

以前小黑哥写过一篇对帐文章,感兴趣的能够再看一下:聊聊对帐系统的设计方案

画外音:稳妥一点,能够先发起查询,而后根据查询结果更新订单记录。

不过有些极端状况,查询没法获取结果,那么直接更新内部记录便可。

那若是次日也没有这笔记录的结果,这种状况下,咱们能够认为这笔是失败的。若是用户被扣款,渠道端内部将会发起退款,将支付金额返回给用户。因此这种状况能够无需处理。

内部掉单异常

支付公司内部订单关系

接下来咱们讲下内部掉单异常,首先咱们来看下为何会发生内部掉单的异常,这其实跟咱们系统架构有关。

如上图随所示,第三方支付公司内部表一般为支付订单与渠道订单这样一种 1 比 N 的关系。

支付订单保存着外部商户系统的订单号,表明第三方支付公司内部订单与外部商户的订单的关系。

而渠道订单表明着第三方支付公司与外部渠道的关系,其实对于外部渠道系统来说,第三方支付公司就是一个外部商户。

为何须要设计这种关系那?而不是使用下面这种 1 对 1 关系的那?

若是咱们使用上图 1 对1 的订单关系,若是第一次支付支付失败,外部商户可能会再次使用相同订单号对第三方支付公司发起支付。

这时若是第三方支付公司也拿相同的内部订单去请求外部渠道系统,有可能外部渠道系统并不支持同一订单号再次请求。

那其实咱们也有其余办法,生成一个新的内部单号,更新原有支付订单上内部记录,而后去请求外部渠道系统。可是这样的话就会丢失上次支付失败记录,这就不利于咱们作一些过后统计了。

那其实第三方支付公司也能够不支持相同的订单号再次发起请求,可是这样的话,就须要外部商户从新生成的新的订单号。

这样的话,第三方支付公司是系统是简单了,所有复杂度都交给了外部商户。

可是现实的状况,不少外部商户并非那么容易更换生成新的订单号,因此通常第三方支付公司都须要支持同一外部商户订单号在未成功的状况下,支持重复支付。

在这种状况下,就须要咱们上面的 1:N 的订单关系图了。

内部掉单异常的缘由

当咱们收到外部渠道系统的成功的返回信息,成功更新了渠道订单表的记录。可是因为渠道订单表与支付订单表可能不是同一个数据库,也有可能二者并不在同一个应用中,这就有可能致使更新支付订单表的更新失败。

因为支付订单是表保存着外部商户订单与内部订单关系,支付订单未成功,因此外部商户也没法查询获得成功的支付结果。

此时渠道订单表已经成功,因此上面外部掉单的方法并不适用内部掉单。

内部掉单异常解决办法

第一种解决办法,分布式事务。

内部掉单异常,说白就是由于支付订单表与渠道订单表没法使用数据库事务保证二者同时更新成功或失败。

那么这种状况下,咱们其实就须要使用分布式事务了。

不过咱们没有采用这种分布式事务,一是由于以前开发的时候市面上并无开源成熟分布式事务框架,第二本身本身开发难度又很大。

因此对于分布式事务这一块,并无什么使用经验。若是有使用分布式事务解决这类的问题同窗,留言去能够评论一下。

第二种解决办法,异步补偿更新。

当发生内部掉单的状况,即更新支付订单失败等状况,能够将这里支付订单保存到一张内部掉单表。

可是这里可能会有一个问题,咱们没法保证保存到内部掉单表这一步骤也必定成功。

因此说,咱们还须要定时查询,查询一段时间内支付订单未成功,而渠道订单表已成功的支付订单记录,而后也将其插入到内部掉单表。

另外一个系统应用,只须要定时扫描内部掉单表,将支付订单成功,而后再删除内部掉单记录便可。

这里须要注意了,当支付订单表数据量很大以后,定时查询可能会慢,为了防止影响主库,因此这类查询能够在备库进行。

总结

今天主要介绍了支付系统中的掉单异常,这类异常每每会致使用户实际已经被扣钱,可是商户订单仍是等待支付的状况。

这个异常若是没有很好处理,将会致使客户用户体验很很差,还有可能收到客户的投诉。

掉单的异常,一般能够外部系统与内部系统。而大部分的掉单都是由于外部系统致使,咱们能够增长超时时间,掉单查询,以及接受异步通知解决 99% 的问题,剩下 1% 的掉单只能经过第二天的对帐来兜底。

内部系统致使掉单异常是典型的分布式环境数据一致性的问题,这类问题咱们能够不须要追求强一致性,只要保证最终一致性便可。咱们可使用分布式事务解决这类问题,也能够定时扫描状态不一致的订单,而后在作批量更新。

最后,此次只是介绍支付系统中一类掉单异常,下一篇文章中,再给你们介绍一下支付系统的其余异常,敬请期待!

参考资料

  1. 知乎@天顺 谈谈异常(一)

欢迎关注个人公众号:程序通事,得到平常干货推送。若是您对个人专题内容感兴趣,也能够关注个人博客:studyidea.cn

相关文章
相关标签/搜索