1. 程式人生 > >簡易支付系統的架構設計

簡易支付系統的架構設計

作者:文刀(微信公眾號:jishuhui_2015),Java Web全棧工程師,高階架構師,技術佈道者。曾任兩家上市公司的技術主管,從事微服務架構設計,DevOps團隊建設工作,在電商、LBS、IoT等相關應用領域有豐富的專案經驗。
責編:錢曙光([email protected]

支付系統是一個老生常談的話題,每個公司開發的支付系統不盡相同,因為業務形態並不太一樣。

本文並不是講一個大而全的支付系統——一個支付系統應提供支付渠道管理、支付閘道器、基本支付/退款/轉賬能力、支付記錄/明細,及其相關的監控運維繫統。至於所謂的賬務清算,對賬功能,賬戶體系,風控體系,現金流量管理,應納入到「財務系統」,也應該是大佬們談論的都是廣義的「支付系統」吧!而本文只談狹義的「支付系統」。

目前,支付的流程包含了三大部分:發起支付,發起退款,接收回調。考慮到吞吐量的影響,將原先同步的程式設計方式改為非同步的程式設計方式,不出意外的話,將會使用到Java8的ExecutorService和CompletableFuture。

此外,還用到了公司其他的現成的東西:RabbitMQ、Redis、MongoDB。

筆者打算將這套支付系統設計成與具體業務無關,並納入到公司的公共平臺系統中。

一、發起支付

這部分主要講客戶端和服務端如何配合完成一次支付請求。服務端必須要有一個意識,最終發起支付的還是客戶端,服務端提供一些必要的引數配置資訊。

發起支付的架構圖如下所示:

這裡寫圖片描述
發起支付架構圖

跟著標註的序號,可以跟蹤到一個支付請求是如何發起的(Sequence Diagram就免了),流程描述如下:

  1. Submit a pay task,當客戶端需要發起支付的時候,起始是向支付任務佇列裡面加入了一個新的支付任務,這個過程是非同步實現的。先根據客戶端提交的引數,構造好一個新的支付任務;
  2. Offer a task,開啟一個非同步任務,做的事情就是向MQ中新增一個新的支付任務,等待被消費;
  3. Pay task description,一旦非同步任務被成功建立,將會把第一步構造好的支付任務資訊直接return給客戶端;
  4. Poll a task,與此同時,支付任務的消費者將新的支付任務poll下來進行執行;
  5. Send a pay request,這一步需要根據實際情況而定。並不是所有的支付請求都要先經過第三方支付平臺,比如支付寶;而對於微信,則還需要憑支付引數申請一個prepay_id,再經由客戶端發起支付;
  6. Response,沒什麼好說的,第三方渠道返回的支付必要引數;
  7. Cache result,至此,一個支付任務可以算是完成了,可以將任務的執行結果(無論成功與否)快取在Redis中,隨時等待客戶端的回訪;
  8. Query result,客戶端在提交支付任務後,間隔一定時間後(建議2~3s),發起一個結果查詢的請求;
  9. Query,直接進Redis查詢結果;
  10. Synchronize,這是一個非同步的操作,將支付任務的執行結果“順便”同步到MongoDB中,並刪除Redis中快取的任務執行結果。持久化到MongoDB主要是為後續的容錯,重試,資料分析等提供落地的資料來源;
  11. Return,由Redis返回給應用伺服器;
  12. Return payment,應用伺服器再將最終的支付物件返回給客戶端。

讓我們更深入一點,我們來看三張Class Diagram:

① 先說說支付任務(PayTask)部分。PayTask和Payment兩個都是MongoDB中的Document物件,但在任務執行期間,PayTask是用Redis進行快取的,方便客戶端隨時發起Query,任務執行成功後,會生成Payment物件,最終PayTask和Payment都會持久化到MongoDB中。在PayService中,有對支付任務的一些基本操作,包括任務提交,取消,重試,構建等等。

這裡寫圖片描述

② 再說說任務的執行(runner)。這部分和RabbitMQ緊密相關,一旦一個支付任務形成了,就會放入任務執行佇列中,由消費者取出執行。在TaskRunner中,有兩個基本的介面方法:run(task)、retry(task),分別是執行任務和重試任務。在AbstractPayTaskRunner中已經封裝好了這兩個方法,繼承AbstractPayTaskRunner需要實現doTask方法,從返回值可以看出,這個過程是非同步化的。關於Retry機制,使用者可以設定重試與否,一旦設定了TaskInfo.needRetry=true(不出意外,預設就是允許重試),就啟用了Retry機制。還可以設定重試的次數(TaskInfo.retryTimes),預設三次,分別間隔1s,2s,3s,間隔時間以公差為1的等差數列組成。當然不會讓使用者無限重試,系統內建有一個最大重試次數,最大重試次數內建為5次。

為什麼是5次?

你感受一下,1s,2s,3s,4s,5s,整個請求鏈條就被拉長到了15s,這對客戶端簡直就是災難了!!

這裡寫圖片描述

③ 接著說一下支付渠道(PayChannel)。這部分設計與具體的支付渠道對接聯絡比較緊密了,包括支付引數配置,支付引數處理,簽名/驗籤等等。

這裡寫圖片描述

④ 最後解釋一下支付引數(PayParams)。

這裡寫圖片描述

大部分還是能看懂的,我解釋幾個關鍵的property:

1) appId,這是為了區分不同的產品所設定的。現實中,很有可能一個產品會申請與之對應的支付渠道,然後在支付平臺中建立應用,設定好對應的支付引數,系統將會分配一個appId,憑此值就可以直接定位到各個支付引數。如果想再更完善一點,可以再區分一下測試環境和正式環境;

2) amount,這裡代表的是支付金額的意思,但是這套支付系統的金額單位統一設定成 人民幣【分】;

3) metadata,理論上,元資料這個欄位沒啥限制,要是非要說有限制,那麼就是欄位長度了——5000個字元。這個欄位的想象空間還是很大的:用於填寫豐富的交易相關資訊,用於在增長智慧系統產品中進行深入商業分析。包括交易行為多維分析、人群分析、產品轉化路徑、個性化推薦、智慧補貼、定向推送等。看產品經理要怎麼玩了;

5) credential,這個欄位非常非常重要,其中裝載的就是客戶端最終發起支付請求的憑證,會作為Payment物件的一部分返回給客戶端;

MongoDB的document欄位設計

解釋一下為什麼要用MongoDB:

個人覺得,如果這個通用服務要得到較好的推廣(甚至是開源),用MySQL等關係型資料庫是不二之選,因為一個完整實用的系統,必然是少不了資料庫的,如果一旦用了一些非傳統的東西,必然會提高一部分人的對接成本。有的人一看不符合團隊的技術棧,直接就不考慮了。

為什麼我還是要用MongoDB呢?

① 團隊的技術棧裡面有這麼個東西,不用白不用;
② MongoDB普及程度實在是不要太高,還不用上點NoSQL的東西,感覺自己分分鐘被OUT掉了;
③ 要儲存的資料結構需要支援動態擴充套件的特性,我就看中MongoDB的靈活性,如下是要儲存的資料結構:

document_name = “Payment”

{
    "payId": "pay_Oyvrf9e9S1",
    "method": "yoogurt.taxi.pay",
    "version": "v1.0",
    "timestamp": 1473044885,
    "created": 1473042835,
    "paid": false,
    "appId": "app_iPGa98ab9ev",
    "channel": "wx",
    "orderNo": "20161899798416",
    "clientIp": "192.168.18.189",
    "amount": 10000,
    "subject": "充值訂單-¥100.0",
    "body": "充值訂單-¥100.0",
    "paidTime": null,
    "transactionNo": "",
    "metadata": {
        "user_id": "170204469176",
        "phone_number": "13811234567"
    },
    "credential": {
        "appId": "wx4932d1311e",
        "partnerId": "1269774001",
        "prepayId": "wx2016099",
        "nonceStr": "1e99d8fe92ba",
        "timeStamp": "1473042837",
        "packageValue": "Sign=WXPay",
        "sign": "1CECCEDEBE"
    },
    "extra": {},
    "statusCode": "",
    "message": "",
    "description": ""}

其中,metadata,credential,extra這類欄位,並沒有一個特別固定的規範,用MySQL要冗餘一下欄位才行,或者針對每個渠道去分表,想想都覺得煩!

MySQL

因為這套支付系統被設計成為支援多應用,多渠道,所以此處用到MySQL存放一些應用配置。 E-R圖免了,直接上資料庫表結構:

① pay_channel:可供接入的支付渠道

這裡寫圖片描述

② app_settings:支付應用資訊

這裡寫圖片描述

③ app_channel:應用已接入的支付渠道

這裡寫圖片描述

④ alipay_settings:支付寶引數設定

這裡寫圖片描述

⑤ wx_settings:微信app支付引數設定

這裡寫圖片描述

如果想要增加支付渠道,只需要新增一張對應的支付引數設定表。

二、發起退款

不出意外,客戶在平臺的每筆訂單都可以發起退款,而且還能分批退,也就是同一個訂單,可以多次發起退款申請,只要保證退款總額不超出實付總額。 架構圖如下所示:

這裡寫圖片描述
發起退款架構圖

跟發起支付請求的流程有很多相似之處,不再一一解釋了,兩個關鍵的地方說明一下:

客戶端發起退款請求的時候,需要攜帶payId,就是支付物件的id。這就意味著,支付系統的呼叫方需要維護payId與orderNo的對應關係,務必在客戶端發起退款請求之前,獲取到正確的payId;
承接上一步,這才有了圖中的第5、6個步驟,從MongoDB中查詢之前的支付物件。第三方渠道通常會要求在退款的時候指定一個退款單號,因為一筆訂單可以分多次退款,所以不建議將訂單號作為退款單號使用。這裡的退款單號由支付系統生成並維護。
這部分的執行流程和之前類似,客戶端發起退款請求,形成一個退款任務(RefundTask),放入任務佇列中,消費者取出並執行各自的業務邏輯,退款成功會生成Refund物件,並持久化到MongoDB中。

MongoDB

document_name = "Refund"

{
    "payId": "pay_vfvS0m1",
    "method": "yoogurt.taxi.pay",
    "version": "v1.0",
    "timestamp": 1473044885,
    "created": 1473042835,
    "refundId": "refund_kmrf9wSr1em",
    "appId": "app_iGa8abLe9ev",
    "orderNo": "20161899798416",
    "clientIp": "192.168.18.189",
    "amount": 10000,
    "succeedTime": 1473150835,
    "transactionNo": "64059968740554",
    "refundStatus": "success",
    "message": "",
    "metadata": {
        "user_id": "170204469176",
        "phone_number": "13811234567"
    },
    "description": ""} 

三、接收回調

這部分功能被設計成了事件驅動型別,所以webhooks當仁不讓。

因為各個渠道的回撥內容都不盡相同,所以這部分設計會按支付渠道切分。

架構圖如下:

這裡寫圖片描述
處理回撥事件

使用者在支付完畢後,第三方支付渠道通過發起支付時指定的回撥地址對商戶進行支付成功的非同步通知。

這部分的執行流程和之前類似,在各自的PayChannel中解析好回撥引數,形成一個回撥事件(Event),並持久化到MongoDB中,然後再生成一個回撥任務(EventTask),放入任務佇列中,消費者取出並執行各自的業務邏輯,這裡的消費者就是上游的業務服務系統。

MongoDB

document_name = “Event”

{
    "eventId": "evt_la06Co7wq",
    "created": 1427555016,
    "eventType": "pay.succeeded",
    "data": {
        "payId": "pay_OvP88CSm1",
        "method": "yoogurt.taxi.pay",
        "version": "v1.0",
        "timestamp": 1473044885,
        "created": 1473042835,
        "paid": false,
        "appId": "app_iGa9aLe9ev",
        "channel": "wx",
        "orderNo": "20161899798416",
        "clientIp": "192.168.18.189",
        "amount": 10000,
        "subject": "使用者充值-¥100.0",
        "body": "充值訂單-¥100.0",
        "paidTime": null,
        "transactionNo": "",
        "statusCode": "",
        "message": "",
        "metadata": {
            "user_id": "170204469176",
            "phone_number": "13811234567"
        },
        "credential": {
            "appId": "wx4932b511e",
            "partnerId": "1269774001",
            "prepayId": "wx201609051039",
            "nonceStr": "1e9d8fddad",
            "timeStamp": "1473042837",
            "packageValue": "Sign=WXPay",
            "sign": "1C0K3C95AKB"
        },
        "extra": {

        },
        "description": ""
    },
    "retryTimes": 0} 

特別說明一下data欄位:

如果是支付成功事件,則返回對應的Payment物件;

如果是退款成功時間,則返回對應的Refund物件。

1月13日,即將強勢來襲,秉承乾貨實料(案例)的內容原則,邀請了來自阿里巴巴騰訊微博網易等多家企業的資料庫專家及高校研究學者,圍繞Oracle、MySQL、PostgreSQL、Redis等熱點資料庫技術展開,從核心技術的深挖到高可用實踐的剖析,打造精華壓縮式分享,舉一反三,思辨互搏,報名及更多詳情可點選此處檢視。

相關推薦

簡易支付系統架構設計

作者:文刀(微信公眾號:jishuhui_2015),Java Web全棧工程師,高階架構師,技術佈道者。曾任兩家上市公司的技術主管,從事微服務架構設計,DevOps團隊建設工作,在電商、LBS、IoT等相關應用領域有豐富的專案經驗。 責編:錢曙光(

架構設計-支付寶、京東、美團、去哪兒的支付系統架構整體設計詳解!!!

支付產品模組是按照支付場景來為業務方提供支付服務。這個模組一般位於支付閘道器之後,支付渠道之前。 它根據支付能力將不同的支付渠道封裝成統一的介面,通過支付閘道器來對外提供服務。所以,從微服務的角度,支付產品本身也是一個代理模式的微服務,它透過支付閘道器響應業務方請求, 進行一些統一處理後,分發到不同

支付系統整體設計:整體架構設計以及注意要點(一)

016-11-23 01:43:00 來源: 鳳凰牌老熊 導讀: 在支付系統中,支付閘道器和支付渠道的對接是最核心的功能。其中支付閘道器是對外提供服務的介面,所有需要渠道支援的資金操作都需要通過閘道器分發到對應的渠道模組上。一旦定型,後續就很少,也很難調整。而支付渠道模組是接收閘道器的請求...

每秒處理10萬高並發訂單的樂視集團支付系統架構分享

記錄 應該 高校 操作 新的 讀寫 官方 來看 一個表 隨著樂視硬件搶購的不斷升級,樂視集團支付面臨的請求壓力百倍乃至千倍的暴增。作為商品購買的最後一環,保證用戶快速穩定的完成支付尤為重要。所以在15年11月,我們對整個支付系統進行了全面的架構升級,使之具備了每秒穩定處理1

全民養豬系統架構設計開發平臺

全民養豬 全民養豬系統開發,(李小姐177-8870-6412微/電)全民養豬系統源碼搭建,全民養豬系統全網模式開發,全民養豬 app系統軟件開發,全民養豬系統專業開發,全民養豬系統app開發平臺,全民養豬系統設計運作、非平臺客服,玩家勿擾!!! 全民養豬每個帳戶每天可以購買100元到20

淺談秒殺系統架構設計

秒殺http://mp.weixin.qq.com/s?__biz=MjM5NDM4MDIwNw%3D%3D&mid=2448834705&idx=1&sn=25cf3d4f6d6826e564a634901189eb8f&chksm=b28a405185fdc9478b6bd

高性能、高可用、高擴展ERP系統架構設計

sqlserve 學習 業務邏輯層 表設計 應用程序 log cnblogs 便在 tab ERP之痛 曾幾何時,我混跡於電商、珠寶行業4年多,為這兩個行業開發過兩套大型業務系統(ERP)。作為一個ERP系統,系統主要功能模塊無非是訂單管理、商品管理、生產采

SaaS 系統架構設計經驗總結

計費 攔截 好處 abc www. ring 需求 分系統 數據庫 2B SaaS系統最近幾年都很火。很多創業公司都在嘗試創建企業級別的應用 cRM, HR,銷售, Desk SaaS系統。很多SaaS創業公司也拿了大額風投。畢竟SaaS相對傳統軟件的優勢非常明顯。 最近一

分布式、服務化的ERP系統架構設計

你會 實現 strong 感覺 項目 更新失敗 統一 都在 優點 每天學習一點點 編程PDF電子書、視頻教程免費下載:http://www.shitanlife.com/code ERP之痛 曾幾何時,我混跡於電商、珠寶行業4年多,為這兩個行業開發

DKhadoop大數據系統架構設計方案

深度 穩定性 alt 自己 系統架構 穩定 得到 國產 style 大數據作為當下最為熱門的事件之一,其實已經不算是很新鮮的事情了。如果是三五年前在討論大數據,那可能會給人一種很新鮮的感覺。大數據作為當下最為重要的一項戰略資源,已經是越來越得到國家和企業的高度重視,我們從大

0. 視頻監控系統架構設計

無線 oot nfs服務 實現 圖1 In inux ubun 設計 0、視頻監控系統架構設計 0.1、功能指標 (1)搭建共享文件夾 (2)實現Ubuntu的NAT上網和橋接上網 (3)搭建局域網 (4)搭建nfs服務器、tftp服務器 (5)將uboot、kernel、

學生信息管理系統架構設計

系統 text 接受 目的 shadow 情況 sha 機房 數據庫 近期學習架構設計,首先從最基本的學生信息管理系統進行分析。 目的:學生信息管理系統架構設計 思考第一步:識別系統復雜度 ??架構設計的真正目的是為了解決軟件復雜度帶來的問題,故應首先識別本系統復雜度在何

高級系統架構設計官方教材(帶目錄),免費拿走

圖片 地址 高級 name mil family 下載 chm wid 高級系統架構設計官方教材(帶目錄)下載地址:點此下載以下為目錄截圖: 高級系統架構設計官方教材(帶目錄),免費拿走高級系統架構設計官方教材(帶目錄),免費拿走

聚合四方支付系統架構及所需配置

三方 一個 完全 支付 位或 windows 定時 活性 擁有 聚合四方支付系統架構及所需配置 聚合支付介於第三方支付和商戶之間,不進行資金清算,但能夠根據商戶的需求進行個性化定制,形成支付通道資源優勢互補,具有中立性、靈活性、便捷性等特點。 聚合支付系統,可以將市面上主流

分布式存儲系統架構設計,應該遵循什麽樣的原則?

不可 功能 故障恢復 硬盤 獨立 實現 存儲系統 技術 本質 分布式存儲系統架構設計,應該遵循什麽樣的原則? 分布式存儲系統,本質是將數據分散存儲在多臺獨立的x86設備上。傳統的網絡存儲系統通常采用集中的存儲服務器存放數據,存儲服務器很容易成為系統性能的瓶頸,也容易成為可

機票實時搜索系統架構設計

family 之間 width call 作用 化運維 mage margin 1-1 機票實時搜索系統架構設計? 不同的業務場景,不同的特征 ? 結合特征去進?設計和優化 ? 通?!=最優 ? 量體裁?分布式系統的CAP理論 首先把分布式系統中的三個特性進行了

網購秒殺系統架構設計案例分析——《大型網站技術架構》筆記

一、核心思想: 網站秒殺時的併發比正常運營時多的多,所以網站的秒殺業務不能使用正常的網站業務流程,也不能和正常的網站交易業務共用伺服器(否則造成巨大浪費),必須設計部署專門的秒殺系統,進行專門應對   二、技術挑戰: 1.對現有網站業務造成衝擊:秒殺活動只是網站營銷的一個附加活動,具有時間短

雲南農墾交易系統架構設計

                                  雲南農墾交易系統架構設計 前言:   針對雲南農墾

每秒處理10萬高併發訂單的某集團支付系統架構分享

轉載自:最程式碼 官方 隨著樂視硬體搶購的不斷升級,樂視集團支付面臨的請求壓力百倍乃至千倍的暴增。作為商品購買的最後一環,保證使用者快速穩定的完成支付尤為重要。所以在15年11月,我們對整個支付系統進行了全面的架構升級,使之具備了每秒穩定處理10萬訂單的能力。為樂視生態各種形式的搶購秒殺活動提供

【阿里雲ACE成長記第5期】分散式鏈路追蹤系統架構設計的經驗分享

【引言】本期由阿里雲ACE(阿里雲開發者社群)&成都檸檬雲網絡技術有限公司資深架構師 曾昌強 為大家分享個人成長經歷與個人專業技術之分散式鏈路追蹤系統架構設計。視訊:https://yq.aliyun.com/live/581 Part 1:成長經歷講述一個不知道什麼叫程式設計的門外漢,如何穿越幾千