我對支付平臺架構設計的一些思考

微信公衆號「後端進階」,專一後端技術分享: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 層的架構設計圖:

寫在最後

以上就是個人支付平臺架構設計思路,總結來講,支付平臺須要具有可擴展性、穩定性、高可用性,所以我在設計支付平臺時使用了不少設計模式以及引入消息隊列處理回調分發的問題,使得支付平臺具有這幾點特性,但願可以給你一些啓發與幫助,最後我把支付平臺總體的架構設計圖貼出來:

公衆號「後端進階」,專一後端技術分享!

相關文章
相關標籤/搜索