我现所在公司是开发统一支付平台,由于公司的业务需求,需要接入多个第三方支付,为了后续对支付平台更深入的思考,整理出来。
由于公司业务在很多地区都有,需要提供多种支付途径,以满足业务的发展,所以设计的支付平台需要接入多种第三方支付渠道,如:微信支付、支付宝支付、卡联支付、迅联 等等,我们都知道,每个第三方支付,都有自己一套对外 API,官方都有一套 SDK 来实现这些 API,我们应该如何组织这些 API 呢?
由于第三方支付渠道会随着业务的发展变动,所以组织这些 SDK 就需要在不影响支付平台整体架构的前提下可灵活插拔,这里我使用了组件的思想,将支付 API 拆分成各种组件支付组件、退款组件、订单组件、账单组件、订单异常处理组件等等,那么这样就可以当引入一个第三方支付 SDK 时,可灵活在组件上面添加需要的 API,架构设计如下:
通过Builder模式根据请求参数,构建对应的组件对象,将组件与外部分离,隐藏组件构建的实现。 组件模式+builder模式使得支付平台具备了高扩展性。
对接第三方支付都有一个特点,就是支付、退款成功后,会有一个支付、退款的回调功能,目的是:1、让商户平台自行校验订单的合法性,比如:防止支付时,客户端恶意篡改金额等参数,那么此时支付成功后订单处于支付中状态,需要等待第三方回调,如果收到回调校验发现支付金额和订单金额不对,我们可以把订单状态改为失败,防止资金损失。2:通过回调我们可以处理我们自己系统的业务逻辑,商品的分发等等。回调的思想只要保证数据的最终一致性,所以我们发起支付时不需要校验参数的正确性,只需在回调时校验。那我们应该如何来设置支付平台的回调。
由于支付平台需要接入多个第三方支付,如果此时每个第三方支付设置一个回调地址,那么将会出现多个回调地址,由于回调的API必须是暴露出去才能接受第三的回调,所以就会有安全问题,我们必须在API外层设置安全过滤,所以我们需要统一回调API,统一做安全校验,之后再进行一层分发。
分发机制我们采用的是RabbitMq,这里可能会有疑问,如果用mq来做分发处理,此时怎么实时返回校验结果给第三方?以下是对回调的一些思考:
1.系统是基于Spring boot微服务架构,服务之间通过Http通信,如果用Http分发,可以保证消息返回的实时性,但由于网络不稳定,会出现请求失败或超时的问题,接口的稳定性得不到保障。 2. 由于第三方支付如果收到false响应,就会在接下来的一段时间内再次发起回调请求,这么做的目的是为了保障回调的成功率,对于第三方支付来说,这没毛病,但对于商户支付平台来说,也许就是一个比较坑爹的设计,你想一下,假设有一笔订单在支付时恶意篡改了金额,回调校验失败,返回 false 到第三方支付,此时第三方支付会再重复发送回调,无论发送多少次回调,都会校验失败,这就额外增加了不必要的交互,当然这里也可以用幂等作处理,以下是微信支付回调的应用场景说明:
基于以上两点思考,我认为返回 false 到第三方支付是没必要的,为了系统的健壮性,我采用了消息队列来做异步分发,支付平台收到回调请求后直接返回 true,这时你可能会提出一个疑问,如果此时校验失败了,但此时返回 true,会不会出现问题?首先,校验失败情况,订单必定是处于支付失败的状态,此时返回 true 目的是为了减少与第三方支付不必要的远程交互。
因为Mq是把消息持久化到磁盘的,所以消息队列来做分发的最大好处,就是复查消息队列里面的消息来排查问题,而且消息队列可以在业务高峰期进行流量削峰。
以下是统一回调和分发的架构图:
支付平台聚合了多种第三方支付,因此在请求层需要做很多的适配工作,以满足多种支付的需求,可能你会想,直接在适配那边加几行if else 不就得了吗,这么做也没问题,也可以满足多种支付的需求,但如果再加一个第三方支付呢,你只能在原有方法上加多个else条件,这么会导致请求层代码不断随着业务发展改变,使得代码极其不优雅,而且也不方便维护,这时我们可以用策略模式,将这些if else代码消除,当我们增加一个第三方支付时,我们只需要新建一个strategy类就可以了,架构如下:
由于支付平台涉及到资金,支付的各种请求和返回,以及异常记录在一个支付平台中异常重要,因此我们需要记录每一次的支付请求记录,以便后续排查问题。
基于这一点,我们在开始请求第三方支付之前,设计一层handler层,所有请求都必须经过Handler层处理,Handler核心方法如下:
public K handle(T t) { K k; try { before(t); k = execute(t); after(k); } catch (Exception e) { exception(t, e); } return k; } protected abstract void before(T t); protected abstract void after(K k); protected abstract void exception(T t, Exception exception);
Handler层利用了模板模式,不仅可以实现日志的记录,还可以实现多种处理方式,比如请求监控,消息推送等等,实现了handler层的高扩展性