date: 2017-11-19 17:12:16
title: 支付系統0X00: 支付系統預研
最近在寫支付系統, 之前公司寫了一版, 量級比較小, 純同步, 應用層就簡單的 api + task, 項目結構也簡單:
├── lib
├── pay-lib
├── pay-gateway
└── pay-task
- lib: 用來存放項目核心依賴, 包括框架和 Util(工具助手類)
- pay-lib: 包括支付公共邏輯, 包括支付渠道 sdk, 數據庫模型(model)
- pay-gateway: 支付網關, 用來處理所有 http 請求相關的內容, 已實現 普通支付(支付寶等) / 支付結果查詢 / 代扣代發(包括批量) / 部分對賬獲取 等功能
- pay-task: 支付腳本, 已實現 異步通知 / 查詢 / 結算 / 對賬 等功能
雖然當時在開發的時候, 也參考了不少資料, 并出了一份設計文檔(之前的 blog - 支付系統設計), 但是當時并沒有好好閱讀源碼, 所以有點淺嘗輒止. 這次重拾支付系統, 自然是想做得 更好一點, 不然也就同 rango 說的那樣了:
無法進入一個擁有大規模并發請求的項目中得到歷練,不堅持提升自己,那也只能在小公司混日子了。
這次的技術預研花了不少時間, 一方面是由于現在開源出來的系統, 都是基于 java 的, 自己確實 java 水平有限. java 的語法看了不止 4 遍(學校里的 java 課, 慕課網 java 視頻教程, 實驗樓 上面的 java 學習路徑, 以及 <算法> 這本書是用 java 實現的), 不過離開學校之后就再沒有用 java 寫過項目. 有一次看 java 同事改請求地址在改動 xml 文件, 當時我還特別納悶, 畢竟 php 的邏輯是 什么都可以 php 文件來處理, 最典型的是直接在 php 文件中寫 html. 所以為了能看這些開源系統的源碼輕松一點, 花了不少時間 繼續 熟悉 java 的語法, 并快速看了幾個主流框架的簡單例子, 差不多能看懂大部分 黑話(各種詞匯縮寫) 的程度為止. 另一方面就比較客觀了, 量變到質變, 沒有足夠的積累就是做不到想要的程度.
blog 的脈絡:
- 通過教程, 嘗試閱讀 java 源碼
- 龍果支付系統源碼解讀
- XxPay 支付系統源碼解讀
- 鳳凰牌老熊的支付系統設計
- php 自研支付系統設計
教程: java SSH 項目使用 dubbo 進行服務化改造
龍果學院 - Dubbo視頻教程(Dubbo項目實戰): http://www.roncoo.com/course/view/f614343765bc4aac8597c6d8b38f06fd
拿項目中比較常見的用戶注冊登錄功能來說, 如果量級比較小, 注冊登錄只是整個項目中的一個模塊(module), php 各類大型框架(laravel, yii, thinkphp 等) 也都是這樣設計, 而且開箱即用, 功能全面. 這方面可以參考 laravel 框架, 功能點很全面, 包括 認證(authentication)/授權(authorization)/密碼安全(password)/流量控制(rate limit) 等.
當然, 如果量級比較大, 就需要一個單獨的用戶(賬戶)系統了. 獨立出來的好處很明顯, 比如統一登錄, 這樣可以踢人下線, 比如安全和風控. 獨立出來作為單獨的系統, 其實就很接近現在流行的 微服務設計.
回到正題, 羅列一下簡單思路(恕我 java 水平有限, 歡迎指正):
- 原項目: user-demo, 包含用戶注冊登錄等功能
- 公共基礎項目: user-common(user-base), 包含公共基礎功能
- Facade 項目: user-facade, 用戶服務接口, 這個需要了解一下 facade 設計模式, 熟悉 laravel 對這個應該不陌生, 幾乎框架提供的所有服務都使用 facede 進行了一層封裝
- service 項目: user-service, 用戶服務實現, 服務提供者(provider)
- boss 項目: user-web-boss, 服務消費者(consumer)
更近一步, 基礎部分(user-common)可以做的更徹底:
- common-parent: maven 父配置
- common: 公共工程
- common-config: 公共配置工程 -> 以后可以演化為配置中心
- common-core: 公共 core 工程
- common-web: 公共 web 工程 -> 接入層更細粒度的控制
當然少不了服務化的基礎設施:
- dubbo: 上面 facade-service-boss 的拆分, 就來自于 dubbo 架構中 provider(container)-registry-consumer 這樣的設計
- zookeeper: 服務注冊與發現
一點個人觀點: 要深入一個工具可能需要與時間長跑, 但是, 先用起來吧, 體驗一下技術的暢快.
這里舉一個之前開發 tcp server 的例子, 當時服務注冊發現這塊使用的 etcd
, 我這里只需要在 server 啟動的時候, 發一條 http 給 etcd 注冊一下即可:
use Swoole\Server;
$s = new Server("0.0.0.0", 9501); // 這就是下面的 gameServer
$s->set([
'work_num' => 1,
'task_work_num' => 1,
]);
$s->on('connect', function (Server $s, $fd) {
echo "connect| $fd \n";
});
$s->on('start', function (Server $s) {
// server 啟動時注冊到注冊中心
// curl -vvv http://10.0.1.48:2379/v2/keys/gameServerList/$zoneId/$nodeId -XPUT -d value="x.x.x.x:9501"
// 獲取 gameServer
// curl http://10.0.1.48:2379/v2/keys/gameServerList/1/1
});
...
$s->start();
是不是 hin簡單?
此亦無他, 唯手生爾.
roncoo-pay: 龍果開源支付系統源碼解讀
龍果支付系統視頻教程: http://www.roncoo.com/course/view/a09d8badbce04bd380f56034f8e68be0
首先參考的源碼是 龍果支付系統:
- 核心支付流程
下面是代碼結構:
├── LICENSE
├── README.md
├── UPDATELOG.md
├── database.sql
├── pom.xml
├── roncoo-pay-app-notify
├── roncoo-pay-app-order-polling
├── roncoo-pay-app-reconciliation
├── roncoo-pay-app-settlement
├── roncoo-pay-common-core
├── roncoo-pay-service
├── roncoo-pay-web-boss
├── roncoo-pay-web-gateway
├── roncoo-pay-web-merchant
└── roncoo-pay-web-sample-shop
- common-core: 公共組件
沒啥可說的, 不過列舉一下, 以后設計的時候可以參考: config(配置); BaseDao(數據模型基類); 接口公共返回值/分頁等請求相關; 數據庫枚舉類型(enum); 業務異常(BizException); 工具助手類(Util)
這里有個細節: 數據庫里的字段用的 string 數據類型, 使用類來存儲枚舉類型
- service: 核心業務
核心業務實現, 包括 account(賬戶, 支付系統中標識交易實體, 參與交易和結算) / notify(異步通知商戶) / permission(權限管理) / reconciliation(對賬) / trade(交易) / user(用戶信息/支付方式, 產品信息) 模塊
每個模塊按照如下方式組織代碼: dao(數據層) + entity(實體, 和數據模型對比) + enums(類型枚舉) + exception(異常) + service(具體業務實現) + util(工具助手類) + vo()
- app-notify: 異步通知商戶
從 db (startInitFromDB()
) 中讀取數據(status 狀態 / notifyTime 通知次數), 使用 線程池(threadPool) 和 隊列(notifyQueue) 完成異步通知, 最后會寫到數據庫(notifyPersist)
需要注意的是, 最后數據庫落庫并不是直接更新, 先將數據寫入到 ActiveMQ, 再由 db 來消費
- app-order-polling: 查詢支付結果
和上面類似, 線程池 + 隊列 來查詢支付結果
這里的區別是: 訂單支付后添加消息到 ActiveMQ, app-order-polling
設置了 PollingMessageListener
來訂閱這個消息, 從而觸發上面的查詢
- app-reconciliation: 對賬
這個項目比較簡單, 因為對賬流程是固定的: 判斷是否對賬 -> 獲取對賬數據 -> 解析 -> 對賬流程
值得借鑒的是: 對賬的每個步驟都抽象成相應的 xxxBiz
類來處理; 解析這一步定義了 ParserInterface
, 確保對賬流程使用的格式一致
- app-settlement: 結算
這個項目也比較簡單, 維護了 SettThreadPool
來處理結算任務, 按照 每日/自動 2個維度進行處理
- web-gateway: 支付網關
基礎的MVC, 項目并沒有實現復雜的路由功能, 只包含 單個支付渠道/多個支付渠道
- web-boss: 運營管理后臺
也是基礎的MVC, 包含 權限管理 + 支付/對賬/清算等支付功能的可視化
- web-sample-shop: 模擬商城
下單并對接支付系統
- web-merchant: 商戶后臺
訂單 / 支付 / 結算
- 數據層簡單劃分
XxPay開源支付系統源碼解讀
XxPay官網:http://www.xxpay.org
另一個參考的開源支付系統時 XxPay支付系統
PS: 此系統還在保持更新, 感興趣的同學可以加群提 feature, 很可能就加到之后的發版計劃了哦
XxPay 支付系統在業務上只包含核心的支付流程, 準確說業務功能實現簡單, 但是在系統架構實現上提供了更多選擇:
- spring-boot-dubbo架構實現
- spring-cloud架構實現
- spring-boot架構實現
而且開發上也引入了 docker 來解決環境問題, 用一句話來總結的話: 更加現代化. 推薦閱讀源碼.
xxpay-master
├── xxpay4dubbo -- spring-boot-dubbo架構實現
| ├── xxpay4dubbo-api -- 接口定義
| ├── xxpay4dubbo-service -- 服務生產者
| ├── xxpay4dubbo-web -- 服務消費者
├── xxpay4spring-cloud -- spring-cloud架構實現
| ├── xxpay-config -- 配置中心
| ├── xxpay-gateway -- API網關
| ├── xxpay-server -- 服務注冊中心
| ├── xxpay-service -- 服務生產者
| └── xxpay-web -- 服務消費者
├── xxpay4spring-boot -- spring-boot架構實現
├── xxpay-common -- 公共模塊
├── xxpay-dal -- 數據持久層
├── xxpay-mgr -- 運營管理平臺
├── xxpay-shop -- 演示商城
對應的數據層也非常簡單:
基于老熊的支付系統設計
鳳凰牌老熊 - 現代支付系統設計 - 基于微服務的實踐: http://blog.lixf.cn/
老熊的博客里面干貨很多, 還建立了金融產品技術群長期分享干貨, 強烈推薦.
php 自研支付系統設計
綜合來看, 實現微服務化, 是 php 自研支付系統設計的正確方向.
常規的 web/api/task/mysql/redis 就不贅述, 你不應該只停留在這個水平
基礎設施
- docker: 開發部署環境的統一, 也是部署微服務的前置條件
- git: 可以采用 git flow 工作流
- gitlab: codereview
- CI/CD: jenkins
- 系統監控: 比如 zabbix
- 日志分析: 比如 ELK, 通過 sequenceID 進行全鏈路分析
- doc: api doc, 比如 swagger; 開發設計文檔, 流程圖等
- test: phpunit
更多準備工作可以參考: 重構中的內部準備工作
更多技術要求:
- 多進程, 保證服務可以橫向擴展
- 任務隊列: 查詢支付渠道; 異步通知商戶; 請求接收后立即返回結果, 異步處理請求內容
- 消息隊列: 比如支付成功后, 推送支付結果到消息隊列中, 多個子系統(BI/風控/賬務)消費這個消息
系統設計
支付產品:
- 支付流程: 參數校驗 -> 支付路由 -> 風控 -> 支付 -> 更新訂單 -> 發送消息(多系統訂閱此消息)
- 系統邊界: 支付網關(路由/接口安全) -> 支持產品(渠道無關) -> 支付渠道
- 支付網關參數: 公共參數 + 業務參數 + 業務擴展參數
- 支付路由: 計算因子(渠道是否可以, 銀行卡是否支持, 營銷, 限額, 費率等); 手工 vs 權重
- 風控決策: 增強驗證 + 拒絕 + 落地支付
- 支付產品模塊功能: 簽約/解約; 支付(代扣/代付); 撤銷; 退款; 查單; 對賬
賬戶&賬務:
- 三戶模型: 客戶 - 用戶(商戶) - 賬戶(支付賬戶)
- 賬戶建模: 基本屬性; 賬戶控制(提現/凍結等); 資金相關; 銀行卡/第三發支付信息
- 賬戶體系(可以根據會計科目制定): 資產類 / 負債類 / 所有者權益類 / 損益類 / 成本類 / 共同類
- 記賬: 單邊記賬 / 復式記賬
風控:
- 場景分析
- 數據倉庫
- 風控模型
核心業務流程:
- 交易: 商戶 - 支付系統 - 第三方支付 -> 同步/異步/查詢(確保獲取支付狀態)
- 退款: 商戶 - 支付系統 - 第三方支付 -> 將退款和交易拆開, 可以冗余退款金額等信息得到交易記錄中
- 結算: 商戶 - 支付系統, 支付系統 - 第三方支付 -> 采取日結的方式, 結算日期或者周期不同, 都可以在日結的基礎上匯總
- 對賬(差錯): 商戶 - 支付系統, 支付系統 - 第三方支付 -> 對賬類目, 各類目流水, 差錯表, 差錯池(自動處理隔天問題)
備注: 隔天問題, 由于雙方記錄的時間可能隔天, 導致對賬日切文件差異, 可以通過對比多天數據進行消除
寫在最后
寫這篇 blog 的初衷, 多少有點來自于現在開源支付系統 java 一家獨大, 但就提供服務而言(服務器領域), php 都應該有一席之地才對. 有個說法對一個語言了解的越多, 就越清楚這個語言擅長(適合)干什么, 所以我認為 php 也完全可以寫出很好的支付系統.
題外話, 關于語言的選擇, 可以參看 鳥哥的這篇 blog: 關于語言的選擇-選易用的