前语

本文主要聊付出领域中的这三个反常场景,分享付出体系中针对下列反常的一些处理方法:

  • 用户明明付款成功,银行卡都扣款了,可是订单却还显现待付款。即掉单的场景。
  • 重复付款的反常,即同一笔订单,扣了用户两笔钱的场景。
  • 用户扣款成功,可是订单却付出失利的场景。

其实这些处理方法并不只是局限于付出体系,也能够适用于其他体系,咱们能够借鉴,运用到自己体系中,进步自己体系的健壮性。

反常是体系运行不可避免会产生的问题,假如一切都正常,咱们的体系规划将会适当简单。

可是可惜没有人能做到这一点,所以为了处理反常或许导致的问题,咱们不得不需求加上许多额外的规划,用来应对这些反常。

能够说体系规划中,反常处理需求咱们着重思考,它将会占据咱们大部分的精力。

掉单反常

一个最常见的付出途径架构联系如下所示:

Java架构-支付设计之路

上图咱们是站在第三方付出公司付出角度,假如是自己公司的内部付出体系,那么外部商户这一块其实便是公司内部一些体系,比方说订单体系,而外部付出途径其实便是第三方付出公司

咱们以携程为例,在其上面建议一笔订单付出,将会经过三个体系:

  • 1.携程创立订单,向第三方付出公司建议付出恳求
  • 2.第三方付出公司创立订单,并向工行建议付出恳求
  • 3.工行完结扣款操作,回来第三方付出公司
  • 4.第三方付出完结订单更新并回来携程
  • 5.携程变更订单状况

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

Java架构-支付设计之路

在这个进程就或许会碰到,用户工行卡现已扣款,可是携程订单却仍是待付出,咱们一般将这种状况称为「掉单 」。

上述掉单的场景,大都是由于「③、⑤」环节信息丢掉导致,这种掉单咱们将其称为「外部掉单 」。

还有一种极少数的状况,收到 「③、⑤」环节回来信息,可是在「④、⑥」环节内部体系更新订单状况失利,然后导致丢掉付出成功的信息,这类掉单由所以内部问题,咱们一般将其称之为「内部掉单 」。

外部掉单

外部掉单是由于没有收到对端回来信息。

这种状况极有或许是网络问题,也有或许对端处理逻辑太慢,导致我方恳求超时,直接断开了网络恳求。

计划一:添加超时时刻

关于这种状况,第一个最简单的处理方法,「适当的添加超时时刻 」。

不过这儿需求留意了,在咱们添加网络超时时刻之后,咱们或许还需求调整整个链路的超时时刻,否则有或许导致整个链路内部差事然后引起内部掉单。

画外音:对接外部途径,必定要「设置网络连接超时时刻与读取超时时刻」。

计划二:接纳异步告诉

第二个方法,接纳途径异步回执告诉信息。

一般来说,现在付出途径接口咱们都能够上送一个异步回调地址,当途径端处理成功,将会把成功信息告诉到这个回调地址上。

这种状况下,咱们只需求接纳告诉信息,然后解析,再更新内部订单状况。

Java架构-支付设计之路

这种状况下,咱们需求留意下面两点:

  • 关于异步恳求信息,必定需求对告诉内容进行签名验证,并校验回来的订单金额是否与商户侧的订单金额一致,避免数据走漏导致出现“假告诉”,形成资金丢失。
  • 异步告诉将会发送多次,所以异步告诉处理需求幂等。
计划三:掉单查询

有的途径或许没有提供异步告诉的功用,只提供了订单查询的接口,这种状况下,咱们只能运用第三种处理方法,守时掉单查询。

咱们能够将这类超时未知的订单的单独保存到掉单表,然后守时向途径端查询订单的状况。

若查询成功或许清晰失利(比方订单不存在等),能够更新订单状况,而且删除去单表记载。

必定必定必定要特别特别特别留意这个订单不存在的状况。有或许你去查询的时分,查询恳求在对方数据还未落库之前就到了,导致对方查不到数据回来订单不存在,可是其实订单是存在的。所以需求意识到“订单不存在”有或许是一个很风险的场景。

若查询依旧未知,这时咱们需求等候下次查询的成果。

Java架构-支付设计之路

这儿咱们需求留意了,有些状况下,有或许无法查询回来订单的状况,所以咱们需求设置订单查询的最大次数,避免无限查询浪费性能。

计划四:对账

终究,极少数的状况下,订单查询与异步告诉都无法获取的付出成果,这就还剩下终究一种兜底的处理方法,对账。

假如第二天途径端给的对账文件有这一笔付出成果,那么咱们能够依据这个记载更新直接更新咱们内部付出记载。

为了稳妥一点,能够先建议查询,然后依据查询成果更新订单记载。

不过有些极点状况,查询无法获取成果,那么直接更新内部记载即可。

那假如第二天也没有这笔记载的成果,这种状况下,咱们能够认为这笔是失利的。

假如用户被扣款,途径端内部将会建议退款,将付出金额回来给用户。所以这种状况能够无需处理。

内部掉单反常

付出公司内部订单联系

接下来咱们讲下内部掉单反常,首要咱们来看下为什么会产生内部掉单的反常,这其实跟咱们体系架构有关。

Java架构-支付设计之路

如上图所示,第三方付出公司内部表一般为付出订单与途径订单这样一种 1 比 N 的联系。

付出订单保存着外部商户体系的订单号,代表第三方付出公司内部订单与外部商户的订单的联系。

而途径订单代表着第三方付出公司与外部途径的联系,其实关于外部途径体系来讲,第三方付出公司便是一个外部商户。

为什么需求规划这种联系那?而不是运用下面这种 1 对 1 联系的呢?

Java架构-支付设计之路

假如咱们运用上图 1 对1 的订单联系,假如第一次付出付出失利,外部商户或许会再次运用相同订单号对第三方付出公司建议付出。

这时假如第三方付出公司也拿相同的内部订单去恳求外部途径体系,有或许外部途径体系并不支撑同一订单号再次恳求。

那其实咱们也有其他方法,生成一个新的内部单号,更新原有付出订单上内部记载,然后去恳求外部途径体系。

可是这样的话就会丢掉前次付出失利记载,这就不利于咱们做一些过后统计了。

那其实第三方付出公司也能够不支撑相同的订单号再次建议恳求,可是这样的话,就需求外部商户从头生成的新的订单号。

这样的话,第三方付出公司是体系是简单了,悉数复杂度都交给了外部商户。

可是现实的状况,许多外部商户并不是那么容易更换生成新的订单号,所以一般第三方付出公司都需求支撑同一外部商户订单号在未成功的状况下,支撑重复付出。

在这种状况下,就需求咱们上面的 1:N 的订单联系图了。

内部掉单反常的原因

当咱们收到外部途径体系的成功的回来信息,成功更新了途径订单表的记载。可是由于途径订单表与付出订单表或许不是同一个数据库,也有或许两者并不在同一个运用中,这就有或许导致更新付出订单表的更新失利。

Java架构-支付设计之路

由于付出订单表保存着外部商户订单与内部订单联系,付出订单未成功,所以外部商户也无法查询得到成功的付出成果。

此刻途径订单表现已成功,所以上面外部掉单的方法并不适用内部掉单。

计划一:分布式业务

内部掉单反常,说白便是由于付出订单表与途径订单表无法运用数据库业务确保两者同时更新成功或失利。

计划二:异步补偿更新

当产生内部掉单的状况,即更新付出订单失利等状况,能够将这儿付出订单保存到一张内部掉单表。

可是这儿或许会有一个问题,咱们无法确保保存到内部掉单表这一步骤也必定成功。

所以说,咱们还需求守时查询,查询一段时刻内付出订单未成功,而途径订单表已成功的付出订单记载,然后也将其刺进到内部掉单表。

另一个体系运用,只需求守时扫描内部掉单表,将付出订单成功,然后再删除内部掉单记载即可。

这儿需求留意了,当付出订单表数据量很大之后,守时查询或许会慢,为了避免影响主库,所以这类查询能够在备库进行。

掉单小结

前面主要介绍了付出体系中的掉单反常,这类反常往往会导致用户实际现已被扣钱,可是商户订单仍是等候付出的状况。

这个反常假如没有很好处理,将会导致客户用户体验很欠好,还有或许收到客户的投诉。

掉单的反常,一般能够外部体系与内部体系。

而大部分的掉单都是由于外部体系导致,咱们能够添加超时时刻,掉单查询,以及承受异步告诉处理 99% 的问题,剩下 1% 的掉单只能经过次日的对账来兜底。

内部体系导致掉单反常是典型的分布式环境数据一致性的问题,这类问题咱们能够不需求追求强一致性,只需确保终究一致性即可。

咱们能够运用分布式业务处理这类问题,也能够守时扫描状况不一致的订单,然后在做批量更新。

接着,咱们聊别的一类付出场景下的反常。

重复付款反常

重复付款反常一般常见于网银付出,微信付出,付出宝等这类需求跳转到一个付出网关页(网银付出),或许跳转到钱包 APP(付出宝、微信),然后异步完结扣款的付出场景。

Java架构-支付设计之路

这种付出场景下,只能经过承受异步告诉才干知道付出成果,咱们一般将其称为异步付出。

别的,多说一句:有了异步付出,那么同步付出是什么?

其实同步付出指的便是调用付出接口之后,就能够立刻回来付出成果的,比方银行卡类方便/代扣等付出便是同步付出。

当然也有一些奇葩的银行卡付出途径,同步付出成果为受理成功,只能承受异步告诉或许查询回来付出成果。

由于银行卡付出需求回来清晰付出成果,关于这类途径只能内部规划将异步转为同步。

好了,说回来。

后台付出流程如下:

Java架构-支付设计之路

为什么会产生重复付款?

主要原因其实跟前次内部掉单反常相同,跟业务表规划有关。

前次咱们说到,付出体系主要表结构如下:

Java架构-支付设计之路

在这个表结构下,只需付出订单未成功,商户就能够重复运用其内部同一订单号调用付出接口。

假定这样一个场景,用户在收银台付出时挑选招行进行网银付出,当他点击付出之后,商户体系将会调用付出公司的网银接口。

这时付出体系内部将会创立一笔付出单以及相关的途径订单,而且调用招行体系的接口。

然后用户的浏览器页面将会翻开一个新页面,然后跳转到招行网站。

这时假如此刻用户再次在收银台点击付出,将会再次调用付出体系接口。

这时分由于付出单已存在,所以只是会再创立一条途径订单记载,而且调用招行体系的接口。这时用户的浏览器将会再次翻开一个招行的网站。

假如用户在两个招行网银页都完结付出,这时就产生了重复付款。

上面这种场景看起来有点傻,可是真实用户操作真的会产生。

除了这种,博客园上的小伙伴还说到这么下面这种状况:

Java架构-支付设计之路

重复付款-处理方法

重复付款反常的主要的处理方法有两种,分为事前与过后。

事前主要的目是尽或许避免用户重复付款,主要处理方法为优化付款页面,尽或许做好提示。

第一种优化方法,付款页面直接跳转到第三方/银行的网银页面,不要翻开新的页面去跳转。

Java架构-支付设计之路

这种方法能够避免用户误翻开两个网银付款的页面,然后导致重复付款。

可是这儿会有一个问题,银行网银页面付款成功之后,用户怎么知道其在商户侧订单状况也成功了?

其实很简单,现在网银付出接口,一般都会有一个参数:

return_url:同步跳转地址。

比方,付出宝开发文档就有这个字段:

Java架构-支付设计之路

只需在接口传入这个地址,当付出成功之后,页面终究就会跳转到这个传入的地址,商户侧就能够在地址显现订单是否付出成功。

Java架构-支付设计之路

上面咱们说到,用户有或许会运用浏览器回退功用,跳转到付出页,然后导致重复付款。

关于这种状况,咱们能够在其回退付出页时,首要向后台查询这笔订单付出成果,假如已付出成功,那就直接显现成功页面。

第二种优化方法,关于这种从头翻开一个页面跳转到银行网站,咱们能够在页面加入弹窗提示,问询用户是否已付出完结。

Java架构-支付设计之路

比方上面这种处理方法,当用户点击承认完结充值,能够立刻向后台建议查询订单状况。

下面来聊聊过后的处理方法。

其实处理方法很简单,建议内部退款,将剩下付出的一笔反向退款回去。

付出体系内部能够有个守时使命,守时扫描付出单下有多条成功途径订单的记载,然后挑选将重复付出途径订单建议退款。

这种方法是付出公司体系内部的操作,不需求商户侧建议指令。

订单失效反常

这种场景一般常见于电商购物,秒杀等购物场景。当用户下单之后,页面将会开始倒计时,用户需求在有效期内付出成功。

Java架构-支付设计之路

假定用户点击跳转到付出宝,可是其没有立刻付出,而是停留了好久,在订单终究一秒时刻内完结了付出,可是这个时分订单早已由于时刻到期而被自动取消。

这样就产生用户扣款现已成功,可是订单却是失利或封闭的场景的。

别的还有一种状况,用户在有效期内付出成功,可是由于网络、内部运用等问题,付出成果的异步告诉过了好久才收到,这时内部订单早由于时刻到期而被取消。

订单失效反常-处理方法

第一种处理方法,上送有效期给付出途径。

一般付出接口都会有一个付出有效期的字段,表明这笔付出最晚能够付出的时刻。假如超时未付出,这笔付出将会被封闭。

比方,付出宝开发文档就有这个字段:

Java架构-支付设计之路

当然一般状况下,假如未上送,这个字段内部一般会有个默认的有效期,比方 3 天,这个时刻就比较长了。

所以当调用付出接口时,能够将订单剩下有效期传入付出接口。这样用户假如在超时时刻内未完结付出,付出将会失利。

第二种处理方法,内部建议退款。

这个处理方法依然过后托底的处理方法,关于付出订单已封闭,可是付出却成功的状况,建议内部退款,将钱退给用户。

内部能够有个守时使命,守时扫描付出订单已封闭可是付出却成功的状况,然后建议退款指令。

终究

终究用思维导图方法帮咱们总结一下付出体系或许会碰到的反常。

Java架构-支付设计之路