开发前准备
- 重点:如果你正在开发的或从别人那接手的项目,财务系统或结算逻辑中可以存在负数,那么建议你不用准备分账了,不管你用什么办法(业务调整、将负账转变为逻辑负账、其他…),先把负数从系统中消除,不然硬给一个存在负账的系统上分账,只会带来灾难。
- 找一家合适的支付机构,(不推荐通联,坑多,他们内部的系统估计是堆屎山)
- 问清楚以下问题,(说多了都是泪,都是在通联踩的坑)
- 会员(在支付机构中开户的用户)是否同时支持商家身份和消费者身份,如果你的系统没分角色,就要考虑怎么让你系统的用户和会员角色建立一对多的映射,消费时取消费者会员角色,收款时取商家会员角色
- 分账时人数限制,如果有单笔分账的人数限制,就要考虑怎么让你的订单分账逻辑支持多次分账
- 确保公司的对公账户能正常在支付机构提现、充值、转账
- 老用户如何迁移到支付机构的用户系统中,如果支持批量导入,先测试一遍,不要对这些支付机构的系统抱有乐观心态
- 。。。后面想起来了再加
业务设计
- 确保你的系统对每一笔订单都能明确知道
相关角色和平台应该得到多少收益
,如果这都不能确定,你的系统就不是一个可以正常的电商系统。 - 如果订单上
剩余的现金(订单实付金额-订单实退金额)
小于相关人的收益总和,显然在现金上是不够完成分账的,这时候就要使用代金券功能,支付一笔足额的代金券,补齐每一方的收益(除了平台,多出来的支出本身就是平台自己承担) - 计算公式:
订单剩余现金 = 订单实付金额 - 订单实退金额 # 如果平台收益为负,说明平台在这笔订单上收益为0,还要支出同等的正数金额的代金券去补齐其他三方的收益 平台收益 = 订单剩余现金 - 三方收益总和(排除平台) 实际平台收益 = max(平台收益,0) # 如果现金分账比例=1.0,则不需要考虑代金券,说明订单剩余的现金够分账 现金分账比例 = 订单剩余现金/(实际平台收益 + 三方收益总和) 现金分账比例 = min(现金分账比例,1.0) # 分账比例最大就是1.0 三方现金收益总和 = 三方收益总和 * 现金分账比例 实际平台收益 = 订单剩余现金 - 三方现金收益总和 # 考虑到小数的精度问题,用减法将剩余的金额归并到平台头上 代金券支付金额 = 三方代金券收益总和 = 三方收益总和 - 三方现金收益总和 代金券分账比例 = 代金券支付金额/三方收益总和
- 实际举例
订单剩余现金 = 50 - 40 #10 三方收益总和 = 60 平台收益 = 订单剩余现金 - 三方收益总和 # 10-60=-50 实际平台收益 = max(平台收益,0) # 0 现金分账比例 = 订单剩余现金/(实际平台收益 + 三方收益总和) # 10/(0+60)=0.166 现金分账比例 = min(现金分账比例,1.0) # 0.166 三方现金收益总和 = 三方收益总和 * 现金分账比例 # 60*0.166=9.96 实际平台收益 = 订单剩余现金 - 三方现金收益总和 # 10-9.96=0.04 代金券支付金额 = 三方代金券收益总和 = 三方收益总和 - 三方现金收益总和 # 60-9.96=50.04 代金券分账比例 = 代金券支付金额/三方收益总和 # 50.04/60=0.834 # 0.834 + 0.166=1.0 刚好等于1
数据库设计
- 分账主单表
CREATE TABLE `order_split` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`order_id` BIGINT NOT NULL COMMENT '主订单id',
`order_no` VARCHAR(100) NOT NULL COMMENT '主订单编号' COLLATE 'utf8mb4_general_ci',
`receiver_id` BIGINT UNSIGNED NOT NULL COMMENT '收款人',
`split_no` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '分账编号' COLLATE 'utf8mb4_general_ci',
`out_split_no` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '外部分账编号' COLLATE 'utf8mb4_general_ci',
`batch_no` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '批次编号' COLLATE 'utf8mb4_general_ci',
`amounts` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '总分账金额',
`plat_amount` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '平台分账金额',
`order_type` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '订单类型 1消费订单 2代金券订单',
`split_status` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '分账状态 0创建 1成功 2失败',
`call_remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '分账|回调备注' COLLATE 'utf8mb4_general_ci',
`create_time` DATETIME NOT NULL DEFAULT (now()) COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `order_id` (`order_id`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
;
- 分账明细
CREATE TABLE `order_split_item` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
`split_id` BIGINT UNSIGNED NOT NULL COMMENT '订单分账id',
`customer_id` BIGINT UNSIGNED NOT NULL COMMENT '收益收款人',
`role_type` TINYINT UNSIGNED NOT NULL COMMENT '分账角色类型',
`amount` INT UNSIGNED NOT NULL DEFAULT '0' COMMENT '分账收益',
`remark` VARCHAR(255) NOT NULL COMMENT '备注' COLLATE 'utf8mb4_general_ci',
`create_time` DATETIME NOT NULL DEFAULT (now()) COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `split_id` (`split_id`) USING BTREE,
INDEX `customer_id` (`customer_id`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
;
3.代金券订单表
CREATE TABLE `voucher_order` (
`id` BIGINT UNSIGNED NOT NULL COMMENT '主键',
`order_id` BIGINT UNSIGNED NOT NULL COMMENT '主订单id',
`customer_id` BIGINT UNSIGNED NOT NULL COMMENT '主订单下单用户id',
`receiver_id` BIGINT UNSIGNED NOT NULL COMMENT '收款人id',
`order_no` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '流水号' COLLATE 'utf8mb4_general_ci',
`out_trade_no` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '外部流水号' COLLATE 'utf8mb4_general_ci',
`payment` INT UNSIGNED NOT NULL,
`refund` INT UNSIGNED NOT NULL DEFAULT '0',
`split_ratio` DECIMAL(5,4) NULL DEFAULT NULL COMMENT '代金券占整个分账总额的比例',
`remark` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '备注' COLLATE 'utf8mb4_general_ci',
`pay_status` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '支付状态 0创建 1支付成功 2支付失败' ,
`create_time` DATETIME NOT NULL DEFAULT (now()) COMMENT '创建时间',
`update_time` DATETIME NOT NULL DEFAULT (now()) ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`pay_time` DATETIME NULL DEFAULT NULL COMMENT '支付时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `order_id` (`order_id`) USING BTREE,
INDEX `customer_id` (`customer_id`) USING BTREE
)
COLLATE='utf8mb4_general_ci'
ENGINE=InnoDB
;
代码设计
自行实现...