通过 上篇文章 的分析,我们已经明确了这个系统要干些什么。接下来的都是实打实的干货。这些内容认真阅读掌握后,相信你能够以此为基础设计一个维护性好、扩展性好的交易系统。

数据库设计

数据的设计是按照:交易、退款、日志 来设计的。对于上面说到的对账等功能并没有。这部分不难大家可以自行设计,按照上面讲到的思路。主要的表介绍如下:

  • 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_transactionpay_transaction_extension 中。

收到通知,记录数据到 pay_log_data 中,然后根据时支付的通知还是退款的通知,更新 pay_transactionpay_refund 的状态。如果是重复支付需要记录数据到 pay_repeat_transaction 中。并且将需要通知应用的数据记录到 pay_notify_app_log,这张表相当于一个消息表,会有消费者会去消费其中的内容。

退款 记录日志日志到 pay_log_data 中,然后记录数据到退款表中 pay_refund

当然这其中还有些细节,需要大家自己看了表结构,实际去思考一下该如何使用。如果有任何疑问欢迎到我们GitHub的项目(点击阅读原文)中留言,我们都会一一解答。

这些表能够满足最基本的需求,其它内容可根据自己的需求进行扩张,比如:支持用户卡列表、退款走银行卡等。

系统设计

这部分主要说下系统该如何搭建,以及代码组织方式的建议。

系统架构

由于支付系统的安全性非常高,因此不建议将对应的入口直接暴露给用户可见。应该是在自己的应用系统中调用支付系统的接口来完成业务。另外系统对数据要求是:强一致性的。因此也没有缓存介入(当如缓存可以用来做报警,这不在本位范畴)。

具体的实现,系统会使用两个域名,一个为内部使用,只有指定来源的ip能够访问固定功能(访问除通知外的其它功能)。另一个域名只能访问 notify return 两个路由。通过这种方式可以保证系统的安全。

在数据库的使用上无论什么请求直接走 Master 库。这样保证数据的强一致。当然从库也是需要的。比如:账单、对账相关逻辑我们可以利用从库完成。

代码设计

不管想做什么最终都要用代码来实现。我们都知道需要可维护、可扩展的代码。那么具体到支付系统你会怎么做呢?我已支付为例说下我的代码结构设计思路。仅供参考。比如我要介入:微信、支付宝、招行 三家支付。我的代码结构图如下:

用文字简单介绍下。我会将每一个第三方封装成: XXXGateway 类,内部是单纯的封装第三方接口,不管对方是 HTTP 请求还是 SOAP 请求,都在内部进行统一处理。

另外有一层XXXProxy 来封装这些第三方提供的能力。这一层主要干两件事情:对传过来请求支付的数据进行个性化处理。对返回的结构进行统一处理返回上层统一的结构。当然根据特殊情况这里可以进行一切业务处理;

通过上面的操作变化已经基本上被完全封装了。如果新增一个支付渠道。只需要增加:XXXGatewayXXXProxy

那么 ContextServer 有什么用呢?Server 内部封装了所有的业务逻辑,它提供接口给 action 或者其它 server 进行调用。而 Context 这一层存在的价值是处理 Proxy 层返回的错误。以及在这里进行报警相关的处理。

上面的结构只是我的一个实践,欢迎大家讨论。

本文描述的系统只是满足了最基本的支付需求。缺少相关的监控、报警。
大家可以到我们的 GitHub主页留言

个人公众号:dayuTalk

联系邮箱:dayugog@gmail.com

GitHub:https://github.com/helei112g

coder,你会设计交易系统吗(实干篇)?相关推荐

  1. coder,你会设计交易系统吗(概念篇)?

    文中我们从严谨的角度一步步聊到支付如何演变成独立的系统.内容包括:系统演进过程.接口设计.数据库设计以及代码如何组织的示例.若有不足之处,欢迎讨论共同学习. 从模块到服务 我记得最开始工作的时候,所有 ...

  2. 学习如何基于鳄鱼(Alligator)设计交易系统

    概述 这是来自我们系列中的一篇新文章,是有关如何基于最流行的技术指标设计交易系统. 在本文中,我们将详细学习有关鳄鱼指标是什么.它衡量什么.我们如何计算它.以及如何读取和运用它. 然后,我们将基于该指 ...

  3. Hbase基础(特点、架构、应用场景、集群搭建、HA设计)这一篇就够了

    Hbase基础(特点.架构.应用场景.集群搭建.HA设计)这一篇就够了 1. Hbase特点 2. Hbase VS RDBMS 3. Hbase架构及版本选择 4. Hbase应用场景 5. Ntp ...

  4. php k线 形态识别,趋势追踪,从设计交易系统开始(七)——形态识别,不只有K线...

    昨天有朋友给我留言,希望看到单均线+形态识别的内容.我思考了一下,只是讲单均线加上某种K线形态识别的过滤,或者单均线加上某个其他的指标进行过滤有点敷衍.于是决定借机引出本文的话题. 有人把不能量化的技 ...

  5. 【学习记录】第一章 数据库设计-《SQL Server数据库设计和开发基础篇视频课程》...

    一.课程笔记 1.1  软件开发周期 (1)需求分析阶段 分析客户的业务和数据处理需求. (2)概要设计阶段 设计数据库的E-R模型图,确认需求信息的正确和完整. /* E-R图:实体-关系图(Ent ...

  6. HBase之Rowkey设计总结与实战篇

    HBase之Rowkey设计总结与实战篇 一.引言 HBase由于其存储和读写的高性能,在OLAP即时分析中越来越发挥重要的作用,在易观精细化运营产品–易观方舟也有广泛的应用.作为Nosql数据库的一 ...

  7. 软开关设计漫谈_软件篇

    //========================================================================   //TITLE:   //    软开关设计漫 ...

  8. 从游戏中学习产品设计2:消费篇

    上一期,我们介绍了游戏中的诱导充值套路,没有看的朋友可点击 从游戏中学习产品设计1:充值篇!了解游戏中是如何引诱玩家充值的,今天我们来聊一聊游戏中的消费套路,上文介绍了游戏中的三类货币:金币,钱币和时 ...

  9. 数字IC设计——跨时钟域篇1(时钟域)

    数字IC设计--跨时钟域篇1 一.时钟域概要 1. CDC介绍 CDC(clock domain crossing)检查(跨时钟域的检查)是对电路设计中同步电路设计的检查.非同步时钟没有固定的相位关系 ...

最新文章

  1. iBATIS之父:iBATIS框架的成功蜕变
  2. 一文解析|首个上榜科创板的机器人企业,江苏北人“闯关记”
  3. 深度解析AIoT背后的发展逻辑
  4. no qualifying bean of type_就是要让你彻底学会 @Bean 注解
  5. tp框架实现ajax
  6. Codeforces Round #382 (Div. 2)B. Urbanization 贪心
  7. 动态更改屏幕方向LANDSCAPE与PORTRAIT 转
  8. Pytest自定义标记mark及特定运行方式
  9. 华为户外模式怎么设置_华为FreeLace Pro降噪器效果怎么样?降噪开启和设置教程!...
  10. flex include和import
  11. 2021泰安市地区高考成绩排名查询,2021年山东高考成绩排名及一分一段表
  12. 偶遇拍外景的小姐姐们
  13. atmega 128 单片机 开发 例子 例程 教程 ADC PWM 呼吸灯
  14. 将CentOS的yum源更换成阿里源
  15. 一种基于波状扩散特征分析的光斑检测方法
  16. Java分层思想:Action层, Service层 ,modle层 和 Dao层的功能区分
  17. Java通过名字查询缘分,姓名缘分配对 从姓名笔画看两人姻缘
  18. Kalman滤波器从原理到实现
  19. 计算机毕业设计Java短视频交流点播系统(源码+系统+mysql数据库+lw文档)
  20. Java项目导入方法

热门文章

  1. 如何判断一个网站是WordPress搭建的网站以及网站SEO网络推广
  2. 软件测试硬件培训,软件测试和硬件测试的技巧
  3. Unity 自定义圆形图片
  4. Chrome控制台打印输出彩色调试信息
  5. Js做的城市三级联动,原创
  6. Python 基于pyecharts自定义经纬度热力图可视化
  7. 易语言跳出循环 c,易语言教程循环控制(到循环尾和跳出循环)
  8. Windows XP 故障恢复控制台的说明
  9. 你问我答:U盘的CPU全部占满的处理办法——mfxp.com
  10. 转一首普希金的诗,给郁闷的日子煽煽情!