經過 上篇文章 的分析,咱們已經明確了這個系統要幹些什麼。接下來的都是實打實的乾貨。這些內容認真閱讀掌握後,相信你可以以此爲基礎設計一個維護性好、擴展性好的交易系統。git
數據的設計是按照:交易、退款、日誌 來設計的。對於上面說到的對帳等功能並無。這部分不難你們能夠自行設計,按照上面講到的思路。主要的表介紹以下:github
pay_transaction
記錄全部的交易數據。pay_transaction_extension
記錄每次向第三方發起交易時,生成的交易號pay_log_data
全部的日誌數據,如:支付請求、退款請求、異步通知等pay_repeat_transaction
重複支付的數據pay_notify_app_log
通知應用程序的日誌pay_refund
記錄全部的退款數據具體的表結構:sql
-- ----------------------------------------------------- -- Table 建立支付流水錶 -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `pay_transaction` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `app_id` VARCHAR(32) NOT NULL COMMENT '應用id', `pay_method_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付方式id,能夠用來識別支付,如:支付寶、微信、Paypal等', `app_order_id` VARCHAR(64) NOT NULL COMMENT '應用方訂單號', `transaction_id` VARCHAR(64) NOT NULL COMMENT '本次交易惟一id,整個支付系統惟一,生成他的緣由主要是 order_id對於其它應用來講可能重複', `total_fee` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付金額,整數方式保存', `scale` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '金額對應的小數位數', `currency_code` CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '交易的幣種', `pay_channel` VARCHAR(64) NOT NULL COMMENT '選擇的支付渠道,好比:支付寶中的花唄、信用卡等', `expire_time` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '訂單過時時間', `return_url` VARCHAR(255) NOT NULL COMMENT '支付後跳轉url', `notify_url` VARCHAR(255) NOT NULL COMMENT '支付後,異步通知url', `email` VARCHAR(64) NOT NULL COMMENT '用戶的郵箱', `sing_type` VARCHAR(10) NOT NULL DEFAULT 'RSA' COMMENT '採用的籤方式:MD5 RSA RSA2 HASH-MAC等', `intput_charset` CHAR(5) NOT NULL DEFAULT 'UTF-8' COMMENT '字符集編碼方式', `payment_time` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '第三方支付成功的時間', `notify_time` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '收到異步通知的時間', `finish_time` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '通知上游系統的時間', `trade_no` VARCHAR(64) NOT NULL COMMENT '第三方的流水號', `transaction_code` VARCHAR(64) NOT NULL COMMENT '真實給第三方的交易code,異步通知的時候更新', `order_status` TINYINT NOT NULL DEFAULT 0 COMMENT '0:等待支付,1:待付款完成, 2:完成支付,3:該筆交易已關閉,-1:支付失敗', `create_at` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '建立時間', `update_at` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新時間', `create_ip` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '建立的ip,這多是本身服務的ip', `update_ip` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新的ip', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_tradid` (`transaction_id`), INDEX `idx_trade_no` (`trade_no`), INDEX `idx_ctime` (`create_at`)), ENGINE = InnoDB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '發起支付的數據'; -- ----------------------------------------------------- -- Table 交易擴展表 -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `pay_transaction_extension` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `transaction_id` VARCHAR(64) NOT NULL COMMENT '系統惟一交易id', `pay_method_id` INT UNSIGNED NOT NULL DEFAULT 0, `transaction_code` VARCHAR(64) NOT NULL COMMENT '生成傳輸給第三方的訂單號', `call_num` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '發起調用的次數', `extension_data` TEXT NOT NULL COMMENT '擴展內容,須要保存:transaction_code 與 trade no 的映射關係,異步通知的時候填充', `create_at` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '建立時間', `create_ip` INT UNSIGNED NOT NULL COMMENT '建立ip', PRIMARY KEY (`id`), INDEX `idx_trads` (`transaction_id`, `pay_status`), UNIQUE INDEX `uniq_code` (`transaction_code`)), ENGINE = InnoDB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '交易擴展表'; -- ----------------------------------------------------- -- Table 交易系統所有日誌 -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `pay_log_data` ( `id` BIGINT UNSIGNED NOT NULL, `app_id` VARCHAR(32) NOT NULL COMMENT '應用id', `app_order_id` VARCHAR(64) NOT NULL COMMENT '應用方訂單號', `transaction_id` VARCHAR(64) NOT NULL COMMENT '本次交易惟一id,整個支付系統惟一,生成他的緣由主要是 order_id對於其它應用來講可能重複', `request_header` TEXT NOT NULL COMMENT '請求的header 頭', `request_params` TEXT NOT NULL COMMENT '支付的請求參數', `log_type` VARCHAR(10) NOT NULL COMMENT '日誌類型,payment:支付; refund:退款; notify:異步通知; return:同步通知; query:查詢', `create_at` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '建立時間', `create_ip` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '建立ip', PRIMARY KEY (`id`), INDEX `idx_tradt` (`transaction_id`, `log_type`)), ENGINE = InnoDB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '交易日誌表'; -- ----------------------------------------------------- -- Table 重複支付的交易 -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `pay_repeat_transaction` ( `id` BIGINT UNSIGNED NOT NULL, `app_id` VARCHAR(32) NOT NULL COMMENT '應用的id', `transaction_id` VARCHAR(64) NOT NULL COMMENT '系統惟一識別交易號', `transaction_code` VARCHAR(64) NOT NULL COMMENT '支付成功時,該筆交易的 code', `trade_no` VARCHAR(64) NOT NULL COMMENT '第三方對應的交易號', `pay_method_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付方式', `total_fee` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '交易金額', `scale` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '小數位數', `currency_code` CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '支付選擇的幣種,CNY、HKD、USD等', `payment_time` INT NOT NULL COMMENT '第三方交易時間', `repeat_type` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '重複類型:1同渠道支付、2不一樣渠道支付', `repeat_status` TINYINT UNSIGNED DEFAULT 0 COMMENT '處理狀態,0:未處理;1:已處理', `create_at` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '建立時間', `update_at` INT UNSIGNED NOT NULL COMMENT '更新時間', PRIMARY KEY (`id`), INDEX `idx_trad` ( `transaction_id`), INDEX `idx_method` (`pay_method_id`), INDEX `idx_time` (`create_at`)), ENGINE = InnoDB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '記錄重複支付'; -- ----------------------------------------------------- -- Table 通知上游應用日誌 -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `pay_notify_app_log` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `app_id` VARCHAR(32) NOT NULL COMMENT '應用id', `pay_method_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付方式', `transaction_id` VARCHAR(64) NOT NULL COMMENT '交易號', `transaction_code` VARCHAR(64) NOT NULL COMMENT '支付成功時,該筆交易的 code', `sign_type` VARCHAR(10) NOT NULL DEFAULT 'RSA' COMMENT '採用的簽名方式:MD5 RSA RSA2 HASH-MAC等', `input_charset` CHAR(5) NOT NULL DEFAULT 'UTF-8', `total_fee` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '涉及的金額,無小數', `scale` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '小數位數', `pay_channel` VARCHAR(64) NOT NULL COMMENT '支付渠道', `trade_no` VARCHAR(64) NOT NULL COMMENT '第三方交易號', `payment_time` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付時間', `notify_type` VARCHAR(10) NOT NULL DEFAULT 'paid' COMMENT '通知類型,paid/refund/canceled', `notify_status` VARCHAR(7) NOT NULL DEFAULT 'INIT' COMMENT '通知支付調用方結果;INIT:初始化,PENDING: 進行中; SUCCESS:成功; FAILED:失敗', `create_at` INT UNSIGNED NOT NULL DEFAULT 0, `update_at` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), INDEX `idx_trad` (`transaction_id`), INDEX `idx_app` (`app_id`, `notify_status`) INDEX `idx_time` (`create_at`)), ENGINE = InnoDB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '支付調用方記錄'; -- ----------------------------------------------------- -- Table 退款 -- ----------------------------------------------------- CREATE TABLE IF NOT EXISTS `pay_refund` ( `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `app_id` VARCHAR(64) NOT NULL COMMENT '應用id', `app_refund_no` VARCHAR(64) NOT NULL COMMENT '上游的退款id', `transaction_id` VARCHAR(64) NOT NULL COMMENT '交易號', `trade_no` VARCHAR(64) NOT NULL COMMENT '第三方交易號', `refund_no` VARCHAR(64) NOT NULL COMMENT '支付平臺生成的惟一退款單號', `pay_method_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付方式', `pay_channel` VARCHAR(64) NOT NULL COMMENT '選擇的支付渠道,好比:支付寶中的花唄、信用卡等', `refund_fee` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '退款金額', `scale` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '小數位數', `refund_reason` VARCHAR(128) NOT NULL COMMENT '退款理由', `currency_code` CHAR(3) NOT NULL DEFAULT 'CNY' COMMENT '幣種,CNY USD HKD', `refund_type` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '退款類型;0:業務退款; 1:重複退款', `refund_method` TINYINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '退款方式:1自動原路返回; 2人工打款', `refund_status` TINYINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '0未退款; 1退款處理中; 2退款成功; 3退款不成功', `create_at` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '建立時間', `update_at` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '更新時間', `create_ip` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '請求源ip', `update_ip` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '請求源ip', PRIMARY KEY (`id`), UNIQUE INDEX `uniq_refno` (`refund_no`), INDEX `idx_trad` (`transaction_id`), INDEX `idx_status` (`refund_status`), INDEX `idx_ctime` (`create_at`)), ENGINE = InnoDB DEFAULT CHARACTER SET = utf8mb4 COMMENT = '退款記錄';
表的使用邏輯進行下方簡單描述:數據庫
支付,首先須要記錄請求日誌到 pay_log_data
中,而後生成交易數據記錄到 pay_transaction
與pay_transaction_extension
中。緩存
收到通知,記錄數據到 pay_log_data
中,而後根據時支付的通知仍是退款的通知,更新 pay_transaction
與 pay_refund
的狀態。若是是重複支付須要記錄數據到 pay_repeat_transaction
中。而且將須要通知應用的數據記錄到 pay_notify_app_log
,這張表至關於一個消息表,會有消費者會去消費其中的內容。安全
退款 記錄日誌日誌到 pay_log_data
中,而後記錄數據到退款表中 pay_refund
。微信
固然這其中還有些細節,須要你們本身看了表結構,實際去思考一下該如何使用。若是有任何疑問歡迎到咱們GitHub的項目(點擊閱讀原文)中留言,咱們都會一一解答。架構
這些表可以知足最基本的需求,其它內容可根據本身的需求進行擴張,好比:支持用戶卡列表、退款走銀行卡等。
這部分主要說下系統該如何搭建,以及代碼組織方式的建議。app
因爲支付系統的安全性很是高,所以不建議將對應的入口直接暴露給用戶可見。應該是在本身的應用系統中調用支付系統的接口來完成業務。另外系統對數據要求是:強一致性的。所以也沒有緩存介入(當如緩存能夠用來作報警,這不在本位範疇)。異步
具體的實現,系統會使用兩個域名,一個爲內部使用,只有指定來源的ip可以訪問固定功能(訪問除通知外的其它功能)。另外一個域名只能訪問 notify
return
兩個路由。經過這種方式能夠保證系統的安全。
在數據庫的使用上不管什麼請求直接走 Master 庫。這樣保證數據的強一致。固然從庫也是須要的。好比:帳單、對帳相關邏輯咱們能夠利用從庫完成。
無論想作什麼最終都要用代碼來實現。咱們都知道須要可維護、可擴展的代碼。那麼具體到支付系統你會怎麼作呢?我已支付爲例說下個人代碼結構設計思路。僅供參考。好比我要介入:微信、支付寶、招行 三家支付。個人代碼結構圖以下:
用文字簡單介紹下。我會將每個第三方封裝成: XXXGateway
類,內部是單純的封裝第三方接口,無論對方是 HTTP 請求仍是 SOAP 請求,都在內部進行統一處理。
另外有一層XXXProxy
來封裝這些第三方提供的能力。這一層主要幹兩件事情:對傳過來請求支付的數據進行個性化處理。對返回的結構進行統一處理返回上層統一的結構。固然根據特殊狀況這裏能夠進行一切業務處理;
經過上面的操做變化已經基本上被徹底封裝了。若是新增一個支付渠道。只須要增長:XXXGateway
與 XXXProxy
。
那麼 Context
與 Server
有什麼用呢?Server
內部封裝了全部的業務邏輯,它提供接口給 action 或者其它 server 進行調用。而 Context
這一層存在的價值是處理 Proxy
層返回的錯誤。以及在這裏進行報警相關的處理。
上面的結構只是個人一個實踐,歡迎你們討論。
本文描述的系統只是知足了最基本的支付需求。缺乏相關的監控、報警。
你們能夠到咱們的 GitHub主頁留言