跟我一步步探寻打款系统的建立过程

1.项目背景
初始阶段
业务方订单审核通过后,会有离线任务不断轮训向支付中心发起调用,支付中心打款处理完成后会返回ifSuccess(是否落库),state,code,error Message等。如果落库且code为打款成功,订单业务状态会修改为打款成功。

发展阶段
为了配合业务发展,会增加各种活动来拉动订单量,有些活动有打款需求,因为业务处于快速发展期,实现方案都是在业务订单向支付中心发起调用后,把与订单有关的活动打款也向支付发起调用。待打款都成功后,再分别将业务订单和活动的状态置为打款成功。

病痛阶段
1.支付中心打款成功需要满足很多条件比如:为每个场景分配的财务编码需要在有效期,财务编码额度需要足够,用户需要有第三方的openId进行打款等。一旦某个打款情况出现问题会导致,其余相关的业务都会被阻塞(主业务订单状态的修改会在最后,因为离线任务是轮训的是未打款成功的订单,即与订单相关活动的打款依赖于订单的重试功能)

2.相关代码耦合越发严重,订单发起打款的代码里冗杂了越来越多各个活动的打款代码,后续开发和维护成本越来越高。

3.打款异常情况越发增多,每天都需要rd分摊很多时间去帮助客服查询订单未完结或者没有收到某个打款的原因。

尝试阶段
1.通过类似spring的扩展点模式,将各个活动的打款代码抽离出去,从代码可读性和易维护开发性上讲好了许多,但是依然会有订单状态阻塞情况出现,各个打款直接也会相互影响,排查问题也会较为困难。

2.提供客服查询工具,让用户可以根据订单id查询业务订单和相关活动的打款情况。但是增加新的活动还需要继续完善客服查询工具,且当调用支付中心因为用户账号没有落库(落库后支付中心会通过各种机制保障打款成功)时,失败信息只会记录在日志(没有被收集在hive)中,两边都不可查询。当客服提供一个很久之前的打款问题订单时,查询会非常麻烦。

2.方案设计目标
解除耦合
剥离业务订单打款与活动打款的耦合,保障业务订单打款一定成功,结合业务情况考虑,不对活动打款进行强一致处理,活动打款失败的少数情况,由客服处理。

方便查询
之前打款问题查询困难,占用开发时间过多,后续活动打款需要新增加开发时间,有一些问题查询困难,希望可以给客服更方便的查询打款方式

功能内聚
新开发的打款方案要做到方便业务调用,内部的逻辑与业务松耦合,只需要提供给业务插入打款和查询打款的能力

监控预警
通过聚拢各个情况下的打款,做到统一监控,配合统计日志,设置关键项目(财务编码过期,打款金额)的报警及监控,统计错误打款邮件发送的功能

3.打款方案
在这里插入图片描述
4.迭代过程
版本1
为了保障系统安全平稳上线,采用小步迭代的方式,先选取一个活动调用打款服务,其余的打款情况依然调用支付中心。打款服务提供插入接口插入待打款记录和离线任务轮训打款记录发起打款。

为了保证打款id的全局唯一性(当然也可以使用更好地保障全局唯一的id生成方式),也为了做到业务的高度聚合,打款id由之前的使用各个打款情况的业务id变为在插入打款记录时生成打款id。

插入接口参数:用户id,业务类型,打款计划id,金额,业务id,开始打款时间(可以为空),打款描述(将会展示在用户的微信收款记录中),mchid,订单id

业务类型与业务id会在数据库中设置唯一索引,来保障唯一性。通过业务类型的方式也避免了不同业务可能存在的业务id冲突的问题,也可以使得打款服务可以支撑未来其它业务的接入。

开始打款时间可以不传,传递时离线任务只会查询开始打款时间在当前时间之前的。

打款描述,打款计划id,mchid为业务方属性,打款服务不与业务耦合,所以由业务方传递。

订单id可以不传,传递时是为与某个订单相关的打款,不传时视为单独的打款记录。

离线任务轮选打款服务
在这里插入图片描述

离线任务应用与打款服务之间采取逐笔调用的方式是为了避免数据过大引发接口超时。最终job执行成功后,也会在任务平台可以观测到job的运行情况,如果任务执行超时也会影响观测结果。在调用支付中心后不再像之前一样只根据打款是否成功进行状态变化,会把支付返回来的所有错误code和message记录在这条打款记录的数据中。也会记录相应的统计日志,供数据平台抓取。

第一步的设计过程中有一个难点,之前的业务是直接用业务id调用支付中心的,每一笔调用虽然幂等但是不能保证立即打款给用户,而业务订单状态和活动打款状态都会在支付中心返回确认打款成功之后,才变为打款成功状态,否则业务会重试调用支付。因为现在打款服务的插入接口中会自己生成打款id,如果打款id不同,就有可能造成多次打款。我们考虑了两种解决方案,第一种是数据迁移,这种方案在迁移过程中需要暂停线上业务的打款,需要人工关注各个业务的数据是否正常,在对用户的影响和人工消耗方面都是很大的。第二种方案,在打款服务插入接口中,冗余一部分代码来处理兼容,每次插入记录之前都先用业务id去支付中心查询,如果能查到结果(说明曾经用它作为打款id打款),则把业务id同同时也作为打款id记录。

版本2
在版本1上线稳定运行一段时间后,打款服务已经做好了接入其它打款场景的准备。根据现有业务情况,我们选择业务订单的打款同步调用打款服务插入接口,其余订单关联活动在消费订单状态变化的mq时调用打款服务插入接口。这样既实现了业务订单与订单关联活动的解耦,也使得活动的代码内聚,从而减少了问题排查的成本和后期代码的维护工作量。

版本3
当打款的事情都完成后,就开始着手提供给客服更好地查询工具以缓解开发人工介入的成本。之前的查询工具问题在于,打款失败问题不明确,有新的活动都需要重新增加查询代码。

在打款服务插入接口中,我们允许业务方传入订单id,我们会将传入的订单id记录在该条记录当中,查询打款记录时,只需要提供订单id,就可以将相关的打款一连串的查询出来,我们只需要将业务类型对应的实际活动告知客服方或者在前端代码中增加一个枚举项即可,打款失败的错误信息因为完善的记录,我们可以都展示给客服,客服可以根据明确的问题,直接找到问题解决方,避免了业务方开发在中间做中介的人工消耗。

版本四
打款服务上线一段时间后,一个用户体验问题暴露了出来,为了代码高度解耦,最初的设计中,只要业务订单调用打款服务插入接口返回成功后,业务订单就可以将订单状态置为打款成功,由打款服务保障段成功。但是这么做可能会在一些场合下,用户看到订单状态已经是打款成功了,但是实际上没有收到钱,会给用户很不好的用户体验。

我们采取在打款服务中提供打款查询接口,根据业务类型和业务id查询打款情况,业务订单在轮训未打款成功订单,插入打款服务后,会接着调用查询打款服务,只有查询结果为打款成功后才将业务订单状态修改为打款成功。

版本五
版本五迎来了最大的变化,原本的打款服务只支持微信打款,而随着业务的拓展未来可能会有qq打款和支付宝打款的情况。为了实现qq生态和阿里生态的业务推广,对应的打款服务也需要具备这样的能力。

根据业务情况,微信打款和qq打款是获得用户的微信或者qq的openid进行打款,可以作为常规打款渠道,支付宝打款是让用户填写支付宝账号和姓名进行打款,只能是用户绑定微信或者绑定qq账户失效后的一个备用渠道,且出于安全考虑用户输入一次支付宝打款信息,只能将用户填写之前的带打款金额打给用户,下次需要支付宝打款时,需要用户重新绑定支付宝。

为了满足上面说的业务场景,我们增加了一个用户支付宝信息记录表,以及一个支付宝打款离线任务,插入接口新增参数:渠道是否明确,打款方式,打款计划与渠道的对应map,old打款id,业务打款创建时间。

之前的离线打款任务也做了一些调整,根据业务情况。即使用户绑定了支付宝,我们也要优先给用户使用微信或者qq打款。在打款时会先判断渠道是否明确(微信或qq生态),如果渠道明确则沿用之前的打款逻辑,如果渠道不明确,先尝试微信打款,查询没有打款成功后,再次尝试qq打款,查询没有打款成功后,修改记录状态为待支付宝打款。

在这里插入图片描述

支付宝离线任务会查询启用状态的支付宝账户记录,然后根据记录去找寻在该记录创建时间之前存在的待打款记录,修改改记录的状态,并且新插入一条打款记录(渠道明确指定支付宝打款)。后续流程可以沿用之前的设计。

在这里插入图片描述

有三个在设计点在设计之时着重考虑了下,从尝试给未知渠道打款到生成新的打款记录都用的是同一个打款id,(支付宝离线任务会在添加记录时把原打款id传入)而没有在新生成的时候沿用版本一的方式新生成打款id,就是希望通过打款id的一致确保不会因为异常情况出现多打款的情况。

插入接口需要传入打款计划和业务对应关系map,是因为不同的业务不同去打的打款计划id可能会有多个,在尝试打款的情况下,业务方并不会指明打款方式和传入唯一的打款计划id,如果再打款服务内记录每个活动可能的打款计划id,会让打款服务与业务高度耦合。所以通过{“weixin”:123,“qq”,231}类似的方式来获得。

支付宝信息记录表设置中间状态,在中间状态时不允许用户绑定新的支付宝账户。是担心用户在支付宝离线任务运行过程中绑定新的支付宝,或者在job运行期间还有新的订单。 造成用户认为的支付宝打款账户和实际不一致,或者订单漏打的情况。

状态流转图
在这里插入图片描述

结语 迭代是没有尽头的,写此文的时候就已经有了不少新改动需要设计了。希望这个系统可以越来越完善,也希望此文可以给大家参考。