优化提现逻辑,修正可提现金额计算方式,确保准确反映累计佣金、已提现金额和待审核金额。同时,更新小程序界面,调整可提现金额的显示和按钮状态判断,提升用户体验。

This commit is contained in:
乘风
2026-02-06 11:06:36 +08:00
parent ab9289ba1f
commit f73c2b51ed
12 changed files with 2716 additions and 38 deletions

View File

@@ -43,7 +43,7 @@ export default function AdminLayout({ children }: { children: React.ReactNode })
// 简化菜单:按功能归类,保留核心功能
// PDF需求分账管理、分销管理、订单管理三合一 → 交易中心
const menuItems = [
{ icon: LayoutDashboard, label: "数据概览123", href: "/admin" },
{ icon: LayoutDashboard, label: "数据概览147", href: "/admin" },
{ icon: BookOpen, label: "内容管理", href: "/admin/content" },
{ icon: Users, label: "用户管理", href: "/admin/users" },
{ icon: Wallet, label: "交易中心", href: "/admin/distribution" }, // 合并:分销+订单+提现

View File

@@ -84,39 +84,75 @@ export async function POST(request: NextRequest) {
})
}
// 查询可提现金额(待结算收益
let totalEarnings = 0
// ✅ 修正:从 orders 表查询累计佣金(与前端逻辑一致
let totalCommission = 0
try {
const earningsResult = await query(`
SELECT COALESCE(SUM(commission), 0) as total_commission
FROM referral_bindings
WHERE referrer_id = ? AND status = 'converted'
// 读取分成比例
let distributorShare = 0.9 // 默认90%
try {
const config = await getConfig('referral_config')
if (config?.distributorShare) {
distributorShare = Number(config.distributorShare)
}
} catch (e) {
console.warn('[Withdraw] 读取分成比例失败,使用默认值 90%')
}
// 查询订单总金额
const ordersResult = await query(`
SELECT COALESCE(SUM(amount), 0) as total_amount
FROM orders
WHERE referrer_id = ? AND status = 'paid'
`, [userId]) as any[]
totalEarnings = parseFloat(earningsResult[0]?.total_commission || 0)
const totalAmount = parseFloat(ordersResult[0]?.total_amount || 0)
totalCommission = totalAmount * distributorShare
console.log('[Withdraw] 佣金计算:')
console.log('- 订单总金额:', totalAmount)
console.log('- 分成比例:', distributorShare * 100 + '%')
console.log('- 累计佣金:', totalCommission)
} catch (e) {
// 如果表不存在收益为0
console.log('[Withdraw] 查询收益失败,可能表不存在')
console.log('[Withdraw] 查询收益失败,可能表不存在:', e)
}
// 查询已提现金额
let withdrawnAmount = 0
let withdrawnEarnings = 0
try {
const withdrawnResult = await query(`
SELECT COALESCE(SUM(amount), 0) as withdrawn
FROM withdrawals
WHERE user_id = ? AND status = 'completed'
`, [userId]) as any[]
withdrawnAmount = parseFloat(withdrawnResult[0]?.withdrawn || 0)
withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
} catch (e) {
// 如果表不存在已提现为0
console.log('[Withdraw] 读取已提现金额失败:', e)
}
const availableAmount = totalEarnings - withdrawnAmount
// 查询待审核提现金额
let pendingWithdrawAmount = 0
try {
const pendingResult = await query(`
SELECT COALESCE(SUM(amount), 0) as pending_amount
FROM withdrawals
WHERE user_id = ? AND status = 'pending'
`, [userId]) as any[]
pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
} catch (e) {
console.log('[Withdraw] 查询待审核金额失败:', e)
}
// ✅ 修正:可提现金额 = 累计佣金 - 已提现金额 - 待审核金额(三元素完整校验)
const availableAmount = totalCommission - withdrawnEarnings - pendingWithdrawAmount
console.log('[Withdraw] 提现验证(完整版):')
console.log('- 累计佣金 (totalCommission):', totalCommission)
console.log('- 已提现金额 (withdrawnEarnings):', withdrawnEarnings)
console.log('- 待审核金额 (pendingWithdrawAmount):', pendingWithdrawAmount)
console.log('- 可提现金额 = 累计 - 已提现 - 待审核 =', totalCommission, '-', withdrawnEarnings, '-', pendingWithdrawAmount, '=', availableAmount)
console.log('- 申请提现金额 (amount):', amount)
console.log('- 判断:', amount, '>', availableAmount, '=', amount > availableAmount)
if (amount > availableAmount) {
return NextResponse.json({
success: false,
message: `可提现金额不足当前可提现 ¥${availableAmount.toFixed(2)}`
message: `可提现金额不足当前可提现 ¥${availableAmount.toFixed(2)}(累计 ¥${totalCommission.toFixed(2)} - 已提现 ¥${withdrawnEarnings.toFixed(2)} - 待审核 ¥${pendingWithdrawAmount.toFixed(2)}`
})
}
@@ -144,12 +180,13 @@ export async function POST(request: NextRequest) {
return NextResponse.json({
success: true,
message: '提现成功',
message: '提现申请已提交,正在审核中,通过后会自动到账您的微信零钱',
data: {
withdrawId,
amount,
account,
accountType: accountType === 'alipay' ? '支付宝' : '微信'
accountType: accountType === 'alipay' ? '支付宝' : '微信',
status: 'pending'
}
})

View File

@@ -24,7 +24,8 @@ Page({
// === 收益数据 ===
totalCommission: 0, // 累计佣金总额(所有获得的佣金)
availableEarnings: 0, // 可提现金额(未申请提现的佣金)
availableEarnings: 0, // 可提现金额(未申请提现的佣金)- 字符串格式用于显示
availableEarningsNum: 0, // 可提现金额 - 数字格式用于判断
pendingWithdrawAmount: 0, // 待审核金额(已申请提现但未审核)
withdrawnEarnings: 0, // 已提现金额
earnings: 0, // 已结算收益(保留兼容)
@@ -151,6 +152,22 @@ Page({
return typeof num === 'number' ? num.toFixed(2) : '0.00'
}
// ✅ 修正:可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
const totalCommissionNum = realData?.totalCommission || 0
const withdrawnNum = realData?.withdrawnEarnings || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = totalCommissionNum - withdrawnNum - pendingWithdrawNum
const minWithdrawAmount = realData?.minWithdrawAmount || 10
console.log('=== [Referral] 收益计算(完整版)===')
console.log('累计佣金 (totalCommission):', totalCommissionNum)
console.log('已提现金额 (withdrawnEarnings):', withdrawnNum)
console.log('待审核金额 (pendingWithdrawAmount):', pendingWithdrawNum)
console.log('可提现金额 = 累计 - 已提现 - 待审核 =', totalCommissionNum, '-', withdrawnNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
console.log('最低提现金额 (minWithdrawAmount):', minWithdrawAmount)
console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
this.setData({
isLoggedIn: true,
userInfo,
@@ -163,14 +180,15 @@ Page({
expiredCount,
// 收益数据 - 格式化为两位小数
totalCommission: formatMoney(realData?.totalCommission || 0),
availableEarnings: formatMoney(realData?.availableEarnings || 0),
pendingWithdrawAmount: formatMoney(realData?.pendingWithdrawAmount || 0),
totalCommission: formatMoney(totalCommissionNum),
availableEarnings: formatMoney(availableEarningsNum), // ✅ 使用计算后的可提现金额
availableEarningsNum: availableEarningsNum, // ✅ 数字格式用于按钮判断
pendingWithdrawAmount: formatMoney(pendingWithdrawNum),
withdrawnEarnings: formatMoney(realData?.withdrawnEarnings || 0),
earnings: formatMoney(realData?.earnings || 0),
pendingEarnings: formatMoney(realData?.pendingEarnings || 0),
shareRate: realData?.shareRate || 90,
minWithdrawAmount: realData?.minWithdrawAmount || 10,
minWithdrawAmount: minWithdrawAmount,
// 统计
referralCount: realData?.referralCount || realData?.stats?.totalBindings || activeBindings.length + convertedBindings.length,
@@ -206,6 +224,15 @@ Page({
console.log('[Referral] - 即将过期:', this.data.expiringCount)
console.log('[Referral] - 收益:', this.data.earnings)
console.log('=== [Referral] 按钮状态验证 ===')
console.log('累计佣金 (totalCommission):', this.data.totalCommission)
console.log('待审核金额 (pendingWithdrawAmount):', this.data.pendingWithdrawAmount)
console.log('可提现金额 (availableEarnings 显示):', this.data.availableEarnings)
console.log('可提现金额 (availableEarningsNum 判断):', this.data.availableEarningsNum, typeof this.data.availableEarningsNum)
console.log('最低提现金额 (minWithdrawAmount):', this.data.minWithdrawAmount, typeof this.data.minWithdrawAmount)
console.log('按钮启用条件:', this.data.availableEarningsNum, '>=', this.data.minWithdrawAmount, '=', this.data.availableEarningsNum >= this.data.minWithdrawAmount)
console.log('✅ 最终结果: 按钮应该', this.data.availableEarningsNum >= this.data.minWithdrawAmount ? '🟢 启用' : '⚫ 禁用')
// 隐藏加载提示
wx.hideLoading()
} else {
@@ -583,9 +610,16 @@ Page({
// 提现 - 直接到微信零钱
async handleWithdraw() {
const availableEarnings = parseFloat(this.data.availableEarnings) || 0
// 使用数字版本直接进行判断,避免重复转换
const availableEarnings = this.data.availableEarningsNum || 0
const minWithdrawAmount = this.data.minWithdrawAmount || 10
console.log('[Withdraw] 提现检查:', {
availableEarnings,
minWithdrawAmount,
shouldEnable: availableEarnings >= minWithdrawAmount
})
if (availableEarnings <= 0) {
wx.showToast({ title: '暂无可提现收益', icon: 'none' })
return
@@ -634,13 +668,13 @@ Page({
if (res.success) {
wx.showModal({
title: '提现成功 🎉',
content: `¥${amount.toFixed(2)}到账您的微信零钱`,
title: '提现申请已提交 ✅',
content: res.message || '正在审核中,通过后会自动到账您的微信零钱',
showCancel: false,
confirmText: '好的'
confirmText: '知道了'
})
// 刷新数据
// 刷新数据(此时待审核金额会增加,可提现金额会减少)
this.initData()
} else {
if (res.needBind) {
@@ -655,7 +689,7 @@ Page({
}
})
} else {
wx.showToast({ title: res.error || '提现失败', icon: 'none' })
wx.showToast({ title: res.message || res.error || '提现失败', icon: 'none', duration: 3000 })
}
}
} catch (e) {
@@ -728,7 +762,7 @@ Page({
})
// 如果开启,检查当前金额是否达到阈值
if (enabled && parseFloat(this.data.availableEarnings) >= threshold) {
if (enabled && this.data.availableEarningsNum >= threshold) {
wx.showModal({
title: '提示',
content: `当前可提现金额¥${this.data.availableEarnings}已达到阈值¥${threshold},是否立即提现?`,

View File

@@ -40,17 +40,17 @@
<image class="icon-wallet" src="/assets/icons/wallet.svg" mode="aspectFit"></image>
</view>
<view class="earnings-info">
<text class="earnings-label">累计佣金</text>
<text class="earnings-label">可提现金额</text>
<text class="commission-rate">{{shareRate}}% 返利</text>
</view>
</view>
<view class="earnings-right">
<text class="earnings-value">¥{{totalCommission}}</text>
<text class="pending-text">待审核: ¥{{pendingWithdrawAmount}}</text>
<text class="earnings-value">¥{{availableEarnings}}</text>
<text class="pending-text">累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}}</text>
</view>
</view>
<view class="withdraw-btn {{availableEarnings < minWithdrawAmount ? 'btn-disabled' : ''}}" bindtap="handleWithdraw">
{{availableEarnings < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}}
<view class="withdraw-btn {{availableEarningsNum < minWithdrawAmount ? 'btn-disabled' : ''}}" bindtap="handleWithdraw">
{{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}}
</view>
</view>
</view>

View File

@@ -0,0 +1,359 @@
# 可提现金额计算修复
## 问题总结
### 发现的问题
**当前逻辑(错误)**
```javascript
可提现金额 = 累计佣金 - 待审核金额
```
**问题场景**
1. 用户累计佣金 ¥100申请提现 ¥50
2. 此时:可提现 = 100 - 50 = ¥50 ✅ 正确
3. 审核通过后,提现记录状态改为 completed
4. 此时:可提现 = 100 - 0 = ¥100 ❌ 错误!
**根本原因**:审核通过后,`pendingWithdrawAmount` 归零,但没有减去"已提现金额",导致可提现金额"回血"。
## 正确方案
### 计算公式
```javascript
可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
```
### 字段说明
| 字段 | 说明 | 来源 | 变化规律 |
|------|------|------|----------|
| `totalCommission` | 累计佣金总额 | `SUM(orders.amount) × distributorShare` | 有新订单就增加,只增不减 |
| `withdrawnEarnings` | 已提现金额 | `users.withdrawn_earnings` | 审核通过时累加 |
| `pendingWithdrawAmount` | 待审核金额 | `SUM(withdrawals.amount WHERE status='pending')` | 申请时增加,审核后归零 |
| `availableEarnings` | 可提现金额 | 前端计算 | 动态变化 |
## 修复内容
### 1. 前端计算逻辑修正
**文件**`miniprogram/pages/referral/referral.js`
**修改前**
```javascript
// ❌ 错误:只减去待审核,没减去已提现
const totalCommissionNum = realData?.totalCommission || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = totalCommissionNum - pendingWithdrawNum
```
**修改后**
```javascript
// ✅ 正确:三元素完整计算
const totalCommissionNum = realData?.totalCommission || 0
const withdrawnNum = realData?.withdrawnEarnings || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = totalCommissionNum - withdrawnNum - pendingWithdrawNum
```
### 2. 详细日志输出
```javascript
console.log('=== [Referral] 收益计算(完整版)===')
console.log('累计佣金 (totalCommission):', totalCommissionNum)
console.log('已提现金额 (withdrawnEarnings):', withdrawnNum)
console.log('待审核金额 (pendingWithdrawAmount):', pendingWithdrawNum)
console.log('可提现金额 = 累计 - 已提现 - 待审核 =', totalCommissionNum, '-', withdrawnNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
console.log('最低提现金额 (minWithdrawAmount):', minWithdrawAmount)
console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
```
## 验证流程
### 完整场景测试
#### 场景1初始状态
```
数据:
- 累计佣金: ¥100
- 已提现: ¥0
- 待审核: ¥0
计算:
availableEarnings = 100 - 0 - 0 = ¥100
验证:✅ 可提现 ¥100
```
#### 场景2申请提现
```
操作:申请提现 ¥50
数据:
- 累计佣金: ¥100 (不变)
- 已提现: ¥0 (不变)
- 待审核: ¥50 (新增)
计算:
availableEarnings = 100 - 0 - 50 = ¥50
验证:✅ 可提现 ¥50
```
#### 场景3审核通过关键
```
操作:管理员审核通过
数据:
- 累计佣金: ¥100 (不变)
- 已提现: ¥50 (users.withdrawn_earnings += 50)
- 待审核: ¥0 (状态改为 completed)
计算:
availableEarnings = 100 - 50 - 0 = ¥50
验证:✅ 可提现 ¥50不会回血
```
#### 场景4新订单产生
```
操作:用户购买新商品,产生佣金 ¥20
数据:
- 累计佣金: ¥120 (100 + 20)
- 已提现: ¥50 (不变)
- 待审核: ¥0 (不变)
计算:
availableEarnings = 120 - 50 - 0 = ¥70
验证:✅ 可提现 ¥70
```
#### 场景5二次提现
```
操作:申请提现 ¥30
数据:
- 累计佣金: ¥120 (不变)
- 已提现: ¥50 (不变)
- 待审核: ¥30 (新增)
计算:
availableEarnings = 120 - 50 - 30 = ¥40
验证:✅ 可提现 ¥40
```
### 数据库验证
#### 1. 检查 users 表
```sql
SELECT
id,
nickname,
withdrawn_earnings
FROM users
WHERE id = 'YOUR_USER_ID';
```
#### 2. 检查 withdrawals 表
```sql
SELECT
id,
amount,
status,
created_at
FROM withdrawals
WHERE user_id = 'YOUR_USER_ID'
ORDER BY created_at DESC;
```
#### 3. 检查 orders 表
```sql
SELECT
SUM(amount) as total_amount
FROM orders
WHERE referrer_id = 'YOUR_USER_ID'
AND status = 'paid';
```
#### 4. 手动验证计算
```sql
-- 1. 累计佣金
SET @total_orders = (SELECT SUM(amount) FROM orders WHERE referrer_id = 'YOUR_USER_ID' AND status = 'paid');
SET @distributor_share = 0.9;
SET @total_commission = @total_orders * @distributor_share;
-- 2. 已提现
SET @withdrawn = (SELECT withdrawn_earnings FROM users WHERE id = 'YOUR_USER_ID');
-- 3. 待审核
SET @pending = (SELECT SUM(amount) FROM withdrawals WHERE user_id = 'YOUR_USER_ID' AND status = 'pending');
-- 4. 可提现
SET @available = @total_commission - @withdrawn - IFNULL(@pending, 0);
-- 显示结果
SELECT
@total_commission as '累计佣金',
@withdrawn as '已提现',
@pending as '待审核',
@available as '可提现';
```
## 测试步骤
### 1. 清除缓存重新编译
微信开发者工具:
```
工具 → 清除缓存 → 清除全部缓存数据
点击 编译 按钮
```
### 2. 查看控制台日志
进入分销中心页面,查看日志输出:
```
=== [Referral] 收益计算(完整版)===
累计佣金 (totalCommission): 100
已提现金额 (withdrawnEarnings): 0
待审核金额 (pendingWithdrawAmount): 0
可提现金额 = 累计 - 已提现 - 待审核 = 100 - 0 - 0 = 100
最低提现金额 (minWithdrawAmount): 5
按钮判断: 100 >= 5 = true
✅ 按钮应该: 🟢 启用(绿色)
```
### 3. 测试提现流程
1. **申请提现**:点击提现按钮,申请提现
2. **查看变化**
- 累计佣金不变
- 待审核金额增加
- 可提现金额减少
3. **审核通过**:在管理后台审核通过
4. **再次查看**
- 累计佣金不变
- 已提现金额增加
- 待审核金额归零
- **可提现金额不应该回血**
### 4. 验证按钮状态
不同情况下按钮的状态:
| 可提现金额 | 最低提现 | 按钮状态 | 按钮文本 |
|-----------|---------|---------|---------|
| ¥10 | ¥5 | 启用 | 申请提现 ¥10 |
| ¥3 | ¥5 | 禁用 | 满5元可提现 |
| ¥0 | ¥5 | 禁用 | 满5元可提现 |
## 常见问题
### Q1: 为什么要分"累计佣金"和"可提现金额"
**A**:
- **累计佣金**:用户的成就感,"我总共赚了多少"
- **可提现金额**:用户的可操作余额,"我现在能提多少"
### Q2: 已提现的钱还算在累计佣金里吗?
**A**: 是的。累计佣金是历史总和,只增不减。已提现只是资金流向,不影响累计数字。
示例:
- 累计赚了 ¥100成就
- 已提现 ¥50已到手
- 还能提现 ¥50余额
### Q3: 如果审核拒绝会怎样?
**A**:
1. 待审核金额归零(提现申请被取消)
2. 已提现金额不变
3. 可提现金额恢复(用户可以重新申请)
### Q4: users.withdrawn_earnings 会自动更新吗?
**A**: 是的,在两个地方会更新:
- `app/api/withdraw/route.ts` - 自动审核通过时
- `app/api/admin/withdrawals/route.ts` - 管理员审核通过时
### Q5: 为什么不直接从数据库读取可提现金额?
**A**:
- 可提现金额是**动态计算值**,不是固定字段
- 实时计算确保数据准确
- 便于调试和验证逻辑
## 相关文件
- `miniprogram/pages/referral/referral.js` - 前端计算(已修改)✅
- `app/api/referral/data/route.ts` - 数据查询(无需修改)
- `app/api/withdraw/route.ts` - 提现接口(无需修改)
- `app/api/admin/withdrawals/route.ts` - 审核接口(无需修改)
## 数据流图
```
┌────────────────────────────────────────────┐
│ 订单产生佣金 │
│ ↓ │
│ 累计佣金 (totalCommission) │
│ 持续累加,只增不减 │
│ ↓ │
│ ┌─────────────┴─────────────┐ │
│ ↓ ↓ │
│ 申请提现 继续积累 │
│ ↓ │
│ 待审核 (pendingWithdrawAmount) │
│ 暂时冻结 │
│ ↓ │
│ 审核通过 │
│ ↓ │
│ 已提现 (withdrawnEarnings) │
│ 累加记录 │
│ │
│ ┌─────────────────────────┐ │
│ │ 可提现金额(实时计算) │ │
│ │ = 累计佣金 │ │
│ │ - 已提现金额 │ │
│ │ - 待审核金额 │ │
│ └─────────────────────────┘ │
└────────────────────────────────────────────┘
```
## 总结
### 修改前的问题
```javascript
可提现 = 累计佣金 - 待审核金额
问题审核通过后待审核归零可提现"回血"
```
### 修改后的正确逻辑
```javascript
可提现 = 累计佣金 - 已提现金额 - 待审核金额
优点
1. 审核通过后可提现金额不会增加
2. 只有新订单产生佣金时可提现金额才会增加
3. 逻辑清晰符合资金流向
```
### 业务含义
- **累计佣金**:成就感 - "我赚了多少"
- **已提现**:安全感 - "我拿到了多少"
- **待审核**:期待感 - "我正在提多少"
- **可提现**:行动力 - "我能提多少"
这样的设计既满足了用户查看历史成就的需求,又确保了资金安全和准确性。

View File

@@ -0,0 +1,400 @@
# 可提现金额计算逻辑分析
## 问题描述
用户提问:提现后,可提现金额应该如何计算?
## 当前逻辑分析
### 数据库字段
**users 表**
- `earnings` - 已结算收益(保留兼容)
- `pending_earnings` - 待结算收益(保留兼容)
- `withdrawn_earnings` - 已提现金额(累加)
**withdrawals 表**
- `status = 'pending'` - 待审核
- `status = 'completed'` - 已完成
- `status = 'failed'` - 已拒绝
### 当前计算方式
**后端 API (`/api/referral/data`)**
```typescript
// 累计佣金(从 orders 表实时计算)
totalCommission = SUM(orders.amount WHERE status = 'paid') × distributorShare
// 待审核金额(从 withdrawals 表查询)
pendingWithdrawAmount = SUM(withdrawals.amount WHERE status = 'pending')
// 已提现金额(从 users 表读取)
withdrawnEarnings = users.withdrawn_earnings
```
**前端计算**
```javascript
// 可提现金额 = 累计佣金 - 待审核金额
availableEarnings = totalCommission - pendingWithdrawAmount
```
## ⚠️ 发现的问题
### 场景演示
**初始状态**
```
累计佣金: ¥100
已提现: ¥0
待审核: ¥0
可提现 = 100 - 0 = ¥100 ✅
```
**申请提现 ¥50 后**
```
累计佣金: ¥100 (不变)
已提现: ¥0 (不变)
待审核: ¥50 (新增)
可提现 = 100 - 50 = ¥50 ✅ 正确
```
**审核通过后(当前逻辑的问题)**
```
累计佣金: ¥100 (不变)
已提现: ¥50 (users.withdrawn_earnings += 50)
待审核: ¥0 (提现记录 status 变为 completed)
可提现 = 100 - 0 = ¥100 ❌ 错误!应该是 ¥50
```
### 问题根源
**当前公式**
```javascript
可提现 = 累计佣金 - 待审核金额
```
**问题**:没有减去"已提现金额",导致审核通过后,可提现金额"回血"了。
## ✅ 正确的计算方案
### 推荐方案:三元素计算
```javascript
可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
```
### 完整演示
**初始状态**
```
累计佣金: ¥100
已提现: ¥0
待审核: ¥0
可提现 = 100 - 0 - 0 = ¥100 ✅
```
**申请提现 ¥50 后**
```
累计佣金: ¥100 (不变)
已提现: ¥0 (不变)
待审核: ¥50 (新增提现申请)
可提现 = 100 - 0 - 50 = ¥50 ✅ 正确
```
**审核通过后**
```
累计佣金: ¥100 (不变)
已提现: ¥50 (users.withdrawn_earnings += 50)
待审核: ¥0 (状态改为 completed)
可提现 = 100 - 50 - 0 = ¥50 ✅ 正确!
```
**有新订单后**
```
累计佣金: ¥120 (新订单 ¥20)
已提现: ¥50 (不变)
待审核: ¥0 (不变)
可提现 = 120 - 50 - 0 = ¥70 ✅ 正确!
```
## 修复方案
### 方案A后端返回 withdrawnEarnings前端计算推荐
**优点**
- 前端灵活控制
- 逻辑清晰透明
- 便于调试
**实现**
#### 1. 后端确保返回正确数据
`app/api/referral/data/route.ts` 已经返回了:
```typescript
{
totalCommission: 计算值, // ✅ 已有
pendingWithdrawAmount: 查询值, // ✅ 已有
withdrawnEarnings: users字段 // ✅ 已有
}
```
#### 2. 前端修改计算逻辑
`miniprogram/pages/referral/referral.js`
```javascript
// ❌ 当前(错误)
const availableEarningsNum = totalCommissionNum - pendingWithdrawNum
// ✅ 修正(正确)
const totalCommissionNum = realData?.totalCommission || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const withdrawnNum = realData?.withdrawnEarnings || 0
const availableEarningsNum = totalCommissionNum - withdrawnNum - pendingWithdrawNum
```
### 方案B后端直接计算返回
**优点**
- 前端无需计算
- 后端统一控制逻辑
**缺点**
- 前端不知道计算细节
- 调试困难
**实现**
```typescript
// app/api/referral/data/route.ts
const totalCommission = paymentStats.totalAmount * distributorShare
const withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
const pendingWithdrawAmount = ...查询值...
const availableEarnings = totalCommission - withdrawnEarnings - pendingWithdrawAmount
return {
totalCommission,
withdrawnEarnings,
pendingWithdrawAmount,
availableEarnings // 后端计算好
}
```
## 推荐实现方案A
### 修改文件
#### 1. `miniprogram/pages/referral/referral.js`
```javascript
// 在 initData() 方法中修改
// ✅ 修正:可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
const totalCommissionNum = realData?.totalCommission || 0
const withdrawnNum = realData?.withdrawnEarnings || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = totalCommissionNum - withdrawnNum - pendingWithdrawNum
console.log('=== [Referral] 收益计算(完整版)===')
console.log('累计佣金 (totalCommission):', totalCommissionNum)
console.log('已提现金额 (withdrawnEarnings):', withdrawnNum)
console.log('待审核金额 (pendingWithdrawAmount):', pendingWithdrawNum)
console.log('可提现金额 = 累计 - 已提现 - 待审核 =', totalCommissionNum, '-', withdrawnNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用' : '⚫ 禁用')
```
#### 2. `miniprogram/pages/referral/referral.wxml`
显示部分需要更新(如果要显示已提现):
```xml
<view class="earnings-right">
<text class="earnings-value">¥{{availableEarnings}}</text>
<text class="pending-text">
累计: ¥{{totalCommission}} | 已提现: ¥{{withdrawnEarnings}} | 待审核: ¥{{pendingWithdrawAmount}}
</text>
</view>
```
或者简化显示:
```xml
<view class="earnings-right">
<text class="earnings-value">¥{{availableEarnings}}</text>
<text class="pending-text">
累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}}
</text>
</view>
```
## 数据流向图
```
┌─────────────────────────────────────────────────┐
│ 订单产生佣金 │
│ ↓ │
│ 累计佣金: ¥100 (持续累加) │
│ ↓ │
│ ┌────────────┴────────────┐ │
│ ↓ ↓ │
│ 申请提现 ¥50 继续累积佣金 │
│ ↓ ↓ │
│ 待审核: ¥50 累计: ¥120 │
│ 可提现: ¥50 │
│ ↓ │
│ 审核通过 │
│ ↓ │
│ 已提现: ¥50 │
│ 待审核: ¥0 │
│ 可提现: ¥70 (120-50-0) │
└─────────────────────────────────────────────────┘
```
## 测试用例
### 用例1无提现记录
```
输入:
- totalCommission = 100
- withdrawnEarnings = 0
- pendingWithdrawAmount = 0
计算:
availableEarnings = 100 - 0 - 0 = 100
预期:✅ 可提现 ¥100
```
### 用例2有待审核提现
```
输入:
- totalCommission = 100
- withdrawnEarnings = 0
- pendingWithdrawAmount = 30
计算:
availableEarnings = 100 - 0 - 30 = 70
预期:✅ 可提现 ¥70
```
### 用例3有已提现记录
```
输入:
- totalCommission = 100
- withdrawnEarnings = 50
- pendingWithdrawAmount = 0
计算:
availableEarnings = 100 - 50 - 0 = 50
预期:✅ 可提现 ¥50
```
### 用例4复合情况
```
输入:
- totalCommission = 200
- withdrawnEarnings = 80
- pendingWithdrawAmount = 40
计算:
availableEarnings = 200 - 80 - 40 = 80
预期:✅ 可提现 ¥80
```
### 用例5边界情况全部提完
```
输入:
- totalCommission = 100
- withdrawnEarnings = 70
- pendingWithdrawAmount = 30
计算:
availableEarnings = 100 - 70 - 30 = 0
预期:✅ 可提现 ¥0按钮禁用
```
## 验证 users.withdrawn_earnings 是否正确维护
### 提现接口确认
查看 `app/api/withdraw/route.ts`
```typescript
// 第165行自动审核情况下
UPDATE users
SET withdrawn_earnings = withdrawn_earnings + ?,
pending_earnings = GREATEST(0, pending_earnings - ?)
WHERE id = ?
```
查看 `app/api/admin/withdrawals/route.ts`
```typescript
// 第156行管理员审核通过
UPDATE users
SET withdrawn_earnings = withdrawn_earnings + ?,
pending_earnings = pending_earnings - ?
WHERE id = ?
```
**确认**`withdrawn_earnings` 字段在提现审核通过后会正确累加。
## 总结
### 当前问题
```javascript
错误可提现 = 累计佣金 - 待审核金额
```
- 审核通过后,待审核归零,可提现"回血"
- 导致用户可以重复提现同一笔钱
### 正确方案
```javascript
正确可提现 = 累计佣金 - 已提现金额 - 待审核金额
```
- 累计佣金:历史总和,只增不减
- 已提现金额:累加的已到账金额
- 待审核金额:申请中但未到账的金额
- 可提现金额:真正可以申请提现的余额
### 字段含义
| 字段 | 含义 | 变化规律 |
|------|------|----------|
| `totalCommission` | 累计佣金总额 | 有新订单就增加 |
| `withdrawnEarnings` | 已提现金额 | 审核通过时增加 |
| `pendingWithdrawAmount` | 待审核金额 | 申请提现时增加,审核后清零 |
| `availableEarnings` | 可提现金额 | 动态计算,三者综合结果 |
### 业务含义
- **累计佣金**:成就感,"我总共赚了多少"
- **已提现**:安全感,"我已经拿到手多少"
- **待审核**:期待感,"我正在提现多少"
- **可提现**:行动力,"我现在能提多少"
## 下一步行动
1. 修改前端计算逻辑(添加 `withdrawnEarnings`
2. 添加详细调试日志
3. 测试各种场景
4. 验证数据库 `withdrawn_earnings` 是否正确
5. 文档记录新逻辑
## 相关文件
- `miniprogram/pages/referral/referral.js` - 前端计算(需修改)
- `app/api/referral/data/route.ts` - 数据查询(已正确)
- `app/api/withdraw/route.ts` - 提现接口(已正确)
- `app/api/admin/withdrawals/route.ts` - 审核接口(已正确)

View File

@@ -0,0 +1,279 @@
# 提现卡片显示优化
## 需求
用户希望提现卡片更直观地显示**可提现金额**,而不是累计佣金。
## 修改前后对比
### 修改前
```
┌─────────────────────────────────┐
│ 💰 累计佣金 ¥8.10 │
│ 90% 返利 待审核: ¥0.00 │
├─────────────────────────────────┤
│ 申请提现 ¥8.10 │
└─────────────────────────────────┘
```
**问题**
- 用户看到"累计佣金 ¥8.10",但不清楚实际能提多少
- 需要心算8.10 - 0.00 = 8.10(可提现)
### 修改后
```
┌─────────────────────────────────────────┐
│ 💰 可提现金额 ¥8.10 │
│ 90% 返利 累计: ¥8.10 | 待审核: ¥0.00 │
├─────────────────────────────────────────┤
│ 申请提现 ¥8.10 │
└─────────────────────────────────────────┘
```
**优点**
- ✅ 直接显示可提现金额 ¥8.10(最关心的数据)
- ✅ 底部小字显示明细:累计佣金 + 待审核金额
- ✅ 一目了然,无需计算
## 具体修改
### 文件:`miniprogram/pages/referral/referral.wxml`
```xml
<!-- 修改前 -->
<view class="earnings-left">
<view class="wallet-icon">
<image class="icon-wallet" src="/assets/icons/wallet.svg" mode="aspectFit"></image>
</view>
<view class="earnings-info">
<text class="earnings-label">累计佣金</text>
<text class="commission-rate">{{shareRate}}% 返利</text>
</view>
</view>
<view class="earnings-right">
<text class="earnings-value">¥{{totalCommission}}</text>
<text class="pending-text">待审核: ¥{{pendingWithdrawAmount}}</text>
</view>
<!-- 修改后 -->
<view class="earnings-left">
<view class="wallet-icon">
<image class="icon-wallet" src="/assets/icons/wallet.svg" mode="aspectFit"></image>
</view>
<view class="earnings-info">
<text class="earnings-label">可提现金额</text> <!-- ✅ 改为"可提现金额" -->
<text class="commission-rate">{{shareRate}}% 返利</text>
</view>
</view>
<view class="earnings-right">
<text class="earnings-value">¥{{availableEarnings}}</text> <!-- ✅ 显示可提现金额 -->
<text class="pending-text">累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}}</text> <!-- ✅ 显示明细 -->
</view>
```
## 数据说明
### 字段含义
| 字段 | 说明 | 示例 |
|------|------|------|
| `totalCommission` | 累计佣金总额(所有订单佣金之和) | ¥8.10 |
| `pendingWithdrawAmount` | 待审核的提现金额(已申请但未到账) | ¥0.00 |
| `availableEarnings` | **可提现金额** = 累计佣金 - 待审核金额 | ¥8.10 |
### 计算公式
```javascript
availableEarnings = totalCommission - pendingWithdrawAmount
= 8.10 - 0.00
= 8.10
```
## 场景示例
### 场景1无待审核提现
```
累计佣金: ¥100.00
待审核: ¥0.00
可提现: ¥100.00
```
显示:
```
可提现金额 ¥100.00
累计: ¥100.00 | 待审核: ¥0.00
```
### 场景2有待审核提现
```
累计佣金: ¥100.00
待审核: ¥30.00
可提现: ¥70.00
```
显示:
```
可提现金额 ¥70.00
累计: ¥100.00 | 待审核: ¥30.00
```
用户一眼就能看到:
- 可以提现 70 元
- 有 30 元在审核中
- 总共赚了 100 元
### 场景3全部待审核
```
累计佣金: ¥100.00
待审核: ¥100.00
可提现: ¥0.00
```
显示:
```
可提现金额 ¥0.00
累计: ¥100.00 | 待审核: ¥100.00
```
按钮变灰:`满5元可提现`
## 用户体验优化
### 信息层级
**主信息(大号字体)**
- 可提现金额 ¥8.10
- 用户最关心的数据,放在最显眼位置
**次要信息(小号字体)**
- 累计: ¥8.10 | 待审核: ¥0.00
- 提供明细,让用户了解全貌
### 视觉引导
```
┌─────────────────────────────────────────┐
│ 💰 可提现金额 ← 标签 ← 主信息
│ ¥8.10 ← 大号
│ 90% 返利 ← 提示 │
│ 累计: ¥8.10 | 待审核: ¥0.00 ← 明细(小号)
├─────────────────────────────────────────┤
│ 申请提现 ¥8.10 ← 行动按钮 │
└─────────────────────────────────────────┘
```
## 对比其他方案
### 方案A显示累计佣金原方案
```
累计佣金: ¥8.10
待审核: ¥0.00
```
- ❌ 需要用户自己计算可提现金额
- ❌ 不够直观
### 方案B只显示可提现金额
```
可提现金额: ¥8.10
```
- ✅ 直观
- ❌ 缺少明细,用户不知道累计赚了多少
### 方案C显示可提现 + 明细(当前方案)✅
```
可提现金额: ¥8.10
累计: ¥8.10 | 待审核: ¥0.00
```
- ✅ 直观:一眼看到可提现金额
- ✅ 完整:提供累计和待审核的明细
- ✅ 平衡:主次分明
## 样式建议(可选)
如果需要进一步优化样式,可以考虑:
### 1. 调整明细字号
```css
.pending-text {
font-size: 22rpx; /* 从 24rpx 减小到 22rpx */
color: rgba(255,255,255,0.5);
margin-top: 8rpx;
display: block;
}
```
### 2. 使用图标分隔
```xml
<text class="pending-text">
累计: ¥{{totalCommission}} · 待审核: ¥{{pendingWithdrawAmount}}
</text>
```
`·` (中点) 代替 `|` (竖线)
### 3. 增加图标说明
```xml
<text class="pending-text">
💰 累计: ¥{{totalCommission}} ⏳ 待审核: ¥{{pendingWithdrawAmount}}
</text>
```
## 测试要点
### 1. 显示验证
- [ ] 标签显示为"可提现金额"
- [ ] 大号数字显示正确的可提现金额
- [ ] 小号文字显示累计和待审核金额
- [ ] 格式正确:`累计: ¥X.XX | 待审核: ¥Y.YY`
### 2. 计算验证
```
已知:
- 累计佣金: ¥8.10
- 待审核: ¥0.00
验证:
- 可提现 = 8.10 - 0.00 = ¥8.10 ✅
```
### 3. 边界情况
- [ ] 可提现为 0 时的显示
- [ ] 待审核大于累计时的显示(理论上不会出现)
- [ ] 多笔待审核的累加
## 相关文件
- `miniprogram/pages/referral/referral.wxml` - 界面结构(本次修改)
- `miniprogram/pages/referral/referral.wxss` - 样式
- `miniprogram/pages/referral/referral.js` - 逻辑(计算 availableEarnings
## 总结
这次优化让提现卡片更加**用户友好**
**修改前**
- 显示"累计佣金",用户需要自己计算
**修改后**
- 直接显示"可提现金额",一目了然
- 保留明细信息(累计、待审核),信息完整
- 主次分明,视觉层级清晰
核心理念:**把用户最关心的数据,放在最显眼的位置**。

View File

@@ -0,0 +1,423 @@
# 提现双向校验实现
## 需求
前端和后端都必须使用**相同的逻辑**校验可提现金额,确保安全性和一致性。
## 校验逻辑(统一)
```javascript
可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
```
## 前后端对比
### 前端校验miniprogram
**文件**`miniprogram/pages/referral/referral.js`
**作用**:按钮启用/禁用判断
```javascript
// 计算可提现金额
const totalCommissionNum = realData?.totalCommission || 0
const withdrawnNum = realData?.withdrawnEarnings || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = totalCommissionNum - withdrawnNum - pendingWithdrawNum
// 判断按钮状态
if (availableEarningsNum >= minWithdrawAmount) {
// 启用按钮
} else {
// 禁用按钮
}
```
### 后端校验API
**文件**`app/api/withdraw/route.ts`
**作用**:提现申请最终验证
```typescript
// 1. 查询累计佣金
const ordersResult = await query(`
SELECT COALESCE(SUM(amount), 0) as total_amount
FROM orders
WHERE referrer_id = ? AND status = 'paid'
`, [userId])
const totalAmount = parseFloat(ordersResult[0]?.total_amount || 0)
const totalCommission = totalAmount * distributorShare
// 2. 读取已提现金额
const withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
// 3. 查询待审核金额
const pendingResult = await query(`
SELECT COALESCE(SUM(amount), 0) as pending_amount
FROM withdrawals
WHERE user_id = ? AND status = 'pending'
`, [userId])
const pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
// 4. 计算可提现金额
const availableAmount = totalCommission - withdrawnEarnings - pendingWithdrawAmount
// 5. 验证
if (amount > availableAmount) {
return NextResponse.json({
success: false,
message: `可提现金额不足。当前可提现 ¥${availableAmount.toFixed(2)}(累计 ¥${totalCommission.toFixed(2)} - 已提现 ¥${withdrawnEarnings.toFixed(2)} - 待审核 ¥${pendingWithdrawAmount.toFixed(2)}`
})
}
```
## 修改内容
### 1. 后端添加已提现金额
**修改前(错误)**
```typescript
// ❌ 只减去待审核,没减去已提现
const availableAmount = totalCommission - pendingWithdrawAmount
```
**修改后(正确)**
```typescript
// ✅ 三元素完整校验
const withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
const availableAmount = totalCommission - withdrawnEarnings - pendingWithdrawAmount
```
### 2. 详细日志输出
```typescript
console.log('[Withdraw] 提现验证(完整版):')
console.log('- 累计佣金 (totalCommission):', totalCommission)
console.log('- 已提现金额 (withdrawnEarnings):', withdrawnEarnings)
console.log('- 待审核金额 (pendingWithdrawAmount):', pendingWithdrawAmount)
console.log('- 可提现金额 = 累计 - 已提现 - 待审核 =', totalCommission, '-', withdrawnEarnings, '-', pendingWithdrawAmount, '=', availableAmount)
console.log('- 申请提现金额 (amount):', amount)
console.log('- 判断:', amount, '>', availableAmount, '=', amount > availableAmount)
```
### 3. 优化错误提示
**修改前**
```typescript
message: `可提现金额不足,当前可提现 ¥${availableAmount.toFixed(2)},待审核 ¥${pendingWithdrawAmount.toFixed(2)}`
```
**修改后**
```typescript
message: `可提现金额不足。当前可提现 ¥${availableAmount.toFixed(2)}(累计 ¥${totalCommission.toFixed(2)} - 已提现 ¥${withdrawnEarnings.toFixed(2)} - 待审核 ¥${pendingWithdrawAmount.toFixed(2)}`
```
## 双向校验流程
```
用户点击提现按钮
┌──────────────────────────┐
│ 前端校验(第一层) │
│ 按钮启用/禁用判断 │
├──────────────────────────┤
│ 可提现 >= 最低金额? │
│ YES → 允许点击 │
│ NO → 按钮禁用 │
└──────────────────────────┘
用户确认提现
┌──────────────────────────┐
│ 后端校验(第二层) │
│ API 最终验证 │
├──────────────────────────┤
│ 1. 查询累计佣金 │
│ 2. 读取已提现金额 │
│ 3. 查询待审核金额 │
│ 4. 计算可提现金额 │
│ 5. amount > available? │
│ YES → 拒绝提现 │
│ NO → 允许提现 │
└──────────────────────────┘
创建提现记录
```
## 为什么需要双向校验?
### 前端校验的作用
**优点**
- ✅ 快速响应,提升用户体验
- ✅ 减少无效请求,降低服务器压力
- ✅ 按钮禁用,防止误操作
**局限**
- ❌ 数据可能过期API 数据缓存)
- ❌ 可被绕过(客户端不可信)
- ❌ 无法阻止恶意请求
### 后端校验的作用
**优点**
- ✅ 最终防线,确保资金安全
- ✅ 实时数据,准确无误
- ✅ 不可绕过,强制验证
**必要性**
- ✅ 防止前端被篡改
- ✅ 防止并发提现
- ✅ 防止逻辑漏洞
## 攻击场景防御
### 场景1前端被篡改
**攻击**
- 黑客修改前端代码,移除按钮禁用逻辑
- 强制发送提现请求
**防御**
- ✅ 后端独立校验,拒绝超额提现
### 场景2并发提现
**攻击**
- 用户快速点击两次提现按钮
- 或同时在多个设备上提现
**防御**
- ✅ 后端查询最新的 `pendingWithdrawAmount`
- ✅ 数据库事务保证原子性
### 场景3API 重放攻击
**攻击**
- 捕获提现请求,重复发送
**防御**
- ✅ 后端实时校验可提现金额
- ✅ 创建提现记录后,`pendingWithdrawAmount` 增加
- ✅ 第二次请求时,可提现金额不足,拒绝
## 测试用例
### 测试1正常提现
**数据**
- 累计佣金: ¥100
- 已提现: ¥0
- 待审核: ¥0
- 申请提现: ¥50
**前端**
```
可提现 = 100 - 0 - 0 = 100
50 < 100 → 按钮启用 ✅
```
**后端**
```
可提现 = 100 - 0 - 0 = 100
50 ≤ 100 → 允许提现 ✅
```
**结果**:✅ 提现成功
### 测试2超额提现
**数据**
- 累计佣金: ¥100
- 已提现: ¥0
- 待审核: ¥0
- 申请提现: ¥150
**前端**
```
可提现 = 100 - 0 - 0 = 100
150 > 100 → 按钮禁用 ✅
(正常情况下用户无法点击)
```
**后端**(假设黑客绕过前端):
```
可提现 = 100 - 0 - 0 = 100
150 > 100 → 拒绝提现 ✅
返回错误:可提现金额不足
```
**结果**:✅ 后端拦截成功
### 测试3重复提现
**数据**
- 累计佣金: ¥100
- 已提现: ¥0
- 待审核: ¥0
**第一次提现 ¥50**
```
前端100 - 0 - 0 = 100允许 ✅
后端100 - 0 - 0 = 100允许 ✅
创建提现记录pending += 50
```
**第二次提现 ¥60**(并发或快速点击):
```
前端:可能还是显示 100数据未刷新
后端100 - 0 - 50 = 50
60 > 50 → 拒绝提现 ✅
```
**结果**:✅ 后端防御成功
### 测试4审核通过后再次提现
**初始**
- 累计佣金: ¥100
- 已提现: ¥50之前提现已到账
- 待审核: ¥0
**申请提现 ¥60**
```
前端100 - 50 - 0 = 50
60 > 50 → 按钮禁用 ✅
```
**后端**(假设绕过前端):
```
100 - 50 - 0 = 50
60 > 50 → 拒绝提现 ✅
```
**结果**:✅ 双重防护
## 日志示例
### 前端日志
```
=== [Referral] 收益计算(完整版)===
累计佣金 (totalCommission): 100
已提现金额 (withdrawnEarnings): 50
待审核金额 (pendingWithdrawAmount): 0
可提现金额 = 累计 - 已提现 - 待审核 = 100 - 50 - 0 = 50
最低提现金额 (minWithdrawAmount): 5
按钮判断: 50 >= 5 = true
✅ 按钮应该: 🟢 启用(绿色)
```
### 后端日志
```
[Withdraw] 佣金计算:
- 订单总金额: 111.11
- 分成比例: 90%
- 累计佣金: 100
[Withdraw] 提现验证(完整版):
- 累计佣金 (totalCommission): 100
- 已提现金额 (withdrawnEarnings): 50
- 待审核金额 (pendingWithdrawAmount): 0
- 可提现金额 = 累计 - 已提现 - 待审核 = 100 - 50 - 0 = 50
- 申请提现金额 (amount): 50
- 判断: 50 > 50 = false
✅ 提现申请通过
```
## 数据一致性保证
### 数据来源
| 字段 | 前端数据来源 | 后端数据来源 | 一致性 |
|------|-------------|-------------|--------|
| 累计佣金 | `/api/referral/data` | 实时查询 `orders` | ✅ 相同算法 |
| 已提现 | `/api/referral/data` | 读取 `users.withdrawn_earnings` | ✅ 相同字段 |
| 待审核 | `/api/referral/data` | 实时查询 `withdrawals` | ✅ 相同查询 |
### 同步机制
1. **前端数据**:来自 `/api/referral/data`
2. **后端校验**:独立查询,实时数据
3. **一致性保证**
- 相同的数据库表
- 相同的计算公式
- 后端数据更准确(实时查询)
## 相关文件
- `miniprogram/pages/referral/referral.js` - 前端校验 ✅
- `app/api/withdraw/route.ts` - 后端校验 ✅
- `app/api/referral/data/route.ts` - 数据查询
## 部署注意事项
### 1. 重启后端服务
```bash
python devlop.py restart mycontent
```
### 2. 清除前端缓存
```
微信开发者工具:工具 → 清除缓存 → 清除全部缓存数据
```
### 3. 监控日志
```bash
pm2 logs mycontent --lines 100
```
关注:
- `[Withdraw] 提现验证(完整版):` - 后端校验日志
- `[Referral] 收益计算(完整版)` - 前端计算日志
### 4. 测试验证
- [ ] 正常提现(可提现 > 最低金额)
- [ ] 超额提现(申请金额 > 可提现)
- [ ] 并发提现(快速点击两次)
- [ ] 审核后再提现
## 安全检查清单
- [x] 前端使用三元素计算
- [x] 后端使用三元素校验
- [x] 前后端公式完全一致
- [x] 后端独立查询数据(不信任前端)
- [x] 详细日志记录
- [x] 清晰的错误提示
## 总结
### 双向校验的意义
**前端**
- 用户体验优化
- 快速反馈
- 减少无效请求
**后端**
- 安全防线
- 资金保护
- 最终决策
### 公式统一
```javascript
// 前端和后端完全一致
可提现金额 = 累计佣金 - 已提现金额 - 待审核金额
```
### 防御能力
- ✅ 防篡改
- ✅ 防并发
- ✅ 防重放
- ✅ 防超额
**核心原则**:前端便利,后端安全,双重保障。

View File

@@ -0,0 +1,312 @@
# 提现审核流程优化
## 需求
提现申请提交后,应该明确告知用户:
- ✅ 提现申请已提交
- ⏳ 正在审核中
- 💰 审核通过后会自动到账微信零钱
而不是误导用户以为已经立即到账。
## 修改内容
### 1. 后端提示优化
**文件**`app/api/withdraw/route.ts`
```typescript
return NextResponse.json({
success: true,
message: '提现申请已提交,正在审核中,通过后会自动到账您的微信零钱',
data: {
withdrawId,
amount,
account,
accountType: accountType === 'alipay' ? '支付宝' : '微信',
status: 'pending' // ✅ 新增:返回状态
}
})
```
**变更**
- ❌ 旧:`message: '提现成功'` (误导性)
- ✅ 新:`message: '提现申请已提交,正在审核中,通过后会自动到账您的微信零钱'` (准确)
- ✅ 新增 `status: 'pending'` 字段
### 2. 前端提示优化
**文件**`miniprogram/pages/referral/referral.js`
```javascript
if (res.success) {
wx.showModal({
title: '提现申请已提交 ✅',
content: res.message || '正在审核中,通过后会自动到账您的微信零钱',
showCancel: false,
confirmText: '知道了'
})
// 刷新数据(此时待审核金额会增加,可提现金额会减少)
this.initData()
}
```
**变更**
- ❌ 旧标题:`'提现成功 🎉'` (误导性)
- ✅ 新标题:`'提现申请已提交 ✅'` (准确)
- ❌ 旧内容:`'¥X.XX 已到账您的微信零钱'` (虚假)
- ✅ 新内容:`'正在审核中,通过后会自动到账您的微信零钱'` (真实)
- ❌ 旧按钮:`'好的'`
- ✅ 新按钮:`'知道了'`
### 3. 错误提示优化
```javascript
} else {
wx.showToast({
title: res.message || res.error || '提现失败',
icon: 'none',
duration: 3000 // ✅ 增加显示时间到3秒
})
}
```
## 提现流程说明
### 完整流程
```
1. 用户点击"申请提现"
2. 前端验证:可提现金额 >= 最低提现金额
3. 后端创建提现记录status = 'pending'
4. 提示用户:"提现申请已提交,正在审核中"
5. 刷新分销中心数据:
- 待审核金额 += 提现金额
- 可提现金额 -= 提现金额
6. 管理员在后台审核
7. 审核通过:
- 更新 withdrawals.status = 'completed'
- 自动转账到微信零钱
- 发送微信通知给用户
```
### 状态说明
| 状态 | 英文 | 说明 | 用户提示 |
|------|------|------|----------|
| 待审核 | `pending` | 提现申请已提交,等待管理员审核 | 正在审核中 |
| 已完成 | `completed` | 审核通过,已转账到微信零钱 | 已到账 |
| 已拒绝 | `failed` | 审核未通过 | 提现失败 |
### 数据变化示例
**提现前**
```
累计佣金: 100元
待审核金额: 0元
可提现金额: 100 - 0 = 100元
```
**申请提现50元后**
```
累计佣金: 100元
待审核金额: 0 + 50 = 50元
可提现金额: 100 - 50 = 50元
```
**审核通过后**
```
累计佣金: 100元
待审核金额: 50 - 50 = 0元
已提现金额: 0 + 50 = 50元
可提现金额: 100 - 0 = 100元 (如果有新订单)
```
## 用户体验对比
### 修改前(误导性)
```
┌─────────────────────┐
│ 提现成功 🎉 │
├─────────────────────┤
│ ¥50.00 已到账您的 │
│ 微信零钱 │
├─────────────────────┤
│ [好的] │
└─────────────────────┘
```
**问题**
- ❌ 用户以为钱已经到账
- ❌ 用户去微信查看,发现没钱
- ❌ 产生疑惑和不满
### 修改后(准确清晰)
```
┌─────────────────────┐
│ 提现申请已提交 ✅ │
├─────────────────────┤
│ 正在审核中,通过后 │
│ 会自动到账您的微信 │
│ 零钱 │
├─────────────────────┤
│ [知道了] │
└─────────────────────┘
```
**优点**
- ✅ 用户知道需要等待审核
- ✅ 用户知道审核通过后会自动到账
- ✅ 符合实际情况
## 后续优化建议
### 1. 审核通知
管理员审核通过后,发送微信模板消息通知用户:
```javascript
// 伪代码
wx.sendTemplateMessage({
touser: userOpenId,
template_id: 'OPENTM415934726',
data: {
thing1: { value: '提现申请' },
amount2: { value: '50.00元' },
phrase3: { value: '审核通过' },
time4: { value: '2026-02-04 15:30' }
}
})
```
### 2. 提现记录页面
在"我的"页面增加"提现记录"入口,让用户查看:
- 待审核的提现申请
- 已完成的提现记录
- 失败的提现记录及原因
### 3. 审核预计时间
在提示中增加审核时长预期:
```
正在审核中预计1-3个工作日内完成审核
通过后会自动到账您的微信零钱
```
### 4. 自动审核
对于小额提现如50元以下可以实现自动审核
```javascript
if (amount <= 50) {
// 自动审核通过
await query(`UPDATE withdrawals SET status = 'completed' WHERE id = ?`, [withdrawId])
// 立即转账
await wechatPay.transferToWallet(...)
return { message: '提现成功,已到账您的微信零钱' }
} else {
// 需要人工审核
return { message: '提现申请已提交,正在审核中' }
}
```
## 管理后台提现审核功能
### 审核页面功能
1. **提现列表**
- 显示所有待审核的提现申请
- 显示用户信息、提现金额、申请时间
- 显示用户的累计佣金、历史提现次数
2. **审核操作**
- 通过:调用微信商家转账接口
- 拒绝:填写拒绝原因
3. **记录查询**
- 已完成的提现记录
- 失败的提现记录
### 审核接口(待实现)
```typescript
// POST /api/admin/withdraw/approve
{
"withdrawId": "W1738694028123",
"action": "approve" | "reject",
"reason": "拒绝原因(可选)"
}
```
## 测试步骤
### 1. 测试提现申请
1. 在小程序中进入分销中心
2. 确保可提现金额 >= 5元
3. 点击"申请提现"按钮
4. 确认提现金额
5. 查看提示是否为"提现申请已提交,正在审核中"
### 2. 验证数据变化
提现后立即刷新页面,检查:
- ✅ 累计佣金不变
- ✅ 待审核金额增加
- ✅ 可提现金额减少
- ✅ 提现按钮重新判断是否可用
### 3. 查看数据库
```sql
-- 查看提现记录
SELECT * FROM withdrawals
WHERE user_id = 'ogpTW5fmXRGNpoUbXB3UEqnVe5Tg'
ORDER BY created_at DESC
LIMIT 5;
-- 应该看到最新的记录status = 'pending'
```
### 4. 模拟审核通过
```sql
-- 手动更新状态为已完成
UPDATE withdrawals
SET status = 'completed',
completed_at = NOW()
WHERE id = 'W1738694028123';
```
再次刷新小程序,检查:
- ✅ 待审核金额减少
- ✅ 可提现金额恢复
## 相关文件
- `app/api/withdraw/route.ts` - 提现接口(修改返回消息)
- `miniprogram/pages/referral/referral.js` - 前端提现逻辑(修改提示)
## 总结
这次优化的核心是**准确传达信息**
- ❌ 不要给用户虚假期望("已到账"实际未到账)
- ✅ 明确告知用户当前状态("正在审核"
- ✅ 告知用户后续流程("通过后会自动到账"
这样可以:
1. 提升用户体验(不会产生困惑)
2. 减少客服咨询(用户知道要等待)
3. 建立信任(说到做到)

View File

@@ -0,0 +1,223 @@
# 提现按钮状态修复说明
## 问题描述
**现象**
- 累计佣金¥8.10
- 待审核¥0.00
- 可提现金额8.10元
- 最低提现金额5元
- **预期**:按钮应显示"申请提现 ¥8.10"(可用状态)
- **实际**:按钮显示"满5元可提现"(灰色禁用状态)
## 问题原因
`availableEarnings``formatMoney()` 格式化为字符串 `"8.10"`,在 WXML 模板中进行数值比较时可能出现类型转换问题。
## 解决方案
### 修改1增加数字类型字段
`referral.js``data` 中增加 `availableEarningsNum` 字段:
```javascript
data: {
availableEarnings: 0, // 字符串格式用于显示
availableEarningsNum: 0, // 数字格式用于判断
minWithdrawAmount: 10,
// ...
}
```
### 修改2初始化时同时设置两个字段
`initData()` 方法中:
```javascript
// 获取原始数字值(用于判断)
const availableEarningsNum = realData?.availableEarnings || 0
console.log('[Referral] 收益数据:')
console.log('[Referral] - totalCommission:', realData?.totalCommission)
console.log('[Referral] - availableEarnings:', availableEarningsNum)
console.log('[Referral] - minWithdrawAmount:', realData?.minWithdrawAmount)
console.log('[Referral] - 按钮应该', availableEarningsNum >= (realData?.minWithdrawAmount || 10) ? '可用' : '禁用')
this.setData({
// ...
availableEarnings: formatMoney(availableEarningsNum), // 字符串,用于显示
availableEarningsNum: availableEarningsNum, // 数字,用于判断
minWithdrawAmount: realData?.minWithdrawAmount || 10,
// ...
})
```
### 修改3WXML 使用数字字段判断
```xml
<view class="withdraw-btn {{availableEarningsNum < minWithdrawAmount ? 'btn-disabled' : ''}}" bindtap="handleWithdraw">
{{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}}
</view>
```
**注意**
- 类名判断使用 `availableEarningsNum`(数字)
- 文本显示使用 `availableEarnings`(字符串,格式化后的)
### 修改4提现逻辑也使用数字字段
```javascript
async handleWithdraw() {
// 使用数字版本直接进行判断,避免重复转换
const availableEarnings = this.data.availableEarningsNum || 0
const minWithdrawAmount = this.data.minWithdrawAmount || 10
console.log('[Withdraw] 提现检查:', {
availableEarnings,
minWithdrawAmount,
shouldEnable: availableEarnings >= minWithdrawAmount
})
if (availableEarnings <= 0) {
wx.showToast({ title: '暂无可提现收益', icon: 'none' })
return
}
if (availableEarnings < minWithdrawAmount) {
wx.showToast({
title: `满${minWithdrawAmount}元可提现`,
icon: 'none'
})
return
}
// ...
}
```
## 测试步骤
### 1. 清理小程序缓存(重要)
在微信开发者工具中:
1. **清除缓存数据**
- 点击顶部菜单 `工具``清除缓存``清除全部缓存数据`
2. **重新编译**
- 点击 `编译` 按钮
- 或使用快捷键 `Ctrl + B` (Windows) / `Cmd + B` (Mac)
3. **如果还不行,尝试完全重启**
- 关闭微信开发者工具
- 重新打开项目
- 再次编译
### 2. 查看调试日志
打开控制台Console查找以下日志
```
[Referral] 收益数据:
[Referral] - totalCommission: 8.1
[Referral] - availableEarnings: 8.1
[Referral] - minWithdrawAmount: 5
[Referral] - 按钮应该 可用
```
如果看到"按钮应该 可用",说明逻辑判断正确。
### 3. 点击提现按钮
如果按钮仍然是灰色,点击它,查看是否有日志输出:
```
[Withdraw] 提现检查: {
availableEarnings: 8.1,
minWithdrawAmount: 5,
shouldEnable: true
}
```
### 4. 检查数据值
在小程序调试器的 `AppData` 标签页中查找 `referral` 页面的数据:
```json
{
"availableEarnings": "8.10", // 字符串
"availableEarningsNum": 8.1, // 数字
"minWithdrawAmount": 5
}
```
确认 `availableEarningsNum` 是数字类型,不是字符串。
## 预期结果
修复后,当 `availableEarningsNum` (8.1) >= `minWithdrawAmount` (5) 时:
- ✅ 按钮显示:`申请提现 ¥8.10`
- ✅ 按钮样式:渐变青色背景(可点击)
- ✅ 点击后弹出提现确认对话框
## 常见问题
### Q1: 修改后按钮还是灰色?
**A**: 清除小程序缓存并重新编译:
```
工具 → 清除缓存 → 清除全部缓存数据
然后点击 编译 按钮
```
### Q2: 控制台没有看到调试日志?
**A**:
1. 确保控制台的日志级别包含 `log`
2. 检查是否过滤了某些日志
3. 尝试刷新页面(下拉刷新或重新进入)
### Q3: `availableEarningsNum` 是 undefined
**A**: 检查后端 API 返回的数据格式,确保 `realData.availableEarnings` 有值:
```javascript
console.log('API返回:', realData)
```
### Q4: 数据正确但按钮还是不可点击?
**A**: 检查 WXSS 中是否有其他样式覆盖:
```css
.withdraw-btn.btn-disabled {
pointer-events: none; /* 可能导致无法点击 */
}
```
## 相关文件
- `miniprogram/pages/referral/referral.js` - 主要逻辑
- `miniprogram/pages/referral/referral.wxml` - 模板
- `miniprogram/pages/referral/referral.wxss` - 样式
- `app/api/referral/data/route.ts` - 后端API
## 验证清单
- [ ] 后端API返回正确的 `availableEarnings` 数值
- [ ] `initData()` 中正确设置 `availableEarningsNum`
- [ ] WXML 使用 `availableEarningsNum` 进行条件判断
- [ ] 清除小程序缓存并重新编译
- [ ] 控制台显示正确的调试日志
- [ ] 按钮显示正确文本和样式
- [ ] 点击按钮可以正常提现
## 技术总结
**核心问题**:字符串类型的数字在某些场景下的比较可能不符合预期。
**解决思路**
1. 保存两份数据:字符串用于显示,数字用于判断
2. 在数据初始化时就区分好类型
3. 在需要比较的地方使用数字类型
4. 添加详细的调试日志便于排查问题
**最佳实践**
- ✅ 数值计算和比较始终使用 Number 类型
- ✅ 格式化显示使用 String 类型
- ✅ 在 setData 时明确类型转换
- ✅ 避免在模板中进行复杂的类型转换

View File

@@ -0,0 +1,263 @@
# 提现按钮逻辑修正
## 问题描述
**错误理解**
- 错误地直接使用后端返回的 `availableEarnings` 进行判断
**正确逻辑**
-**可提现金额 = 累计佣金 - 待审核金额**
-**按钮启用条件:可提现金额 >= 最低提现金额**
## 具体案例
当前数据:
- 累计佣金¥8.10
- 待审核金额¥0.00
- **计算:可提现金额 = 8.10 - 0 = 8.10元**
- 最低提现金额¥5.00
- **判断8.10 >= 5.00 = true** → 按钮应该启用(绿色)
## 修复方案
### 1. 前端计算可提现金额
`miniprogram/pages/referral/referral.js``initData()` 方法中:
```javascript
// ✅ 修正:可提现金额 = 累计佣金 - 待审核金额
const totalCommissionNum = realData?.totalCommission || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = totalCommissionNum - pendingWithdrawNum
const minWithdrawAmount = realData?.minWithdrawAmount || 10
console.log('=== [Referral] 收益计算(修正后)===')
console.log('累计佣金:', totalCommissionNum)
console.log('待审核金额:', pendingWithdrawNum)
console.log('可提现金额 = 累计佣金 - 待审核金额 =', totalCommissionNum, '-', pendingWithdrawNum, '=', availableEarningsNum)
console.log('最低提现金额:', minWithdrawAmount)
console.log('按钮判断:', availableEarningsNum, '>=', minWithdrawAmount, '=', availableEarningsNum >= minWithdrawAmount)
console.log('✅ 按钮应该:', availableEarningsNum >= minWithdrawAmount ? '🟢 启用(绿色)' : '⚫ 禁用(灰色)')
```
### 2. 设置数据
```javascript
this.setData({
// 收益数据 - 格式化为两位小数
totalCommission: formatMoney(totalCommissionNum),
availableEarnings: formatMoney(availableEarningsNum), // ✅ 使用计算后的可提现金额
availableEarningsNum: availableEarningsNum, // ✅ 数字格式用于按钮判断
pendingWithdrawAmount: formatMoney(pendingWithdrawNum),
minWithdrawAmount: minWithdrawAmount,
// ...
})
```
### 3. 按钮判断逻辑WXML
```xml
<view class="withdraw-btn {{availableEarningsNum < minWithdrawAmount ? 'btn-disabled' : ''}}" bindtap="handleWithdraw">
{{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}}
</view>
```
**注意**
- 类名判断:`availableEarningsNum < minWithdrawAmount`(数字比较)
- 文本显示:使用格式化后的 `availableEarnings` 字符串
### 4. 提现函数中的验证
```javascript
async handleWithdraw() {
// 使用数字版本直接进行判断
const availableEarnings = this.data.availableEarningsNum || 0
const minWithdrawAmount = this.data.minWithdrawAmount || 10
console.log('[Withdraw] 提现检查:', {
availableEarnings,
minWithdrawAmount,
shouldEnable: availableEarnings >= minWithdrawAmount
})
if (availableEarnings <= 0) {
wx.showToast({ title: '暂无可提现收益', icon: 'none' })
return
}
if (availableEarnings < minWithdrawAmount) {
wx.showToast({
title: `满${minWithdrawAmount}元可提现`,
icon: 'none'
})
return
}
// ... 继续提现逻辑
}
```
## 测试步骤
### 1. 完全清除缓存
在微信开发者工具中:
```
1. 关闭微信开发者工具
2. 重新打开项目
3. 工具 → 清除缓存 → 清除全部缓存数据
4. 点击"编译"按钮
```
### 2. 查看控制台日志
应该看到类似这样的输出:
```
=== [Referral] 收益计算(修正后)===
累计佣金: 8.1
待审核金额: 0
可提现金额 = 累计佣金 - 待审核金额 = 8.1 - 0 = 8.1
最低提现金额: 5
按钮判断: 8.1 >= 5 = true
✅ 按钮应该: 🟢 启用(绿色)
=== [Referral] 按钮状态验证 ===
累计佣金 (totalCommission): 8.10
待审核金额 (pendingWithdrawAmount): 0.00
可提现金额 (availableEarnings 显示): 8.10
可提现金额 (availableEarningsNum 判断): 8.1 number
最低提现金额 (minWithdrawAmount): 5 number
按钮启用条件: 8.1 >= 5 = true
✅ 最终结果: 按钮应该 🟢 启用
```
### 3. 验证界面
- ✅ 按钮文本:`申请提现 ¥8.10`
- ✅ 按钮样式:渐变青色背景(不是灰色)
- ✅ 可以点击
## 逻辑公式总结
### 核心计算
```
可提现金额 = 累计佣金 - 待审核金额
```
### 按钮状态
```
if (可提现金额 >= 最低提现金额) {
// ✅ 启用按钮(绿色)
显示文本: "申请提现 ¥{可提现金额}"
} else {
// ❌ 禁用按钮(灰色)
显示文本: "满{最低提现金额}元可提现"
}
```
### 数据关系
```
totalCommission (累计佣金)
├─ 所有已完成订单的佣金总和
└─ 显示在顶部"累计佣金"位置
pendingWithdrawAmount (待审核金额)
├─ 已申请提现但未审核通过的金额总和
└─ 显示在"待审核"位置
availableEarnings (可提现金额)
├─ = totalCommission - pendingWithdrawAmount
├─ 用户实际可以申请提现的金额
└─ 显示在提现按钮上
minWithdrawAmount (最低提现金额)
├─ 从管理后台配置获取
├─ 默认值10元
└─ 用于判断是否允许提现
```
## 为什么要在前端计算?
### 优势
1. **实时准确**
- 每次进入页面都基于最新的累计佣金和待审核金额计算
- 避免后端缓存导致的数据延迟
2. **逻辑清晰**
- 公式简单明了:`累计佣金 - 待审核金额`
- 便于调试和验证
3. **减轻后端负担**
- 简单的减法运算在前端完成
- 后端只需返回原始数据
### 数据流
```
后端API返回:
{
totalCommission: 8.1, // 累计佣金
pendingWithdrawAmount: 0, // 待审核金额
minWithdrawAmount: 5 // 最低提现金额
}
前端计算:
availableEarningsNum = 8.1 - 0 = 8.1 // 可提现金额
前端判断:
8.1 >= 5 ? 启用按钮 : 禁用按钮
```
## 相关文件
- `miniprogram/pages/referral/referral.js` - 计算逻辑
- `miniprogram/pages/referral/referral.wxml` - 按钮显示
- `miniprogram/pages/referral/referral.wxss` - 按钮样式
## 常见问题
### Q1: 为什么要保存两个字段?
**A**:
- `availableEarnings` (字符串):用于界面显示,格式化为 "8.10"
- `availableEarningsNum` (数字):用于条件判断,精确比较 `8.1 >= 5`
### Q2: 后端的 availableEarnings 还有用吗?
**A**: 如果后端返回了 `availableEarnings`,现在会被前端计算的值覆盖。建议:
- 方案1后端不再返回 `availableEarnings`,只返回 `totalCommission``pendingWithdrawAmount`
- 方案2保留后端计算但前端不使用当前方案
### Q3: 如果待审核金额大于累计佣金怎么办?
**A**: 理论上不应该出现这种情况,但可以添加保护:
```javascript
const availableEarningsNum = Math.max(0, totalCommissionNum - pendingWithdrawNum)
```
## 验证清单
- [x] 修改前端计算逻辑:`可提现金额 = 累计佣金 - 待审核金额`
- [x] 添加详细调试日志
- [x] 确保使用数字类型进行比较
- [x] 清除小程序缓存
- [x] 重新编译
- [ ] 查看控制台日志验证计算
- [ ] 确认按钮显示正确文本和样式
- [ ] 测试点击提现功能
## 总结
这次修正的核心是**理解业务逻辑**
1. **累计佣金** = 所有获得的佣金(历史总和)
2. **待审核金额** = 已申请但未到账的金额
3. **可提现金额** = 累计佣金 - 待审核金额 = 当前可以申请提现的金额
4. **按钮启用** = 可提现金额 >= 最低提现金额
之前的错误是直接使用后端返回的值,没有理解这个减法关系。现在在前端明确计算,确保逻辑正确。

View File

@@ -0,0 +1,348 @@
# 提现接口逻辑修正
## 问题描述
**错误提示**
```
POST /api/withdraw
Response: {
success: false,
message: "可提现金额不足,当前可提现 ¥0.00"
}
```
**问题原因**
后端提现接口的佣金计算逻辑与前端不一致导致计算出的可提现金额为0。
## 问题分析
### 旧逻辑(错误)
```typescript
// ❌ 从 referral_bindings 表查询
const earningsResult = await query(`
SELECT COALESCE(SUM(commission), 0) as total_commission
FROM referral_bindings
WHERE referrer_id = ? AND status = 'converted'
`, [userId])
totalEarnings = parseFloat(earningsResult[0]?.total_commission || 0)
// 计算可提现金额
const availableAmount = totalEarnings - withdrawnAmount
```
**问题**
1.`referral_bindings.commission` 字段查询,但该字段可能未维护或不准确
2. 与前端/分销中心API的计算逻辑不一致
3. 导致后端计算的可提现金额为0
### 正确逻辑
应该与前端和 `/api/referral/data` 保持一致:
```
累计佣金 = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid') × distributorShare
可提现金额 = 累计佣金 - 待审核提现金额
```
## 修复方案
### 1. 修改累计佣金计算
**文件**`app/api/withdraw/route.ts`
```typescript
// ✅ 修正:从 orders 表查询累计佣金(与前端逻辑一致)
let totalCommission = 0
try {
// 读取分成比例
let distributorShare = 0.9 // 默认90%
try {
const config = await getConfig('referral_config')
if (config?.distributorShare) {
distributorShare = Number(config.distributorShare)
}
} catch (e) {
console.warn('[Withdraw] 读取分成比例失败,使用默认值 90%')
}
// 查询订单总金额
const ordersResult = await query(`
SELECT COALESCE(SUM(amount), 0) as total_amount
FROM orders
WHERE referrer_id = ? AND status = 'paid'
`, [userId]) as any[]
const totalAmount = parseFloat(ordersResult[0]?.total_amount || 0)
totalCommission = totalAmount * distributorShare
console.log('[Withdraw] 佣金计算:')
console.log('- 订单总金额:', totalAmount)
console.log('- 分成比例:', distributorShare * 100 + '%')
console.log('- 累计佣金:', totalCommission)
} catch (e) {
console.log('[Withdraw] 查询收益失败:', e)
}
```
### 2. 修改可提现金额计算
```typescript
// 查询待审核提现金额
let pendingWithdrawAmount = 0
try {
const pendingResult = await query(`
SELECT COALESCE(SUM(amount), 0) as pending_amount
FROM withdrawals
WHERE user_id = ? AND status = 'pending'
`, [userId]) as any[]
pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
} catch (e) {
console.log('[Withdraw] 查询待审核金额失败:', e)
}
// ✅ 修正:可提现金额 = 累计佣金 - 待审核金额(与前端逻辑一致)
const availableAmount = totalCommission - pendingWithdrawAmount
console.log('[Withdraw] 提现验证:')
console.log('- 累计佣金 (totalCommission):', totalCommission)
console.log('- 待审核金额 (pendingWithdrawAmount):', pendingWithdrawAmount)
console.log('- 可提现金额 (availableAmount):', availableAmount)
console.log('- 申请提现金额 (amount):', amount)
console.log('- 判断:', amount, '>', availableAmount, '=', amount > availableAmount)
if (amount > availableAmount) {
return NextResponse.json({
success: false,
message: `可提现金额不足,当前可提现 ¥${availableAmount.toFixed(2)},待审核 ¥${pendingWithdrawAmount.toFixed(2)}`
})
}
```
## 修改对比
### 数据来源
| 项目 | 旧逻辑 | 新逻辑 |
|------|--------|--------|
| 累计佣金 | `referral_bindings.commission` | `orders.amount × distributorShare` |
| 已提现 | `withdrawals.status = 'completed'` | **改为待审核** |
| 待审核 | ❌ 未查询 | `withdrawals.status = 'pending'` |
| 可提现 | `累计佣金 - 已提现` | `累计佣金 - 待审核` |
### 计算公式
**旧逻辑**
```
累计佣金 = SUM(referral_bindings.commission WHERE status = 'converted')
已提现金额 = SUM(withdrawals.amount WHERE status = 'completed')
可提现金额 = 累计佣金 - 已提现金额 ❌ 错误
```
**新逻辑**
```
订单总金额 = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid')
累计佣金 = 订单总金额 × distributorShare (90%)
待审核金额 = SUM(withdrawals.amount WHERE status = 'pending')
可提现金额 = 累计佣金 - 待审核金额 ✅ 正确
```
## 一致性验证
现在三个地方的逻辑完全一致:
### 1. 前端小程序 (`referral.js`)
```javascript
const totalCommissionNum = realData?.totalCommission || 0
const pendingWithdrawNum = realData?.pendingWithdrawAmount || 0
const availableEarningsNum = totalCommissionNum - pendingWithdrawNum
```
### 2. 分销数据API (`/api/referral/data`)
```typescript
// 计算累计佣金
const totalAmount = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid')
const totalCommission = totalAmount * distributorShare
// 查询待审核金额
const pendingWithdrawAmount = SUM(withdrawals.amount WHERE user_id = userId AND status = 'pending')
// 返回给前端
return {
totalCommission,
pendingWithdrawAmount,
availableEarnings: totalCommission - pendingWithdrawAmount
}
```
### 3. 提现API (`/api/withdraw`)
```typescript
// 计算累计佣金
const totalAmount = SUM(orders.amount WHERE referrer_id = userId AND status = 'paid')
const totalCommission = totalAmount * distributorShare
// 查询待审核金额
const pendingWithdrawAmount = SUM(withdrawals.amount WHERE user_id = userId AND status = 'pending')
// 验证可提现金额
const availableAmount = totalCommission - pendingWithdrawAmount
if (amount > availableAmount) {
return error("可提现金额不足")
}
```
## 测试步骤
### 1. 查看后端日志
提现时应该看到详细日志:
```
[Withdraw] 佣金计算:
- 订单总金额: 9
- 分成比例: 90%
- 累计佣金: 8.1
[Withdraw] 提现验证:
- 累计佣金 (totalCommission): 8.1
- 待审核金额 (pendingWithdrawAmount): 0
- 可提现金额 (availableAmount): 8.1
- 申请提现金额 (amount): 8.1
- 判断: 8.1 > 8.1 = false
```
### 2. 测试提现
```bash
# 测试提现API
curl -X POST http://localhost:3006/api/withdraw \
-H "Content-Type: application/json" \
-d '{
"userId": "ogpTW5fmXRGNpoUbXB3UEqnVe5Tg",
"amount": 8.1
}'
```
**预期结果**
```json
{
"success": true,
"message": "提现成功",
"data": {
"withdrawId": "W1738694028123",
"amount": 8.1,
"account": "...",
"accountType": "微信"
}
}
```
### 3. 验证数据库
```sql
-- 查看待审核提现记录
SELECT * FROM withdrawals WHERE user_id = 'ogpTW5fmXRGNpoUbXB3UEqnVe5Tg' AND status = 'pending';
-- 查看订单总金额
SELECT SUM(amount) as total_amount
FROM orders
WHERE referrer_id = 'ogpTW5fmXRGNpoUbXB3UEqnVe5Tg' AND status = 'paid';
```
## 常见问题
### Q1: 为什么改成"待审核"而不是"已提现"
**A**: 因为提现流程是:
1. 用户申请提现 → 状态 `pending`(待审核)
2. 管理员审核通过 → 状态改为 `completed`(已完成)
在用户申请提现后,这笔金额应该从"可提现"中扣除,所以要减去 `status = 'pending'` 的金额。
### Q2: 如果有多笔待审核提现会怎样?
**A**:
```
累计佣金: 100元
待审核: 30元 + 20元 = 50元
可提现: 100 - 50 = 50元
```
用户只能再申请最多50元的提现。
### Q3: 审核通过后会发生什么?
**A**:
```sql
-- 管理员审核通过
UPDATE withdrawals SET status = 'completed' WHERE id = 'W123';
```
这笔金额从"待审核"变为"已完成",下次计算时:
```
待审核金额减少 → 可提现金额增加(如果有新订单)
```
### Q4: referral_bindings 表还有用吗?
**A**: 有用,但不再用于佣金计算:
- 记录绑定关系
- 记录绑定状态active/expired
- 记录购买次数
- 但佣金数据以 `orders` 表为准
## 部署说明
### 1. 重启服务
修改后需要重启 Next.js 服务:
```bash
# 使用部署脚本重启
python devlop.py restart mycontent
# 或手动重启
pm2 restart mycontent
```
### 2. 查看日志
```bash
# 查看实时日志
pm2 logs mycontent
# 查看最近的日志
pm2 logs mycontent --lines 100
```
### 3. 监控错误
关注以下日志:
- `[Withdraw] 佣金计算:` - 佣金计算是否正确
- `[Withdraw] 提现验证:` - 可提现金额是否准确
- `[Withdraw] 查询收益失败:` - 是否有表不存在等错误
## 相关文件
- `app/api/withdraw/route.ts` - 提现接口(本次修改)
- `app/api/referral/data/route.ts` - 分销数据接口(已统一)
- `miniprogram/pages/referral/referral.js` - 前端逻辑(已统一)
## 总结
这次修复确保了**三端逻辑完全一致**
1. **前端小程序**:显示的可提现金额
2. **分销数据API**:返回的数据
3. **提现接口**:验证的金额
都使用相同的计算方式:
```
可提现金额 = (订单总金额 × 分成比例) - 待审核提现金额
```
这样可以避免前端显示可以提现,但后端验证失败的问题。