diff --git a/app/admin/layout.tsx b/app/admin/layout.tsx index baf45d6b..b1d70049 100644 --- a/app/admin/layout.tsx +++ b/app/admin/layout.tsx @@ -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" }, // 合并:分销+订单+提现 diff --git a/app/api/withdraw/route.ts b/app/api/withdraw/route.ts index e2e3aadc..8ec4e999 100644 --- a/app/api/withdraw/route.ts +++ b/app/api/withdraw/route.ts @@ -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' } }) diff --git a/miniprogram/pages/referral/referral.js b/miniprogram/pages/referral/referral.js index bfa3b83b..f2c20f8d 100644 --- a/miniprogram/pages/referral/referral.js +++ b/miniprogram/pages/referral/referral.js @@ -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},是否立即提现?`, diff --git a/miniprogram/pages/referral/referral.wxml b/miniprogram/pages/referral/referral.wxml index 806840cf..40f8645c 100644 --- a/miniprogram/pages/referral/referral.wxml +++ b/miniprogram/pages/referral/referral.wxml @@ -40,17 +40,17 @@ - 累计佣金 + 可提现金额 {{shareRate}}% 返利 - ¥{{totalCommission}} - 待审核: ¥{{pendingWithdrawAmount}} + ¥{{availableEarnings}} + 累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}} - - {{availableEarnings < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}} + + {{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}} diff --git a/开发文档/8、部署/可提现金额计算修复.md b/开发文档/8、部署/可提现金额计算修复.md new file mode 100644 index 00000000..f2dd5a5d --- /dev/null +++ b/开发文档/8、部署/可提现金额计算修复.md @@ -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. 逻辑清晰,符合资金流向 +``` + +### 业务含义 + +- **累计佣金**:成就感 - "我赚了多少" +- **已提现**:安全感 - "我拿到了多少" +- **待审核**:期待感 - "我正在提多少" +- **可提现**:行动力 - "我能提多少" + +这样的设计既满足了用户查看历史成就的需求,又确保了资金安全和准确性。 diff --git a/开发文档/8、部署/可提现金额计算逻辑分析.md b/开发文档/8、部署/可提现金额计算逻辑分析.md new file mode 100644 index 00000000..772fab56 --- /dev/null +++ b/开发文档/8、部署/可提现金额计算逻辑分析.md @@ -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 + + ¥{{availableEarnings}} + + 累计: ¥{{totalCommission}} | 已提现: ¥{{withdrawnEarnings}} | 待审核: ¥{{pendingWithdrawAmount}} + + +``` + +或者简化显示: +```xml + + ¥{{availableEarnings}} + + 累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}} + + +``` + +## 数据流向图 + +``` +┌─────────────────────────────────────────────────┐ +│ 订单产生佣金 │ +│ ↓ │ +│ 累计佣金: ¥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` - 审核接口(已正确) diff --git a/开发文档/8、部署/提现卡片显示优化.md b/开发文档/8、部署/提现卡片显示优化.md new file mode 100644 index 00000000..7687df1f --- /dev/null +++ b/开发文档/8、部署/提现卡片显示优化.md @@ -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 + + + + + + + 累计佣金 + {{shareRate}}% 返利 + + + + ¥{{totalCommission}} + 待审核: ¥{{pendingWithdrawAmount}} + + + + + + + + + 可提现金额 + {{shareRate}}% 返利 + + + + ¥{{availableEarnings}} + 累计: ¥{{totalCommission}} | 待审核: ¥{{pendingWithdrawAmount}} + +``` + +## 数据说明 + +### 字段含义 + +| 字段 | 说明 | 示例 | +|------|------|------| +| `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 + + 累计: ¥{{totalCommission}} · 待审核: ¥{{pendingWithdrawAmount}} + +``` + +用 `·` (中点) 代替 `|` (竖线) + +### 3. 增加图标说明 + +```xml + + 💰 累计: ¥{{totalCommission}} ⏳ 待审核: ¥{{pendingWithdrawAmount}} + +``` + +## 测试要点 + +### 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) + +## 总结 + +这次优化让提现卡片更加**用户友好**: + +**修改前**: +- 显示"累计佣金",用户需要自己计算 + +**修改后**: +- 直接显示"可提现金额",一目了然 +- 保留明细信息(累计、待审核),信息完整 +- 主次分明,视觉层级清晰 + +核心理念:**把用户最关心的数据,放在最显眼的位置**。 diff --git a/开发文档/8、部署/提现双向校验实现.md b/开发文档/8、部署/提现双向校验实现.md new file mode 100644 index 00000000..0556f0b5 --- /dev/null +++ b/开发文档/8、部署/提现双向校验实现.md @@ -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` +- ✅ 数据库事务保证原子性 + +### 场景3:API 重放攻击 + +**攻击**: +- 捕获提现请求,重复发送 + +**防御**: +- ✅ 后端实时校验可提现金额 +- ✅ 创建提现记录后,`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 +// 前端和后端完全一致 +可提现金额 = 累计佣金 - 已提现金额 - 待审核金额 +``` + +### 防御能力 + +- ✅ 防篡改 +- ✅ 防并发 +- ✅ 防重放 +- ✅ 防超额 + +**核心原则**:前端便利,后端安全,双重保障。 diff --git a/开发文档/8、部署/提现审核流程优化.md b/开发文档/8、部署/提现审核流程优化.md new file mode 100644 index 00000000..ff9acd1c --- /dev/null +++ b/开发文档/8、部署/提现审核流程优化.md @@ -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. 建立信任(说到做到) diff --git a/开发文档/8、部署/提现按钮状态修复说明.md b/开发文档/8、部署/提现按钮状态修复说明.md new file mode 100644 index 00000000..35a7ef77 --- /dev/null +++ b/开发文档/8、部署/提现按钮状态修复说明.md @@ -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, + // ... +}) +``` + +### 修改3:WXML 使用数字字段判断 + +```xml + + {{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}} + +``` + +**注意**: +- 类名判断使用 `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 时明确类型转换 +- ✅ 避免在模板中进行复杂的类型转换 diff --git a/开发文档/8、部署/提现按钮逻辑修正.md b/开发文档/8、部署/提现按钮逻辑修正.md new file mode 100644 index 00000000..f3fe2743 --- /dev/null +++ b/开发文档/8、部署/提现按钮逻辑修正.md @@ -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 + + {{availableEarningsNum < minWithdrawAmount ? '满' + minWithdrawAmount + '元可提现' : '申请提现 ¥' + availableEarnings}} + +``` + +**注意**: +- 类名判断:`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. **按钮启用** = 可提现金额 >= 最低提现金额 + +之前的错误是直接使用后端返回的值,没有理解这个减法关系。现在在前端明确计算,确保逻辑正确。 diff --git a/开发文档/8、部署/提现接口逻辑修正.md b/开发文档/8、部署/提现接口逻辑修正.md new file mode 100644 index 00000000..bf850617 --- /dev/null +++ b/开发文档/8、部署/提现接口逻辑修正.md @@ -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. **提现接口**:验证的金额 + +都使用相同的计算方式: +``` +可提现金额 = (订单总金额 × 分成比例) - 待审核提现金额 +``` + +这样可以避免前端显示可以提现,但后端验证失败的问题。