我在前一家公司的第一个任务是开发统一支付平台,由于公司的业务需求,需要接入多个第三方支付,之前公司的支付都是散落在各个项目中,及其不利于支付的管理,于是聚合三方支付,统一支付平台的任务就落在我手上,可以说是完全从 0 开始设计,经过一翻实战总结,我得出了一些架构设计上的思考,之前就一直很想把自己的架构设计思路写出来,但一直没动手,前几天在技术群里有人问到相关问题,我觉得有必要把它写出来,以帮助到更多需要开发支付平台的开发人员。
组件模式
由于公司业务在很多地区都有,需要提供多种支付途径,以满足业务的发展,所以设计的支付平台需要接入多种第三方支付渠道,如:微信支付、支付宝支付、PayPal、IPayLinks 等等,我们都知道,每个第三方支付,都有自己一套对外 API,官方都有一套 SDK 来实现这些 API,我们应该如何组织这些 API 呢?
由于第三方支付渠道会随着业务的发展变动,所以组织这些 SDK 就需要在不影响支付平台整体架构的前提下可灵活插拔,这里我使用了组件的思想,将支付 API 拆分成各种组件支付组件、退款组件、订单组件、账单组件等等,那么这样就可以当引入一个第三方支付 SDK 时,可灵活在组件上面添加需要的 API,架构设计如下:
通过 Builder 模式根据请求参数构建对应的组件对象,将组件与外部分离,隐藏组件构建的实现。组件模式 + Builder 模式使得支付平台具备了高扩展性。
多账户体系
在接入各种第三方支付平台,我们当时又遇到一个账户的问题,原因是公司当时的小程序与 APP 使用的是不同的微信账号,因此会出现微信支付会对应到多个账户的问题,而我当时设计支付平台时,没有考虑到这个问题,当时第三方支付只对应了一个账户,而且不同的第三方支付的账户之间相互独立且不统一。
于是我引入了多账户体系,多账户体系最重要的一个核心概念是以账户为粒度,接入多个第三方支付,统一账户的参数,构建了统一的支付账户体系,支付平台无需关心不同支付之间的账户差异以及第三方支付是否有多少个账户。
此时我在支付平台架构图加上账户层:
前端只需要传递 accountId,支付平台就可以根据 accountId 查询出对应的支付账户,然后通过 Builder 模式构建支付账户对应的组件对象,完全屏蔽不同支付之间的差异,在多账户体系里面,可以支持无限多个支付账户,完全满足了公司业务的发展需求。
统一回调与异步分发处理
做过支付开发的同学都知道,目前的第三方支付都有一个特点,就是支付/退款成功后,会有一个支付/退款回调的功能,目的是为了让商户平台自行校验该笔订单是否合法,比如:防止在支付时,客户端恶意篡改金额等参数,那么此时支付成功后,订单会处于支付中状态,需要等待第三方支付的回调,如果此时收到了回调,在校验时发现订单的金额与支付的金额不对,然后将订单改成支付失败,以防止资金损失。回调的思想是只要保证最终的一致性,所以我们调起支付时,并不需要在此时校验参数的正确性,只需要在回调时校验即可。
讲完了回调的目的,那么我们如何来设计支付平台的回调呢?
由于支付平台接入了多个第三方支付,如果此时每个第三方支付设置一个回调地址,那么将会出现多个回调地址,由于回调的 API 必须是暴露出去才能接受第三方的回调请求,所以就会有安全问题,我们必须在 API 外层设置安全过滤,不然很容易出现一些非法访问暴力破解,所以我们需要统一回调 API,统一做安全校验,之后再进行一层分发。
分发的机制我这里建议用 RocketMQ 来处理,可能有人会问,如果用 RocketMQ 来做分发处理,此时怎么实时返回校验结果到第三方支付呢?这个问题也是我当时一直头疼的问题,以下是我对回调设计的一些思考:
聚合支付
请求处理
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);