新增订单推荐人和邀请码功能,优化支付流程中的订单插入逻辑,确保订单记录准确。更新小程序支付请求,支持传递邀请码以便于分销归属和对账。同时,调整数据库结构以支持新字段,提升系统的稳定性和用户体验。

This commit is contained in:
2026-02-06 18:34:02 +08:00
parent f8fac00c85
commit 2e65d68e1e
34 changed files with 3288 additions and 1255 deletions

View File

@@ -1,33 +1,27 @@
/**
* 提现API
* 用户提现到微信零钱或支付宝
* 提现API - 使用 Prisma ORM
* 用户提现到微信零钱
*
* Prisma 优势:
* - 完全类型安全
* - 自动防SQL注入
* - 简化复杂查询
*/
import { NextRequest, NextResponse } from 'next/server'
import { query, getConfig } from '@/lib/db'
import { prisma } from '@/lib/prisma'
import { Decimal } from '@/lib/generated/prisma/runtime/library'
// 确保提现表存在
async function ensureWithdrawalsTable() {
// 读取系统配置(使用 Prisma
async function getPrismaConfig(key: string): Promise<any> {
try {
await query(`
CREATE TABLE IF NOT EXISTS withdrawals (
id VARCHAR(64) PRIMARY KEY,
user_id VARCHAR(64) NOT NULL,
amount DECIMAL(10,2) NOT NULL,
account_type VARCHAR(20) DEFAULT 'wechat',
account VARCHAR(100),
status ENUM('pending', 'processing', 'success', 'failed') DEFAULT 'pending',
wechat_openid VARCHAR(100),
transaction_id VARCHAR(100),
error_message VARCHAR(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
processed_at TIMESTAMP NULL,
INDEX idx_user_id (user_id),
INDEX idx_status (status)
)
`)
const config = await prisma.system_config.findUnique({
where: { config_key: key }
})
return config?.config_value
} catch (e) {
console.log('[Withdraw] 表已存在或创建失败')
console.warn(`[Config] 读取配置 ${key} 失败:`, e)
return null
}
}
@@ -44,10 +38,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ success: false, message: '提现金额无效' }, { status: 400 })
}
// 读取最低提现门槛
// 1. 读取最低提现门槛
let minWithdrawAmount = 10 // 默认值
try {
const config = await getConfig('referral_config')
const config = await getPrismaConfig('referral_config')
if (config?.minWithdrawAmount) {
minWithdrawAmount = Number(config.minWithdrawAmount)
}
@@ -63,37 +57,47 @@ export async function POST(request: NextRequest) {
}, { status: 400 })
}
// 确保表存在
await ensureWithdrawalsTable()
// 2. 查询用户信息Prisma 保证类型安全)
const user = await prisma.users.findUnique({
where: { id: userId },
select: {
id: true,
open_id: true,
wechat_id: true,
withdrawn_earnings: true
}
})
// 查询用户信息
const users = await query('SELECT * FROM users WHERE id = ?', [userId]) as any[]
if (!users || users.length === 0) {
if (!user) {
return NextResponse.json({ success: false, message: '用户不存在' }, { status: 404 })
}
const user = users[0]
// 微信零钱提现需要 open_id小程序/公众号登录获得)
const openId = user.open_id || ''
const wechatId = user.wechat || user.wechat_id || ''
const alipayId = user.alipay || ''
if (!openId && !alipayId) {
if (!openId) {
return NextResponse.json({
success: false,
message: '提现到微信零钱需先使用微信登录;或绑定支付宝后提现到支付宝',
message: '提现到微信零钱需先使用微信登录',
needBind: true
})
}
// ✅ 修正:从 orders 表查询累计佣金(与前端逻辑一致
// 提现需绑定微信号(用于后台展示收款账号
const wechatId = user.wechat_id?.trim() || ''
if (!wechatId) {
return NextResponse.json({
success: false,
message: '请先到「设置」中绑定微信号后再提现',
needBindWechat: true
})
}
// 3. 计算累计佣金(从 orders 表查询)
let totalCommission = 0
try {
// 读取分成比例
let distributorShare = 0.9 // 默认90%
try {
const config = await getConfig('referral_config')
const config = await getPrismaConfig('referral_config')
if (config?.distributorShare) {
distributorShare = Number(config.distributorShare)
}
@@ -101,14 +105,18 @@ export async function POST(request: NextRequest) {
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[]
// 使用 Prisma 聚合查询
const ordersResult = await prisma.orders.aggregate({
where: {
referrer_id: userId,
status: 'paid'
},
_sum: {
amount: true
}
})
const totalAmount = parseFloat(ordersResult[0]?.total_amount || 0)
const totalAmount = Number(ordersResult._sum.amount || 0)
totalCommission = totalAmount * distributorShare
console.log('[Withdraw] 佣金计算:')
@@ -116,41 +124,32 @@ export async function POST(request: NextRequest) {
console.log('- 分成比例:', distributorShare * 100 + '%')
console.log('- 累计佣金:', totalCommission)
} catch (e) {
// 如果表不存在收益为0
console.log('[Withdraw] 查询收益失败,可能表不存在:', e)
console.log('[Withdraw] 查询收益失败:', e)
}
// 查询已提现金额
let withdrawnEarnings = 0
try {
withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
} catch (e) {
console.log('[Withdraw] 读取已提现金额失败:', e)
}
// 4. 已提现金额与待审核金额均以提现表为准(与分销页展示一致,避免 user.withdrawn_earnings 不同步导致负数或超额提现)
const [pendingResult, successResult] = await Promise.all([
prisma.withdrawals.aggregate({
where: { user_id: userId, status: 'pending' },
_sum: { amount: true }
}),
prisma.withdrawals.aggregate({
where: { user_id: userId, status: 'success' },
_sum: { amount: true }
})
])
const withdrawnEarnings = Number(successResult._sum.amount || 0)
const pendingWithdrawAmount = Number(pendingResult._sum.amount || 0)
// 查询待审核提现金额
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
// 5. 计算可提现金额(不低于 0
const availableAmount = Math.max(0, 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)
console.log('- 累计佣金:', totalCommission)
console.log('- 已提现金额:', withdrawnEarnings)
console.log('- 待审核金额:', pendingWithdrawAmount)
console.log('- 可提现金额 =', availableAmount)
console.log('- 申请提现金额:', amount)
if (amount > availableAmount) {
return NextResponse.json({
@@ -159,37 +158,29 @@ export async function POST(request: NextRequest) {
})
}
// 创建提现记录(微信零钱需保存 wechat_openid 供后台批准时调用商家转账到零钱
// 7. 创建提现记录(使用 Prisma无SQL注入风险
const withdrawId = `W${Date.now()}`
const accountType = alipayId ? 'alipay' : 'wechat'
const account = alipayId || wechatId
try {
await query(`
INSERT INTO withdrawals (id, user_id, amount, status, wechat_openid, created_at)
VALUES (?, ?, ?, 'pending', ?, NOW())
`, [withdrawId, userId, amount, accountType === 'wechat' ? openId : null])
// 微信零钱由后台批准时调用「商家转账到零钱」;支付宝/无 openid 时仅标记成功(需线下打款)
if (accountType !== 'wechat' || !openId) {
await query(`UPDATE withdrawals SET status = 'success', processed_at = NOW() WHERE id = ?`, [withdrawId])
await query(`
UPDATE users SET withdrawn_earnings = withdrawn_earnings + ?, pending_earnings = GREATEST(0, pending_earnings - ?) WHERE id = ?
`, [amount, amount, userId])
const withdrawal = await prisma.withdrawals.create({
data: {
id: withdrawId,
user_id: userId,
amount: new Decimal(amount),
status: 'pending',
wechat_openid: openId,
wechat_id: wechatId,
created_at: new Date()
}
} catch (e) {
console.log('[Withdraw] 创建提现记录失败:', e)
}
})
return NextResponse.json({
success: true,
message: '提现申请已提交,正在审核中,通过后会自动到账您的微信零钱',
data: {
withdrawId,
amount,
account,
accountType: accountType === 'alipay' ? '支付宝' : '微信',
status: 'pending'
withdrawId: withdrawal.id,
amount: Number(withdrawal.amount),
accountType: '微信',
status: withdrawal.status
}
})