我对支付平台架构设计的一些思考

微信公众号「后端进阶」,专一后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。
老司机倾囊相授,带你一路进阶,来不及解释了快上车!前端

我在前一家公司的第一个任务是开发统一支付平台,因为公司的业务需求,须要接入多个第三方支付,以前公司的支付都是散落在各个项目中,及其不利于支付的管理,因而聚合三方支付,统一支付平台的任务就落在我手上,能够说是彻底从 0 开始设计,通过一翻实战总结,我得出了一些架构设计上的思考,以前就一直很想把本身的架构设计思路写出来,但一直没动手,前几天在技术群里有人问到相关问题,我以为有必要把它写出来,以帮助到更多须要开发支付平台的开发人员。java

组件模式

因为公司业务在不少地区都有,须要提供多种支付途径,以知足业务的发展,因此设计的支付平台须要接入多种第三方支付渠道,如:微信支付、支付宝支付、PayPal、IPayLinks 等等,咱们都知道,每一个第三方支付,都有本身一套对外 API,官方都有一套 SDK 来实现这些 API,咱们应该如何组织这些 API 呢?小程序

因为第三方支付渠道会随着业务的发展变更,因此组织这些 SDK 就须要在不影响支付平台总体架构的前提下可灵活插拔,这里我使用了组件的思想,将支付 API 拆分红各类组件支付组件、退款组件、订单组件、帐单组件等等,那么这样就能够当引入一个第三方支付 SDK 时,可灵活在组件上面添加须要的 API,架构设计以下:后端

经过 Builder 模式根据请求参数构建对应的组件对象,将组件与外部分离,隐藏组件构建的实现。组件模式 + Builder 模式使得支付平台具有了高扩展性。设计模式

多帐户体系

在接入各类第三方支付平台,咱们当时又遇到一个帐户的问题,缘由是公司当时的小程序与 APP 使用的是不一样的微信帐号,所以会出现微信支付会对应到多个帐户的问题,而我当时设计支付平台时,没有考虑到这个问题,当时第三方支付只对应了一个帐户,并且不一样的第三方支付的帐户之间相互独立且不统一。安全

因而我引入了多帐户体系,多帐户体系最重要的一个核心概念是以帐户为粒度,接入多个第三方支付,统一帐户的参数,构建了统一的支付帐户体系,支付平台无需关心不一样支付之间的帐户差别以及第三方支付是否有多少个帐户。微信

此时我在支付平台架构图加上帐户层:网络

前端只须要传递 accountId,支付平台就能够根据 accountId 查询出对应的支付帐户,而后经过 Builder 模式构建支付帐户对应的组件对象,彻底屏蔽不一样支付之间的差别,在多帐户体系里面,能够支持无限多个支付帐户,彻底知足了公司业务的发展需求。架构

统一回调与异步分发处理

作过支付开发的同窗都知道,目前的第三方支付都有一个特色,就是支付/退款成功后,会有一个支付/退款回调的功能,目的是为了让商户平台自行校验该笔订单是否合法,好比:防止在支付时,客户端恶意篡改金额等参数,那么此时支付成功后,订单会处于支付中状态,须要等待第三方支付的回调,若是此时收到了回调,在校验时发现订单的金额与支付的金额不对,而后将订单改为支付失败,以防止资金损失。回调的思想是只要保证最终的一致性,因此咱们调起支付时,并不须要在此时校验参数的正确性,只须要在回调时校验便可。框架

讲完了回调的目的,那么咱们如何来设计支付平台的回调呢?

因为支付平台接入了多个第三方支付,若是此时每一个第三方支付设置一个回调地址,那么将会出现多个回调地址,因为回调的 API 必须是暴露出去才能接受第三方的回调请求,因此就会有安全问题,咱们必须在 API 外层设置安全过滤,否则很容易出现一些非法暴力访问,因此咱们须要统一回调 API,统一作安全校验,以后再进行一层分发。

分发的机制我这里建议用 RocketMQ 来处理,可能有人会问,若是用 RocketMQ 来作分发处理,此时怎么实时返回校验结果到第三方支付呢?这个问题也是我当时一直头疼的问题,如下是我对回调设计的一些思考:

  1. 公司的系统是基于 SpringCloud 微服务架构,微服务之间经过 HTTP 通讯,当时有不少个微服务接入了个人支付平台,若是用 HTTP 做分发,能够保证消息返回的实时性,但也会出现一个问题,因为网络不稳定,就会出现请求失败或超时的问题,接口的稳定性得不到保障。
  2. 因为第三方支付若是收到 false 响应,就在接下来一段时间内再次发起回调请求,这么作的目的是为了保证回调的成功率,对于第三方支付来讲,这没毛病,但对于商户支付平台来讲,也许就是一个比较坑爹的设计,你想一下,假设有一笔订单在支付时恶意篡改了金额,回调校验失败,返回 false 到第三方支付,此时第三方支付会再重复发送回调,不管发送多少次回调,都会校验失败,这就额外增长了没必要要的交互,固然这里也能够用幂等做处理,如下是微信支付回调的应用场景说明:

基于以上两点思考,我认为返回 false 到第三方支付是不必的,为了系统的健壮性,我采用了消息队列来作异步分发,支付平台收到回调请求后直接返回 true,这时你可能会提出一个疑问,若是此时校验失败了,但此时返回 true,会不会出现问题?首先,校验失败状况,订单一定是处于支付失败的状态,此时返回 true 目的是为了减小与第三方支付没必要要的远程交互。

由于 RocketMQ 的消息是持久化到磁盘的,因此用消息队列来作异步分发最大的好处,就是能够复查消息队列里面的消息来排查问题,并且消息队列能够在业务的高峰期进行流量削峰。

如下是统一回调与分发处理的架构设计图:

聚合支付

支付平台聚合了多种第三方支付,所以在请求层须要作不少的适配工做,以知足多种支付的需求,可能你会想,直接在适配那里加几行 if else 不就得了吗,这么作也没问题,也能够知足多种支付的需求,但你有没有想过,假设此时再加一个第三方支付,你会怎么作?你只能原有方法上加多个 else 条件,这样就会致使请求层代码不断地随着业务发展改变,使得代码及其不优雅,并且也很差维护,这时咱们就得用上策略模式,将这些 if else 代码消除,当咱们增长一个第三方支付时,咱们只须要新建一个 Strategy 类就能够了,策略模式究竟怎么使用能够看看大话设计模式。

所以我在 Builder 模式前加多了一层支付策略层:

请求处理

因为支付平台涉及到资金,支付的各类请求与返回,以及异常记录在一个支付平台中异常重要,所以咱们须要记录每一次的支付请求记录,以便后续排查问题。

基于这点需求,我在开始请求第三方支付以前,设计了一层 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 层的高扩展性。

如下是 Handler 层的架构设计图:

写在最后

以上就是个人支付平台架构设计思路,总结来讲,支付平台须要具有可扩展性、稳定性、高可用性,所以我在设计支付平台时使用了不少设计模式以及引入消息队列处理回调分发的问题,使得支付平台具有这几点特性,但愿可以给你一些启发与帮助,最后我把支付平台总体的架构设计图贴出来:

公众号「后端进阶」,专一后端技术分享!

相关文章
相关标签/搜索