經過 上篇文章 的分析,咱們已經明確了這個系統要幹些什麼。接下來的都是實打實的乾貨。這些內容認真閱讀掌握後,相信你可以以此爲基礎設計一個維護性好、擴展性好的交易系統。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主頁留言