20 KiB
结算与财务系统设计文档
版本:v2.0
最后更新:2026-05-21
状态:✅ 已实现并优化
📋 目录
系统概述
结算与财务系统是平台的核心模块,负责处理订单支付、资金流转、商家结算、平台钱包和提现等业务。
核心功能
- ✅ 订单支付时记录平台账户收入
- ✅ 自动周结算(每周一凌晨2点)
- ✅ 手动执行周结算(管理员触发)
- ✅ 结算预览功能
- ✅ 防止重复结算
- ✅ 服务费自动计算和扣除
- ✅ 平台钱包管理
- ✅ 商家账户余额管理
- ✅ 提现申请和审核
- ✅ 完整的交易流水记录
核心概念
平台账户 vs 平台钱包
平台账户(Platform Account)
- 定位:资金汇总账户,记录所有资金流转
- 包含内容:
- 用户支付的订单金额(全额)
- 待结算给商家的金额
- 平台的收入(服务费、广告费等)
- 平台的支出(商家结算、邀请返现等)
平台钱包(Platform Wallet)
- 定位:平台可支配的资金
- 包含内容:
- 服务费收入
- 其他收入(广告费、会员费等)
- 扣除:邀请返现等支出
- 计算公式:
钱包余额 = 账户总收入 - 账户总支出 = (订单总额) - (商家结算 + 邀请返现) = (商家应得 + 服务费) - 商家应得 - 邀请返现 = 服务费 + 其他收入 - 邀请返现
三方账户体系
系统账户体系:
├─ 平台账户(PlatformAccount)
│ └─ 主账户(全局唯一,资金中转)
├─ 商家账户(MerchantAccount)
│ └─ 每个商家一个账户
└─ 用户账户(UserAccount)
└─ 每个用户一个账户(用于返现、提现)
可提现金额
| 角色 | 可提现金额计算 | 说明 |
|---|---|---|
| 商家 | balance - frozen_balance |
已结算金额(扣除服务费后) |
| 用户 | balance - frozen_balance |
邀请返现所得 |
| 平台 | total_service_fee - frozen_balance |
服务费收入(未来可扩展其他收入) |
资金流转流程
完整流程图
┌─────────────┐
│ 用户支付 │
└──────┬──────┘
│ 订单金额(全额)
↓
┌─────────────────────┐
│ 平台账户收入 │
│ - 记录交易流水 │
│ - 更新账户余额 │
│ - 累加服务费 │
└──────┬──────────────┘
│ 等待订单完成
↓
┌─────────────────────┐
│ 商家确认离店 │
│ - 订单状态: completed│
│ - 记录离店时间 │
└──────┬──────────────┘
│ 等待周结算
↓
┌─────────────────────┐
│ 周结算(自动) │
│ - 每周一凌晨2点 │
│ - 统计上周完成订单 │
│ - 计算结算金额 │
└──────┬──────────────┘
│ 结算金额 = 订单金额 - 服务费
↓
┌─────────────────────┐
│ 商家账户入账 │
│ - 平台账户扣减 │
│ - 商家账户增加 │
│ - 复式记账 │
└──────┬──────────────┘
│ 商家可提现
↓
┌─────────────────────┐
│ 商家申请提现 │
│ - 冻结余额 │
│ - 管理员审核 │
│ - 确认打款 │
└─────────────────────┘
详细流程说明
1. 用户支付订单
用户支付 1000元
├─ 平台账户收入:+1000元
│ ├─ balance: +1000
│ ├─ total_income: +1000
│ └─ total_service_fee: +50(5%)
└─ 商家账户:无变化(等待结算)
代码位置:order.service.ts
await this.accountService.addPlatformBalance(
order.payAmount, // 订单金额(全额)
order.serviceFee, // 服务费(5%)
transactionNo,
'order_payment',
order.id,
order.orderNo,
`订单 ${order.orderNo} 支付收入`
);
2. 订单完成后周结算
每周一凌晨2点自动结算
├─ 计算商家应得:1000 × 95% = 950元
├─ 平台账户支出:-950元
│ ├─ balance: 1000 - 950 = 50
│ └─ total_expense: +950
├─ 商家账户收入:+950元
│ ├─ balance: +950
│ └─ total_income: +950
└─ 平台保留服务费:50元
3. 邀请返现(可选)
用户邀请新用户注册
├─ 平台账户支出:-10元
│ ├─ balance: 50 - 10 = 40
│ └─ total_expense: +10
└─ 用户账户收入:+10元
├─ balance: +10
└─ total_income: +10
4. 各方提现
商家提现:500元
├─ 商家账户:balance -500, frozen_balance +500
└─ 审核通过后:frozen_balance -500, total_expense +500
用户提现:10元
├─ 用户账户:balance -10, frozen_balance +10
└─ 审核通过后:frozen_balance -10, total_expense +10
平台提现:30元
├─ 平台账户:balance -30, frozen_balance +30
└─ 审核通过后:frozen_balance -30, total_expense +30
平台钱包设计
钱包余额计算
// 钱包余额 = 账户总收入 - 账户总支出
balance = total_income - total_expense
// 展开计算
balance = (订单总额) - (商家结算 + 邀请返现)
= (商家应得 + 服务费) - 商家应得 - 邀请返现
= 服务费 - 邀请返现
可提现金额计算
当前实现:
// 可提现金额 = 累计服务费 - 冻结余额
const withdrawableAmount = total_service_fee - frozen_balance;
未来扩展:
// 方案1:使用钱包余额(推荐)
const withdrawableAmount = balance - frozen_balance;
// 方案2:累加所有收入类型
const withdrawableAmount = (total_service_fee + total_ad_revenue + ...) - frozen_balance;
前端展示
核心指标卡片
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ 钱包余额 │ 可提现金额 │ 冻结金额 │ 服务费收入 │
│ 40.00元 │ 40.00元 │ 0.00元 │ 50.00元 │
│ 平台可支配 │ 余额-冻结 │ 提现申请中 │ 累计服务费 │
└─────────────┴─────────────┴─────────────┴─────────────┘
收入支出统计
┌─────────────────┬─────────────────┬─────────────────┐
│ 账户总收入 │ 账户总支出 │ 其他收入 │
│ 1000.00元 │ 960.00元 │ 0.00元 │
│ 所有订单支付 │ 结算+返现 │ 广告费、会员费 │
└─────────────────┴─────────────────┴─────────────────┘
结算周期
周结算机制
执行时间:每周一凌晨 2:00
结算周期:上周一 00:00:00 ~ 上周日 23:59:59
结算对象:状态为 completed 的订单
判断依据:订单的 checkout_at(离店时间)字段
定时任务配置
@Cron('0 2 * * 1') // 每周一凌晨2点
async handleWeeklySettlement() {
await this.settlementService.handleWeeklySettlement();
}
结算逻辑
-
查询上周已完成订单
const orders = await this.orderRepo .createQueryBuilder('o') .where('o.status = :status', { status: 'completed' }) .andWhere('o.checkout_at BETWEEN :start AND :end', { start: `${lastWeekStart} 00:00:00`, end: `${lastWeekEnd} 23:59:59` }) .getMany(); -
按商家分组统计
- 订单数量:
orderCount - 订单总额:
orderAmount - 服务费总额:
serviceFee - 结算金额:
settlementAmount = orderAmount - serviceFee
- 订单数量:
-
生成结算单
- 创建
Settlement记录 - 创建
SettlementItem记录
- 创建
-
执行资金转账
- 平台账户扣减
- 商家账户增加
- 复式记账
防止重复结算
// 检查该周期是否已经结算过
const existingSettlements = await this.settlementRepo.count({
where: {
periodStart: lastWeekStart,
periodEnd: lastWeekEnd
}
});
if (existingSettlements > 0) {
throw new Error(`该周期 ${lastWeekStart} ~ ${lastWeekEnd} 已经结算过,无法重复结算`);
}
服务费计算
计算公式
// 用户实付金额
payAmount = totalAmount - couponDiscount
// 服务费
serviceFee = Math.round(payAmount * serviceFeeRate * 100) / 100
// 商家实收金额
merchantIncome = payAmount - serviceFee
服务费率配置
默认值:5% (0.05)
配置位置:platform_configs 表
配置键:service_fee_rate
示例计算
房费总额:1000元
优惠券抵扣:100元
服务费率:5%
用户实付:1000 - 100 = 900元
服务费:900 × 0.05 = 45元
商家实收:900 - 45 = 855元
平台收入:45元
数据库设计
核心表结构
1. platform_accounts(平台账户表)
CREATE TABLE `platform_accounts` (
`id` BIGINT UNSIGNED AUTO_INCREMENT,
`account_name` VARCHAR(50) COMMENT '账户名称',
`balance` DECIMAL(12,2) DEFAULT 0 COMMENT '钱包余额',
`frozen_balance` DECIMAL(12,2) DEFAULT 0 COMMENT '冻结余额',
`total_income` DECIMAL(12,2) DEFAULT 0 COMMENT '账户总收入',
`total_expense` DECIMAL(12,2) DEFAULT 0 COMMENT '账户总支出',
`total_service_fee` DECIMAL(12,2) DEFAULT 0 COMMENT '累计服务费',
`version` INT UNSIGNED DEFAULT 0 COMMENT '乐观锁版本号',
`status` ENUM('active', 'frozen', 'closed') DEFAULT 'active',
PRIMARY KEY (`id`)
);
字段说明:
| 字段 | 说明 | 当前用途 | 未来扩展 |
|---|---|---|---|
balance |
钱包余额 | 服务费 - 邀请返现 | 所有收入 - 所有支出 |
frozen_balance |
冻结余额 | 提现申请中的金额 | 同左 |
total_income |
账户总收入 | 所有订单支付金额 | 订单 + 广告 + 会员等 |
total_expense |
账户总支出 | 商家结算 + 邀请返现 | 同左 + 其他支出 |
total_service_fee |
服务费收入 | 订单服务费累计 | 同左 |
2. merchant_accounts(商家账户表)
CREATE TABLE `merchant_accounts` (
`id` BIGINT UNSIGNED AUTO_INCREMENT,
`merchant_id` BIGINT UNSIGNED,
`balance` DECIMAL(12,2) DEFAULT 0 COMMENT '可用余额',
`frozen_balance` DECIMAL(12,2) DEFAULT 0 COMMENT '冻结余额',
`debt_amount` DECIMAL(12,2) DEFAULT 0 COMMENT '欠款金额',
`total_income` DECIMAL(12,2) DEFAULT 0 COMMENT '累计收入',
`total_expense` DECIMAL(12,2) DEFAULT 0 COMMENT '累计支出',
`total_settlement` DECIMAL(12,2) DEFAULT 0 COMMENT '累计结算',
`total_withdraw` DECIMAL(12,2) DEFAULT 0 COMMENT '累计提现',
`pending_settlement` DECIMAL(12,2) DEFAULT 0 COMMENT '待结算',
`last_settlement_at` DATETIME COMMENT '最后结算时间',
`version` INT UNSIGNED DEFAULT 0,
`status` ENUM('active', 'frozen', 'closed') DEFAULT 'active',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_merchant_id` (`merchant_id`)
);
3. settlements(结算单表)
CREATE TABLE `settlements` (
`id` BIGINT UNSIGNED AUTO_INCREMENT,
`settlement_no` VARCHAR(32) UNIQUE,
`merchant_id` BIGINT UNSIGNED,
`period_start` DATE,
`period_end` DATE,
`order_count` INT UNSIGNED DEFAULT 0,
`order_amount` DECIMAL(12,2) DEFAULT 0,
`service_fee` DECIMAL(12,2) DEFAULT 0,
`settlement_amount` DECIMAL(12,2) DEFAULT 0,
`status` ENUM('pending','settled','failed'),
`settled_at` DATETIME,
PRIMARY KEY (`id`),
KEY `idx_merchant_id` (`merchant_id`),
KEY `idx_period` (`period_start`, `period_end`)
);
4. platform_transactions(平台交易流水表)
CREATE TABLE `platform_transactions` (
`id` BIGINT UNSIGNED AUTO_INCREMENT,
`transaction_no` VARCHAR(32) UNIQUE,
`account_id` BIGINT UNSIGNED,
`direction` ENUM('income','expense'),
`amount` DECIMAL(12,2),
`balance_before` DECIMAL(12,2),
`balance_after` DECIMAL(12,2),
`transaction_type` VARCHAR(50),
`business_type` VARCHAR(50),
`business_id` BIGINT UNSIGNED,
`business_no` VARCHAR(32),
`related_account_type` ENUM('merchant','user'),
`related_account_id` BIGINT UNSIGNED,
`remark` VARCHAR(255),
PRIMARY KEY (`id`),
KEY `idx_account_id` (`account_id`),
KEY `idx_business` (`business_type`, `business_id`)
);
API接口
平台钱包接口
1. 查询平台账户
接口:GET /api/admin/finance/platform-accounts
权限:平台管理员
响应示例:
{
"id": 1,
"account_name": "主账户",
"balance": 40.00,
"frozen_balance": 0.00,
"total_income": 1000.00,
"total_expense": 960.00,
"total_service_fee": 50.00,
"status": "active"
}
2. 平台申请提现
接口:POST /api/admin/finance/platform-withdrawals
权限:平台管理员
请求参数:
{
"amount": 30.00,
"bankName": "中国工商银行",
"bankAccount": "6222021234567890123",
"accountName": "某某科技有限公司",
"remark": "提现备注"
}
结算管理接口
3. 预览周结算
接口:GET /api/admin/finance/settlements/preview-weekly
权限:平台管理员
响应示例:
{
"periodStart": "2026-05-13",
"periodEnd": "2026-05-19",
"totalOrders": 45,
"totalAmount": 35000.00,
"totalServiceFee": 1750.00,
"totalSettlementAmount": 33250.00,
"merchants": [...]
}
4. 执行周结算
接口:POST /api/admin/finance/settlements/execute-weekly
权限:平台管理员
技术实现
核心服务
1. AccountService(账户服务)
文件位置:account.service.ts
核心方法:
// 平台账户增加余额(订单支付)
async addPlatformBalance(
amount: number,
serviceFee: number,
transactionNo: string,
businessType: string,
businessId: number,
businessNo: string,
remark: string,
): Promise<void> {
// 更新字段:
// - balance: +amount
// - total_income: +amount
// - total_service_fee: +serviceFee
}
// 平台账户扣减余额(结算/返现)
async deductPlatformBalance(
amount: number,
transactionNo: string,
transactionType: string,
businessType: string,
businessId: number,
businessNo: string,
remark: string,
): Promise<void> {
// 更新字段:
// - balance: -amount
// - total_expense: +amount
}
2. WithdrawalService(提现服务)
平台提现逻辑:
async createPlatformWithdrawal(dto: {
amount: number;
bankName: string;
bankAccount: string;
accountName: string;
}) {
const account = await this.accountService.getPlatformAccount();
// 计算可提现金额
const availableAmount = Number(account.total_service_fee) - Number(account.frozen_balance);
if (availableAmount < amount) {
throw new BadRequestException(`可提现金额不足,当前可提现:${availableAmount.toFixed(2)}元`);
}
// 冻结余额
account.balance -= amount;
account.frozen_balance += amount;
// 创建提现记录
// ...
}
并发控制
悲观锁
const account = await queryRunner.manager.findOne(PlatformAccount, {
where: { account_name: '主账户' },
lock: { mode: 'pessimistic_write' } // 行级锁
});
乐观锁
await this.accountRepo.update(
{ id: accountId, version: currentVersion },
{ balance: newBalance, version: currentVersion + 1 }
);
复式记账
每笔转账生成两条流水记录:
- 一条支出记录(平台账户)
- 一条收入记录(商家账户)
两条流水的 transaction_no 相同,通过 related_account_type 和 related_account_id 关联。
前端页面
1. 平台钱包页面
文件位置:PlatformWallet.tsx
功能特性:
- 钱包余额、可提现金额、冻结金额、服务费收入展示
- 账户总收入、总支出、其他收入统计
- 钱包详情查看
- 申请提现功能(集成在页面内)
2. 平台交易记录
功能特性:
- 交易流水查询(按流水号、方向、类型、时间筛选)
- 交易详情展示
- 分页展示
3. 结算管理
文件位置:Settlements.tsx
功能特性:
- 结算单列表查询
- 结算单详情查看
- 预览周结算数据
- 手动执行周结算
4. 商家提现审核
文件位置:Withdrawals.tsx
功能特性:
- 商家提现申请列表
- 审核通过/拒绝
- 确认打款
扩展方案
未来增加其他收入
1. 数据库增加字段
ALTER TABLE platform_accounts
ADD COLUMN total_ad_revenue DECIMAL(12,2) DEFAULT 0 COMMENT '累计广告收入',
ADD COLUMN total_membership_fee DECIMAL(12,2) DEFAULT 0 COMMENT '累计会员费收入',
ADD COLUMN total_other_income DECIMAL(12,2) DEFAULT 0 COMMENT '累计其他收入';
2. 后端增加方法
// 广告收入
async addPlatformAdRevenue(amount: number, ...): Promise<void> {
account.balance += amount;
account.total_income += amount;
account.total_ad_revenue += amount;
}
// 会员费收入
async addPlatformMembershipFee(amount: number, ...): Promise<void> {
account.balance += amount;
account.total_income += amount;
account.total_membership_fee += amount;
}
3. 前端增加展示
<Statistic
title="广告收入"
value={account.total_ad_revenue}
precision={2}
suffix="元"
/>
<Statistic
title="会员费收入"
value={account.total_membership_fee}
precision={2}
suffix="元"
/>
4. 调整可提现计算
// 方案1:继续使用各项收入累加
const withdrawableAmount = (
total_service_fee +
total_ad_revenue +
total_membership_fee
) - frozen_balance;
// 方案2:直接使用钱包余额(推荐)
const withdrawableAmount = balance - frozen_balance;
推荐方案2,因为:
balance已经包含了所有收入和支出- 不需要每次增加新收入类型都修改代码
- 更简洁、更易维护
数据一致性保证
1. 事务保证
所有涉及金额变动的操作都在事务中执行:
- 订单支付:订单状态更新 + 平台账户收入
- 结算转账:平台账户扣减 + 商家账户增加 + 结算单生成
- 提现:余额冻结/扣减 + 提现记录更新
2. 乐观锁
账户表使用 version 字段实现乐观锁,防止并发更新导致余额错误。
3. 复式记账
每笔转账生成两条流水记录,确保资金流向可追溯。
4. CHECK 约束
CONSTRAINT `chk_balance` CHECK (`balance` >= 0)
CONSTRAINT `chk_frozen_balance` CHECK (`frozen_balance` >= 0)
相关文档
维护团队:开发团队
最后更新:2026-05-21