coder,你會設計交易系統嗎(概念篇)?

大愚Talk發表於2019-03-11

文中我們從嚴謹的角度一步步聊到支付如何演變成獨立的系統。內容包括:系統演進過程、介面設計、資料庫設計以及程式碼如何組織的示例。若有不足之處,歡迎討論共同學習。

從模組到服務

我記得最開始工作的時候,所有的功能:加購物車/下單/支付 等邏輯都是放在一個專案裡。如果一個新的專案需要某個功能,就把這個部分的功能包拷貝到新的專案。資料庫也原封不動的拷貝過來,稍微根據需求改改。

這就是所謂的 單體應用 時代,隨著公司產品線開始多元,每條產品線都需要用到支付服務。如果支付模組調整了程式碼,那麼就會處處改動、處處測試。另一方面公司的交易資料割裂在不同的系統中,無法有效彙總統一分析、管理。

這時就到了系統演進的時候,我們把每個產品線的支付模組抽離成統一的服務。對自己公司內部提供統一的API使用,可以對這些API進一步包裝成對應的SDK,供內部業務線快速接入。這裡服務使用HTTP或者是RPC協議都可以根據公司實際情況決定。不過如果考慮到未來給第三方使用,建議使用HTTP協議,

系統的演變過程:

image-20190309104541749

總結下,將支付單獨抽離成服務後,帶來好處如下:

  1. 避免重複開發,資料隔離的現象出現;
  2. 支付系統周邊功能演進更容易,整個系統更完善豐滿。如:對賬系統、實時交易資料展示;
  3. 隨時可對外開發,對外輸出Paas能力,成為有收入的專案;
  4. 專門的團隊進行維護,系統更有機會演進成頂級系統;
  5. 公司重要賬號資訊儲存一處,風險更小。

系統能力

如果我們接手該需求,需要為公司從零搭建支付系統。我們該從哪些方面入手?這樣的系統到底需要具備什麼樣的能力呢?

首先支付系統我們可以理解成是一個介面卡。他需要把很多第三方的介面進行統一的整合封裝後,對內部提供統一的介面,減少內部接入的成本。做為一個最基本的支付系統。需要對內提供如下介面出來:

  1. 發起支付,我們取名:/gopay
  2. 發起退款,我們取名:/refund
  3. 介面非同步通知,我們取名:/notify/支付渠道/商戶交易號
  4. 介面同步通知,我們取名:/return/支付渠道/商戶交易號
  5. 交易查詢,我們取名:/query/trade
  6. 退款查詢,我們取名:/query/refund
  7. 賬單獲取,我們取名:/query/bill
  8. 結算明細,我們取名:/query/settle

一個基礎的支付系統,上面8個介面是肯定需要提供的(這裡忽略某些支付中的轉賬、綁卡等介面)。現在我們來基於這些介面看看都有哪些系統會用到。

image-20190309111001880

下面按照系統維度,介紹下這些介面如何使用,以及內部的一些邏輯。

應用系統

一般支付閘道器會提供兩種方式讓應用系統接入:

  1. 閘道器模式,也就是應用系統自己需要開發一個收銀臺;(適合提供給第三方)
  2. 收銀臺模式,應用系統直接開啟支付閘道器的統一收銀臺。(內部業務)

下面為了講清楚設計思路,我們按照 閘道器模式 進行講解。

對於應用系統它需要能夠請求支付,也就是呼叫 gopay 介面。這個介面會處理商戶的資料,完成後會呼叫第三方閘道器介面,並將返回結果統一處理後返回給應用方。

這裡需要注意,第三方針對支付介面根據我的經驗大致有以下情況:

  1. 支付時,不需要呼叫第三方,按照規則生成資料即可;
  2. 支付時,需要呼叫第三方多個介面完成邏輯(這可能比較慢,大型活動時需要考慮限流/降配);
  3. 返回的資料是一個url,可直接跳轉到第三方完成支付(wap/pc站);
  4. 返回的資料是xml/json結構,需要拼裝或作為引數傳給她的sdk(app)。

這裡由於第三方返回結構的不統一,我們需要統一處理成統一格式,返回給商戶端。我推薦使用json格式。

{
    "errno":0,
    "msg":"ok",
    "data":{

    }
}
複製程式碼

我們把所有的變化封裝在 data 結構中。舉個例子,如果返回的一個url。只需要應用程式發起 GET 請求。我們可以這樣返回:

{
    "errno":0,
    "msg":"ok",
    "data":{
        "url":"xxxxx",
        "method":"GET"
    }
}
複製程式碼

如果是返回的結構,需要應用程式直接發起 POST 請求。我們可以這樣返回:

{
    "errno":1,
    "msg":"ok",
    "data":{
        "from":"<form action="xxx" method="POST">xxxxx</form>",
        "method":"POST"
    }
}
複製程式碼

這裡的 form 欄位,生成了一個form表單,應用程式拿到後可直接顯示然後自動提交。當然封裝成 from表單這一步也可以放在商戶端進行。

上面的資料格式僅僅是一個參考。大家可根據自己的需求進行調整。

一般應用系統除了會呼叫發起支付的介面外,可能還需要呼叫 支付結果查詢介面。當然大多數情況下不需要呼叫,應用系統對交易的狀態只應該依賴自己的系統狀態。

對賬系統

對於對賬,一般分為兩個型別:交易對賬結算對賬

交易對賬

交易對賬的核心點是:檢查每一筆交易是否正確。它主要目的是看我們系統中的每一筆交易與第三方的每一筆交易是否一致。

這個檢查邏輯很簡單,對兩份賬單資料進行比較。它主要是使用 /query/bill 介面,拿到在第三方那邊完成的交易資料。然後跟我方的交易成功資料進行比較。檢查是否存在誤差。

這個邏輯非常簡單,但是有幾點需要大家注意:

  1. 我方的資料需要正常支付資料+重複支付資料的總和;
  2. 對賬檢查不成功主要包括:金額不對第三方沒有找到對應的交易資料我方不存在對應的交易資料

針對這些情況都需要有對應的處理手段進行處理。在我的經驗中上面的情況都有過遇到。

金額不對:主要是由於第三方的問題,可能是系統升級故障、可能是賬單介面金額錯誤;

第三方無交易資料: 可能是拉去的賬單時間維度問題(比如存在時差),這種時區問題需要自己跟第三方確認找到對應的時間差。也可能是被攻擊,有人冒充第三方非同步通知(說明系統校驗機制又問題或者金鑰洩漏了)。

自己系統無交易資料: 這種原因可能是第三方通知未發出或者未正確處理導致的。

上面這些問題的處理絕大部份都可以依賴 query/trade query/refund 來完成自動化處理。

結算對賬

那麼有了上面的 交易對賬 為什麼還需要 結算對賬 呢?這個系統又是幹嘛的?先來看下結算的含義。

結算,就是第三方閘道器在固定時間點,將T+x或其它約定時間的金額,匯款到公司賬號。

下面我們假設結算週期是: T+1。結算對賬主要使用到的介面是 /query/settle,這個介面獲取的主要內容是:每一筆結算的款項都是由哪些筆交易組成(交易成功與退款資料)。以及本次結算扣除多少手續費用。

它的邏輯其實也很簡單。我們先從自己的系統按照 T+1 的結算週期,計算出對方應該匯款給我們多少金額。然後與剛剛介面獲取到的資料金額比較:

銀行收款金額 + 手續費 = 我方系統計算的金額

這一步檢查通過後,說明金額沒有問題。接下來需要檢查本次結算下的每一筆訂單是否一致。

結算系統是 強依賴 對賬系統的。如果對賬發現異常,那麼結算金額肯定會出現異常。另外結算需要注意的一些問題是:

  • 銀行可能會自行退款給使用者,因為使用者可直接向自己髮卡行申請退款;
  • 結算也存在時區差問題;
  • 結算介面中的明細交易狀態與我方並不完全一致。比如:銀行結算時發現某筆退款完成,但我方系統在進行比較時按照未退款完成的邏輯在處理。

針對上面的問題,大家根據自己的業務需求需要做一些方案來進行自動化處理。

財務系統

財務系統有很多內部業務,我這裡只聊與支付系統相關的。(當然上面的對賬系統也可以算是財務範疇)。

財務系統與支付主要的一個關係點在於校驗交易、以及退款。這裡校驗交易可以使用 query/trade query/refund這兩個介面來完成。這個邏輯過程就不需要說了。下面重點說下退款。

我看到很多的系統退款是直接放在了應用裡邊,使用者申請退款直接就呼叫退款介面進行退款。這樣的風險非常高。支付系統的關於資金流向的介面一定要慎重,不能過多的直接暴露給外部,帶來風險。

退款的功能應該是放到財務系統來做。這樣可以走內部的審批流程(是否需要根據業務來),並且在財務系統中可以進行更多檢查來覺得是否立即進行退款,或者進入等待、拒絕等流程。

第三方閘道器

針對第三方主要使用到的其實就是非同步通知與同步通知兩個介面。這一部分的邏輯其實非常簡單。就是根據第三方的通知完成交易狀態的變更。以及通知到自己對應的應用系統。

這部分比較複雜的是,第三方的通知資料結構不統一、通知的型別不統一。比如:有的退款是同步返回結果、有的是非同步返回結果。這裡如何設計會在後面的 系統設計 中給出答案。

第一部份的內容就到此結束了。如果有什麼疑問歡迎到我們GitHub主頁留言。

GitHub: https://github.com/skr-shop

相關文章