公司有一项储值卡充值业务:客户在微信公众号开通储值卡服务,经过微信支付往卡里面充值,充值成功后客户可收到消息通知,并进行消费。前端
看起来是一项很简单的业务,最初咱们储值卡团队的实现也确实很简单。咱们看看最初的实现:
数据库
相信聪明的你一眼就能看出问题:服务器
看到这里你可能会大呼开发人员是否是没长脑子?微信
实际状况是,这个版本的开发是几年前的事情了,那时候公司仍是创业早期,第一目标是尽快上线能用,并且客户量没有那么大,虽然中间也出现过一些数据不一致的状况,也都经过人工处理了事了。网络
随着公司业务的发展,用户量愈来愈大,并且还要和第三方合做(储值卡做为一种支付方式提供给第三方使用),问题出现得也愈来愈频繁,不得不将这块提上重构议程。分布式
那么,针对上面提的几点问题,咱们大致能想到以下重构项:微信支付
初步设计以下:
设计
这里咱们重点讨论下对第 14 步(卡充值接口返回结果)的处理:3d
骚年你等等!
你说什么?重试失败了就去退款?blog
实践中,远程调用失败的一个很大缘由是网络超时(而超时的很大缘由又是对方负载太高),而面对超时,咱们是不知道对方到底有没有处理成功的,万一这边把钱退掉了,那边又充值成功咋办?(咱们是 SaaS 服务商,这时真正的损失方是咱们的商户,而商户无疑会找咱们索赔的)
一种方案是:
在屡次重试失败后发起微信退款以前,先调卡系统查询接口,若是查询结果是充值成功,则不退款,继续后续流程,不然发起退款;
该方案在实际中也基本行不通,由于若是那段时间网络有问题或者对方服务器负载高,查询也有很大几率失败,或者就算查成功了并返回充值记录不存在,也有可能以前调的充值接口还在跑(好比处于锁等待状态)。
有人可能会说,不要紧啊,就算退款后充值成功了,那后面经过人工或者系统发现数据问题再处理掉不就好了吗?
问题在于,若是在发现问题以前,用户已经从卡上消费掉了呢(好比用户当场冲1000 而后立马消费掉,这在咱们实际场景中是常常发生的,由于不少商户会搞充值活动,好比冲1000 送 200)?把卡余额扣成负数?(这不是我杜撰的,在咱们老储值卡系统就出现过几回这种状况,当时是直接由公司给商户赔钱)
所以,关键在于,当充值中心不知道卡系统有无充值成功的状况下,须要内部假定充值成功了。
最终,咱们决定用定时任务来解决。在微信支付回调中,若是屡次调卡充值接口失败,咱们不发起退款,也不进行后续流程,而是在数据库中写入一条异常记录,而后结束本次处理。
在定时任务中(好比 10 分钟一次),咱们取出那些异常记录,调卡系统相关接口核对最终状态,若是充值成功了,则补充执行充值成功的后续流程,不然发起微信退款,并执行其余充值失败流程(如改订单状态,给用户发通知、回调业务系统等)。
为了防止钱退了后卡又充值成功,定时任务中只处理 1 小时前的数据。
另外一个隐藏的问题是,在前面的充值流程中,直到微信支付回调,卡系统都没有关于此次充值行为的任何记录。这可能会致使后续一系列问题,其中一个问题是,在最初下单(步骤 5)到最终充值(步骤 13)这段时间内,一旦任何变量(充值规则)发生改变,此次充值就有可能会失败(或者致使数据差错)。这个时间差短则几十毫秒,长则几分钟十几分钟都有可能。另外一个次要问题是,一旦发生充值异常,卡系统自身是不知情的(由于没有任何记录),对卡系统的任何查询也都不会反映此次充值行为。
为了解决该问题,咱们引入预充值的概念。在下单后调微信支付前,先同步调卡系统的预充值接口,该接口计算充值合法性并生成一条预充值记录,该记录包含充值帐号、充值金额、支付金额、充值单号等关键信息,状态为“充值中”。
在微信支付回调中,将预充值状态改为“充值成功”,并处理一些其余逻辑。
综合,最终方案如图: