2026-01-25 19:37:59 +08:00
|
|
|
|
/**
|
2026-02-06 18:34:02 +08:00
|
|
|
|
* 提现API - 使用 Prisma ORM
|
|
|
|
|
|
* 用户提现到微信零钱
|
|
|
|
|
|
*
|
|
|
|
|
|
* Prisma 优势:
|
|
|
|
|
|
* - 完全类型安全
|
|
|
|
|
|
* - 自动防SQL注入
|
|
|
|
|
|
* - 简化复杂查询
|
2026-01-25 19:37:59 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
2026-02-06 18:34:02 +08:00
|
|
|
|
import { prisma } from '@/lib/prisma'
|
|
|
|
|
|
import { Decimal } from '@/lib/generated/prisma/runtime/library'
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 读取系统配置(使用 Prisma)
|
|
|
|
|
|
async function getPrismaConfig(key: string): Promise<any> {
|
2026-01-29 13:04:38 +08:00
|
|
|
|
try {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
const config = await prisma.system_config.findUnique({
|
|
|
|
|
|
where: { config_key: key }
|
|
|
|
|
|
})
|
|
|
|
|
|
return config?.config_value
|
2026-01-29 13:04:38 +08:00
|
|
|
|
} catch (e) {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
console.warn(`[Config] 读取配置 ${key} 失败:`, e)
|
|
|
|
|
|
return null
|
2026-01-29 13:04:38 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-25 19:37:59 +08:00
|
|
|
|
export async function POST(request: NextRequest) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const body = await request.json()
|
|
|
|
|
|
const { userId, amount } = body
|
|
|
|
|
|
|
|
|
|
|
|
if (!userId) {
|
2026-01-29 12:44:29 +08:00
|
|
|
|
return NextResponse.json({ success: false, message: '缺少用户ID' }, { status: 400 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!amount || amount <= 0) {
|
|
|
|
|
|
return NextResponse.json({ success: false, message: '提现金额无效' }, { status: 400 })
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 1. 读取最低提现门槛
|
2026-02-05 18:45:28 +08:00
|
|
|
|
let minWithdrawAmount = 10 // 默认值
|
|
|
|
|
|
try {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
const config = await getPrismaConfig('referral_config')
|
2026-02-05 18:45:28 +08:00
|
|
|
|
if (config?.minWithdrawAmount) {
|
|
|
|
|
|
minWithdrawAmount = Number(config.minWithdrawAmount)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn('[Withdraw] 读取配置失败,使用默认值 10 元')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查最低提现门槛
|
|
|
|
|
|
if (amount < minWithdrawAmount) {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: `最低提现金额为 ¥${minWithdrawAmount},当前 ¥${amount}`
|
|
|
|
|
|
}, { status: 400 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 2. 查询用户信息(Prisma 保证类型安全)
|
|
|
|
|
|
const user = await prisma.users.findUnique({
|
|
|
|
|
|
where: { id: userId },
|
|
|
|
|
|
select: {
|
|
|
|
|
|
id: true,
|
|
|
|
|
|
open_id: true,
|
|
|
|
|
|
wechat_id: true,
|
|
|
|
|
|
withdrawn_earnings: true
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-01-29 13:04:38 +08:00
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
if (!user) {
|
2026-01-29 12:44:29 +08:00
|
|
|
|
return NextResponse.json({ success: false, message: '用户不存在' }, { status: 404 })
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-31 17:39:21 +08:00
|
|
|
|
const openId = user.open_id || ''
|
2026-02-06 18:34:02 +08:00
|
|
|
|
if (!openId) {
|
2026-01-29 12:44:29 +08:00
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
2026-02-06 18:34:02 +08:00
|
|
|
|
message: '提现到微信零钱需先使用微信登录',
|
2026-01-29 12:44:29 +08:00
|
|
|
|
needBind: true
|
2026-01-29 13:04:38 +08:00
|
|
|
|
})
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 提现需绑定微信号(用于后台展示收款账号)
|
|
|
|
|
|
const wechatId = user.wechat_id?.trim() || ''
|
|
|
|
|
|
if (!wechatId) {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: '请先到「设置」中绑定微信号后再提现',
|
|
|
|
|
|
needBindWechat: true
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 计算累计佣金(从 orders 表查询)
|
2026-02-06 11:06:36 +08:00
|
|
|
|
let totalCommission = 0
|
2026-01-29 13:04:38 +08:00
|
|
|
|
try {
|
2026-02-06 11:06:36 +08:00
|
|
|
|
// 读取分成比例
|
|
|
|
|
|
let distributorShare = 0.9 // 默认90%
|
|
|
|
|
|
try {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
const config = await getPrismaConfig('referral_config')
|
2026-02-06 11:06:36 +08:00
|
|
|
|
if (config?.distributorShare) {
|
|
|
|
|
|
distributorShare = Number(config.distributorShare)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn('[Withdraw] 读取分成比例失败,使用默认值 90%')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 使用 Prisma 聚合查询
|
|
|
|
|
|
const ordersResult = await prisma.orders.aggregate({
|
|
|
|
|
|
where: {
|
|
|
|
|
|
referrer_id: userId,
|
|
|
|
|
|
status: 'paid'
|
|
|
|
|
|
},
|
|
|
|
|
|
_sum: {
|
|
|
|
|
|
amount: true
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2026-02-06 11:06:36 +08:00
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
const totalAmount = Number(ordersResult._sum.amount || 0)
|
2026-02-06 11:06:36 +08:00
|
|
|
|
totalCommission = totalAmount * distributorShare
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[Withdraw] 佣金计算:')
|
|
|
|
|
|
console.log('- 订单总金额:', totalAmount)
|
|
|
|
|
|
console.log('- 分成比例:', distributorShare * 100 + '%')
|
|
|
|
|
|
console.log('- 累计佣金:', totalCommission)
|
2026-01-29 13:04:38 +08:00
|
|
|
|
} catch (e) {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
console.log('[Withdraw] 查询收益失败:', e)
|
2026-02-06 11:06:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 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)
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 5. 计算可提现金额(不低于 0)
|
|
|
|
|
|
const availableAmount = Math.max(0, totalCommission - withdrawnEarnings - pendingWithdrawAmount)
|
2026-02-06 11:06:36 +08:00
|
|
|
|
|
|
|
|
|
|
console.log('[Withdraw] 提现验证(完整版):')
|
2026-02-06 18:34:02 +08:00
|
|
|
|
console.log('- 累计佣金:', totalCommission)
|
|
|
|
|
|
console.log('- 已提现金额:', withdrawnEarnings)
|
|
|
|
|
|
console.log('- 待审核金额:', pendingWithdrawAmount)
|
|
|
|
|
|
console.log('- 可提现金额 =', availableAmount)
|
|
|
|
|
|
console.log('- 申请提现金额:', amount)
|
2026-01-29 12:44:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (amount > availableAmount) {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
2026-02-06 11:06:36 +08:00
|
|
|
|
message: `可提现金额不足。当前可提现 ¥${availableAmount.toFixed(2)}(累计 ¥${totalCommission.toFixed(2)} - 已提现 ¥${withdrawnEarnings.toFixed(2)} - 待审核 ¥${pendingWithdrawAmount.toFixed(2)})`
|
2026-01-29 13:04:38 +08:00
|
|
|
|
})
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
// 7. 创建提现记录(使用 Prisma,无SQL注入风险)
|
2026-01-29 12:44:29 +08:00
|
|
|
|
const withdrawId = `W${Date.now()}`
|
|
|
|
|
|
|
2026-02-06 18:34:02 +08:00
|
|
|
|
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()
|
2026-01-31 17:39:21 +08:00
|
|
|
|
}
|
2026-02-06 18:34:02 +08:00
|
|
|
|
})
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
2026-02-06 11:06:36 +08:00
|
|
|
|
message: '提现申请已提交,正在审核中,通过后会自动到账您的微信零钱',
|
2026-01-25 19:37:59 +08:00
|
|
|
|
data: {
|
2026-02-06 18:34:02 +08:00
|
|
|
|
withdrawId: withdrawal.id,
|
|
|
|
|
|
amount: Number(withdrawal.amount),
|
|
|
|
|
|
accountType: '微信',
|
|
|
|
|
|
status: withdrawal.status
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2026-01-29 12:44:29 +08:00
|
|
|
|
console.error('[Withdraw] Error:', error)
|
2026-01-25 19:37:59 +08:00
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
2026-01-29 13:04:38 +08:00
|
|
|
|
message: '提现失败: ' + String(error)
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}, { status: 500 })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|