Files
soul-yongping/app/api/withdraw/route.ts

195 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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