经过 上篇文章 的分析,咱们已经明确了这个系统要干些什么。接下来的都是实打实的干货。这些内容认真阅读掌握后,相信你可以以此为基础设计一个维护性好、扩展性好的交易系统。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主页留言