前言
在涉及关于金钱的业务系统时,一定要注意数据安全。通常可以把财务相关的数据库,独立于其他的业务系统数据库。从表的设计角度来看。应该纪录每一步资金的流向,方便数据追踪下面来开始设计数据表。
1,表设计
这里主要用到的表有。用户钱包表,流水纪录表,充值订单表,支付纪录表,提现表。
DROP TABLE IF EXISTS `user_wallet`;
CREATE TABLE `user_wallet` (
`user_uuid` char(32) NOT NULL COMMENT '用户user_uuid',
`wallet_income` decimal(10,2) unsigned DEFAULT '0.00' COMMENT '钱包总收入额',
`wallet_outcome` decimal(10,2) unsigned DEFAULT '0.00' COMMENT '钱包总支出额',
`balance_fee` decimal(10,2) unsigned DEFAULT '0.00' COMMENT '钱包总可用余额',
`check_sign` varchar(100) DEFAULT '' COMMENT '用于安全检查,检查不通过为异常。',
`update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`user_uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户钱包';
上面的钱包表中,check_sign字段,是有效保证数据安全,不被轻易篡改的方法。sign字段的值是当前一条纪录的数据加密计算值。在每次对这张表进行操作时。都要进行check_sign字段签名验证。通过则继续。不通过,则可以根据具体情况采取措施。比如发送通知到相关人员。
DROP TABLE IF EXISTS `user_wallet_log`;
CREATE TABLE `user_wallet_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'auto id',
`user_uuid` char(32) DEFAULT '' COMMENT '用uuid',
`number` varchar(32) NOT NULL DEFAULT '' COMMENT '流水号',
`target_type` smallint(5) unsigned DEFAULT '0' COMMENT '业务类型,1:充值,2:提现 3:下单',
`target_uuid` char(32) DEFAULT '' COMMENT '来源uuid(如提现uuid)',
`action_type` smallint(5) unsigned DEFAULT '0' COMMENT '操作类型,1:充值,2:提现,3:订单',
`fee` decimal(10,2) DEFAULT '0.00' COMMENT '变动的金额,正负数。',
`original_account_json` varchar(1000) DEFAULT '账户变更前的数据 json存储',
`dispose_account_json` varchar(1000) DEFAULT '账户变更后的数据 json存储',
`status` smallint(5) unsigned DEFAULT '0' COMMENT '处理状态1,处理完成,0未完成',
`result_type` smallint(5) unsigned DEFAULT '0' COMMENT '处理结果,0:没有变更,1:有变更。',
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户钱包流水记录表';
流水纪录表中的original_account_json和dispose_account_json字段,可以用来排查账户异常,方便梳理对帐。
DROP TABLE IF EXISTS `user_top_up_order`;
CREATE TABLE `user_top_up_order` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'auto id',
`user_uuid` char(32) NOT NULL DEFAULT '' COMMENT '用户ID',
`order_num` char(32) NOT NULL COMMENT '订单号',
`amounts` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '订单总金额',
`currency` varchar(8) NOT NULL DEFAULT 'CNY' COMMENT '货币类型',
`pay_fee` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '实际支付金额',
`exchange` decimal(10,4) NOT NULL DEFAULT '0.00' COMMENT '汇率',
`status` tinyint(5) NOT NULL DEFAULT '0' COMMENT '支付状态:0待支付,1已支付,100已取消',
`update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`deleted` tinyint(5) NOT NULL DEFAULT '0' COMMENT '0未删除1已删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='充值订单';
充值订单没什么好说的
DROP TABLE IF EXISTS `user_withdraw_cash_list`;
CREATE TABLE `user_withdraw_cash_list` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'auto id',
`user_uuid` varchar(32) NOT NULL COMMENT '申请用户uuid',
`withdraw_way` tinyint(1) NOT NULL DEFAULT '1' COMMENT '提现(渠道)方式 1银行转账',
`withdraw_status` smallint(5) unsigned DEFAULT '0' COMMENT '处理状态。 1发起申请(待审核理)前台显示处理中,2提现成功,3审核不通过',
`number` char(32) DEFAULT '' COMMENT '提现单号',
`receivable_account` varchar(32) DEFAULT '' COMMENT '收款账户',
`name` varchar(30) DEFAULT '' COMMENT '收款人姓名',
`address` varchar(100) DEFAULT '' COMMENT '开户行地址',
`withdraw_fee` decimal(10,2) DEFAULT '0.00' COMMENT '提现金额',
`content` varchar(500) DEFAULT '' COMMENT '审核不通过原因',
`verify_user` varchar(32) DEFAULT '' COMMENT '审核人',
`action_user` varchar(32) DEFAULT '' COMMENT '操作人',
`action_at` datetime DEFAULT NULL COMMENT '审核时间',
`sent_notice_at` datetime DEFAULT NULL COMMENT '发送通知时间',
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除:0未删除,1已删除',
PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='提现记录表';
一般来说,有充值,就有提现。很多业务系统中没有充值,也会有提现场景。最后一张就是支付纪录表了。
DROP TABLE IF EXISTS `payment_list`;
CREATE TABLE `payment_list` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'auto id',
`user_uuid` varchar(32) NOT NULL DEFAULT '' COMMENT '用户uuid',
`pay_num` bigint(20) unsigned DEFAULT '0' COMMENT '支付号',
`trade_no` varchar(64) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '第三方付款成功交易号',
`order_type` smallint(5) unsigned DEFAULT '0' COMMENT '订单类型 1充值 ',
`order_uuid` char(32) DEFAULT '' COMMENT '充值订单表uuid',
`pay_way` smallint(5) unsigned DEFAULT '0' COMMENT '付款方式 1微信 2支付宝 ',
`pay_status` smallint(5) unsigned DEFAULT '0' COMMENT '支付状态 1 支付成功,2 支付失败 ',
`step` smallint(5) unsigned DEFAULT '0' COMMENT '步骤 1 创建支付,2 支付回调通知',
`post_status` smallint(5) unsigned DEFAULT '0' COMMENT '请求支付状态,1:成功,2:失败',
`return_status` smallint(5) unsigned DEFAULT '0' COMMENT '回调状态,1:成功,2:失败',
`total_fee` decimal(10,2) unsigned DEFAULT '0.00' COMMENT '支付价格',
`result_code` varchar(30) DEFAULT '' COMMENT '第三方返回错误码',
`err_code_str` varchar(255) DEFAULT '' COMMENT '第三方返回的错误记录',
`currency` varchar(8) NOT NULL DEFAULT 'HKD' COMMENT '货币类型',
`ip2long` int(10) unsigned DEFAULT '0' COMMENT 'ip2long',
`post_json` varchar(2000) DEFAULT '' COMMENT '提交post json 数据',
`return_json` varchar(2000) DEFAULT '' COMMENT '回调post return data json',
`create_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`success_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '支付成功时间',
`procedure_kb` mediumint(8) unsigned DEFAULT '0' COMMENT '手续费比例,千位比。6/1000',
`procedure_fee` decimal(10,2) unsigned DEFAULT '0.00' COMMENT '支付渠道收取手续费金额',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付记录列表';
以上,用户钱包功能所需表就设计好了。之前一篇提到的支付纪录表也面世了。因为很多公司并没有这张表,或者说只有一张简单的表纪录第三方交易单号的信息和订单信息。而且后台管理系统没有页面展示。导致客服运营等人员,经常反馈支付失败的问题。很多时候只是网络延时导致订单回调不及时,而没有修改订单状态。而我们开发则不得不停下手中的活,去排查相关的问题。
2 简要流程
这里就不画流程图和时序图了。简单说下步骤
- 何时创建用户钱包纪录?。新用户在注册时可创建。老用户写脚本创建。
- 创建用户钱包纪录根据用的钱包纪录信息生产一个sign。并同时创建一条流水纪录。
- 操作钱包表时,要先获取一条纪录。生产一个sign值,然后根据纪录的sign值进行对比。相同则继续走正常的步骤。不同时,则应该终止后续操作,并发通知给相关人员进行排查异常。
- 重新生成sign值,并正常更新数据,纪录流水纪录。
- 支付纪录表,应该是每发起一次支付,就要生成一条支付纪录。同一个订单对应多个支付纪录。
3,check_sign字段生成
/**
* @param $walletInfo
*/
public function updateSign($walletInfo)
{
unset($walletInfo['check_sign']);
unset($walletInfo['update_at']);
$walletCheckKey = config('bill.walletCheckKey');
$checkSign = $this->encode_HMAC($walletInfo, $walletCheckKey);
$update['check_sign'] = $checkSign;
$this->walletRepository->saveTransaction($walletInfo['id'], $update);
}
/**
* 检查sign
*
* @param $walletInfo
*
* @return bool
*/
public function walletSignCheck($walletInfo)
{
$walletCheckKey = config('bill.walletCheckKey');
$check = false;
$checkSignDB = $walletInfo['check_sign'];
unset($walletInfo['check_sign']);
unset($walletInfo['update_at']);
$checkSign = $this->encode_HMAC($walletInfo, $walletCheckKey);
if ($checkSignDB == $checkSign) {
$check = true;
}
return $check;
}
/** * sign生成
* * @param $walletInfo
* * @return bool
*/
protected function encode_HMAC($data, $walletCheckKey)
{
if (empty($data) || !is_array($data)) {
_writeBizException(json_encode([$data]), __FUNCTION__, class_basename(__CLASS__));
}
ksort($data);
$str = "";
foreach ($data as $k => $v) {
$str = $str . "&" . $k . "=" . $v;
}
$str = trim($str, "&");
$str = $str . "&key=" . $walletCheckKey;
$re = strtoupper(hash_hmac("sha256", $str, $walletCheckKey));
return $re;
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 lzdong@foxmail.com