coder,你会设计交易系统吗(实干篇)?
通过 上篇文章 的分析,我们已经明确了这个系统要干些什么。接下来的都是实打实的干货。这些内容认真阅读掌握后,相信你能够以此为基础设计一个维护性好、扩展性好的交易系统。
数据库设计
数据的设计是按照:交易、退款、日志 来设计的。对于上面说到的对账等功能并没有。这部分不难大家可以自行设计,按照上面讲到的思路。主要的表介绍如下:
pay_transaction
记录所有的交易数据。pay_transaction_extension
记录每次向第三方发起交易时,生成的交易号pay_log_data
所有的日志数据,如:支付请求、退款请求、异步通知等pay_repeat_transaction
重复支付的数据pay_notify_app_log
通知应用程序的日志pay_refund
记录所有的退款数据
具体的表结构:
-- -----------------------------------------------------
-- 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的项目(点击阅读原文)中留言,我们都会一一解答。
这些表能够满足最基本的需求,其它内容可根据自己的需求进行扩张,比如:支持用户卡列表、退款走银行卡等。
系统设计
这部分主要说下系统该如何搭建,以及代码组织方式的建议。
系统架构
由于支付系统的安全性非常高,因此不建议将对应的入口直接暴露给用户可见。应该是在自己的应用系统中调用支付系统的接口来完成业务。另外系统对数据要求是:强一致性的。因此也没有缓存介入(当如缓存可以用来做报警,这不在本位范畴)。
具体的实现,系统会使用两个域名,一个为内部使用,只有指定来源的ip能够访问固定功能(访问除通知外的其它功能)。另一个域名只能访问 notify
return
两个路由。通过这种方式可以保证系统的安全。
在数据库的使用上无论什么请求直接走 Master 库。这样保证数据的强一致。当然从库也是需要的。比如:账单、对账相关逻辑我们可以利用从库完成。
代码设计
不管想做什么最终都要用代码来实现。我们都知道需要可维护、可扩展的代码。那么具体到支付系统你会怎么做呢?我已支付为例说下我的代码结构设计思路。仅供参考。比如我要介入:微信、支付宝、招行 三家支付。我的代码结构图如下:
用文字简单介绍下。我会将每一个第三方封装成: XXXGateway
类,内部是单纯的封装第三方接口,不管对方是 HTTP 请求还是 SOAP 请求,都在内部进行统一处理。
另外有一层XXXProxy
来封装这些第三方提供的能力。这一层主要干两件事情:对传过来请求支付的数据进行个性化处理。对返回的结构进行统一处理返回上层统一的结构。当然根据特殊情况这里可以进行一切业务处理;
通过上面的操作变化已经基本上被完全封装了。如果新增一个支付渠道。只需要增加:XXXGateway
与 XXXProxy
。
那么 Context
与 Server
有什么用呢?Server
内部封装了所有的业务逻辑,它提供接口给 action 或者其它 server 进行调用。而 Context
这一层存在的价值是处理 Proxy
层返回的错误。以及在这里进行报警相关的处理。
上面的结构只是我的一个实践,欢迎大家讨论。
本文描述的系统只是满足了最基本的支付需求。缺少相关的监控、报警。
大家可以到我们的 GitHub主页留言
个人公众号:dayuTalk
联系邮箱:dayugog@gmail.com
GitHub:https://github.com/helei112g
coder,你会设计交易系统吗(实干篇)?相关推荐
- coder,你会设计交易系统吗(概念篇)?
文中我们从严谨的角度一步步聊到支付如何演变成独立的系统.内容包括:系统演进过程.接口设计.数据库设计以及代码如何组织的示例.若有不足之处,欢迎讨论共同学习. 从模块到服务 我记得最开始工作的时候,所有 ...
- 学习如何基于鳄鱼(Alligator)设计交易系统
概述 这是来自我们系列中的一篇新文章,是有关如何基于最流行的技术指标设计交易系统. 在本文中,我们将详细学习有关鳄鱼指标是什么.它衡量什么.我们如何计算它.以及如何读取和运用它. 然后,我们将基于该指 ...
- Hbase基础(特点、架构、应用场景、集群搭建、HA设计)这一篇就够了
Hbase基础(特点.架构.应用场景.集群搭建.HA设计)这一篇就够了 1. Hbase特点 2. Hbase VS RDBMS 3. Hbase架构及版本选择 4. Hbase应用场景 5. Ntp ...
- php k线 形态识别,趋势追踪,从设计交易系统开始(七)——形态识别,不只有K线...
昨天有朋友给我留言,希望看到单均线+形态识别的内容.我思考了一下,只是讲单均线加上某种K线形态识别的过滤,或者单均线加上某个其他的指标进行过滤有点敷衍.于是决定借机引出本文的话题. 有人把不能量化的技 ...
- 【学习记录】第一章 数据库设计-《SQL Server数据库设计和开发基础篇视频课程》...
一.课程笔记 1.1 软件开发周期 (1)需求分析阶段 分析客户的业务和数据处理需求. (2)概要设计阶段 设计数据库的E-R模型图,确认需求信息的正确和完整. /* E-R图:实体-关系图(Ent ...
- HBase之Rowkey设计总结与实战篇
HBase之Rowkey设计总结与实战篇 一.引言 HBase由于其存储和读写的高性能,在OLAP即时分析中越来越发挥重要的作用,在易观精细化运营产品–易观方舟也有广泛的应用.作为Nosql数据库的一 ...
- 软开关设计漫谈_软件篇
//======================================================================== //TITLE: // 软开关设计漫 ...
- 从游戏中学习产品设计2:消费篇
上一期,我们介绍了游戏中的诱导充值套路,没有看的朋友可点击 从游戏中学习产品设计1:充值篇!了解游戏中是如何引诱玩家充值的,今天我们来聊一聊游戏中的消费套路,上文介绍了游戏中的三类货币:金币,钱币和时 ...
- 数字IC设计——跨时钟域篇1(时钟域)
数字IC设计--跨时钟域篇1 一.时钟域概要 1. CDC介绍 CDC(clock domain crossing)检查(跨时钟域的检查)是对电路设计中同步电路设计的检查.非同步时钟没有固定的相位关系 ...
最新文章
- iBATIS之父:iBATIS框架的成功蜕变
- 一文解析|首个上榜科创板的机器人企业,江苏北人“闯关记”
- 深度解析AIoT背后的发展逻辑
- no qualifying bean of type_就是要让你彻底学会 @Bean 注解
- tp框架实现ajax
- Codeforces Round #382 (Div. 2)B. Urbanization 贪心
- 动态更改屏幕方向LANDSCAPE与PORTRAIT 转
- Pytest自定义标记mark及特定运行方式
- 华为户外模式怎么设置_华为FreeLace Pro降噪器效果怎么样?降噪开启和设置教程!...
- flex include和import
- 2021泰安市地区高考成绩排名查询,2021年山东高考成绩排名及一分一段表
- 偶遇拍外景的小姐姐们
- atmega 128 单片机 开发 例子 例程 教程 ADC PWM 呼吸灯
- 将CentOS的yum源更换成阿里源
- 一种基于波状扩散特征分析的光斑检测方法
- Java分层思想:Action层, Service层 ,modle层 和 Dao层的功能区分
- Java通过名字查询缘分,姓名缘分配对 从姓名笔画看两人姻缘
- Kalman滤波器从原理到实现
- 计算机毕业设计Java短视频交流点播系统(源码+系统+mysql数据库+lw文档)
- Java项目导入方法