2026-01-25 19:37:59 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 提现API
|
2026-01-29 12:44:29 +08:00
|
|
|
|
* 用户提现到微信零钱或支付宝
|
2026-01-25 19:37:59 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
2026-02-05 18:45:28 +08:00
|
|
|
|
import { query, getConfig } from '@/lib/db'
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-01-29 13:04:38 +08:00
|
|
|
|
// 确保提现表存在
|
|
|
|
|
|
async function ensureWithdrawalsTable() {
|
|
|
|
|
|
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),
|
2026-02-06 12:29:56 +08:00
|
|
|
|
status ENUM('pending', 'processing', 'success', 'failed') DEFAULT 'pending',
|
|
|
|
|
|
wechat_openid VARCHAR(100),
|
|
|
|
|
|
transaction_id VARCHAR(100),
|
|
|
|
|
|
error_message VARCHAR(500),
|
2026-01-29 13:04:38 +08:00
|
|
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
2026-02-06 12:29:56 +08:00
|
|
|
|
processed_at TIMESTAMP NULL,
|
2026-01-29 13:04:38 +08:00
|
|
|
|
INDEX idx_user_id (user_id),
|
|
|
|
|
|
INDEX idx_status (status)
|
|
|
|
|
|
)
|
|
|
|
|
|
`)
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log('[Withdraw] 表已存在或创建失败')
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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-05 18:45:28 +08:00
|
|
|
|
// 读取最低提现门槛
|
|
|
|
|
|
let minWithdrawAmount = 10 // 默认值
|
|
|
|
|
|
try {
|
|
|
|
|
|
const config = await getConfig('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 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-29 13:04:38 +08:00
|
|
|
|
// 确保表存在
|
|
|
|
|
|
await ensureWithdrawalsTable()
|
|
|
|
|
|
|
2026-01-29 12:44:29 +08:00
|
|
|
|
// 查询用户信息
|
2026-01-25 19:37:59 +08:00
|
|
|
|
const users = await query('SELECT * FROM users WHERE id = ?', [userId]) as any[]
|
2026-01-29 12:44:29 +08:00
|
|
|
|
if (!users || users.length === 0) {
|
|
|
|
|
|
return NextResponse.json({ success: false, message: '用户不存在' }, { status: 404 })
|
2026-01-25 19:37:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const user = users[0]
|
|
|
|
|
|
|
2026-01-31 17:39:21 +08:00
|
|
|
|
// 微信零钱提现需要 open_id(小程序/公众号登录获得)
|
|
|
|
|
|
const openId = user.open_id || ''
|
2026-01-29 13:04:38 +08:00
|
|
|
|
const wechatId = user.wechat || user.wechat_id || ''
|
|
|
|
|
|
const alipayId = user.alipay || ''
|
|
|
|
|
|
|
2026-01-31 17:39:21 +08:00
|
|
|
|
if (!openId && !alipayId) {
|
2026-01-29 12:44:29 +08:00
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
2026-01-31 17:39:21 +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 11:06:36 +08:00
|
|
|
|
// ✅ 修正:从 orders 表查询累计佣金(与前端逻辑一致)
|
|
|
|
|
|
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 {
|
|
|
|
|
|
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'
|
2026-01-29 13:04:38 +08:00
|
|
|
|
`, [userId]) as any[]
|
2026-02-06 11:06:36 +08:00
|
|
|
|
|
|
|
|
|
|
const totalAmount = parseFloat(ordersResult[0]?.total_amount || 0)
|
|
|
|
|
|
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) {
|
|
|
|
|
|
// 如果表不存在,收益为0
|
2026-02-06 11:06:36 +08:00
|
|
|
|
console.log('[Withdraw] 查询收益失败,可能表不存在:', e)
|
2026-01-29 13:04:38 +08:00
|
|
|
|
}
|
2026-01-29 12:44:29 +08:00
|
|
|
|
|
|
|
|
|
|
// 查询已提现金额
|
2026-02-06 11:06:36 +08:00
|
|
|
|
let withdrawnEarnings = 0
|
2026-01-29 13:04:38 +08:00
|
|
|
|
try {
|
2026-02-06 11:06:36 +08:00
|
|
|
|
withdrawnEarnings = parseFloat(user.withdrawn_earnings) || 0
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log('[Withdraw] 读取已提现金额失败:', e)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查询待审核提现金额
|
|
|
|
|
|
let pendingWithdrawAmount = 0
|
|
|
|
|
|
try {
|
|
|
|
|
|
const pendingResult = await query(`
|
|
|
|
|
|
SELECT COALESCE(SUM(amount), 0) as pending_amount
|
2026-01-29 13:04:38 +08:00
|
|
|
|
FROM withdrawals
|
2026-02-06 11:06:36 +08:00
|
|
|
|
WHERE user_id = ? AND status = 'pending'
|
2026-01-29 13:04:38 +08:00
|
|
|
|
`, [userId]) as any[]
|
2026-02-06 11:06:36 +08:00
|
|
|
|
pendingWithdrawAmount = parseFloat(pendingResult[0]?.pending_amount || 0)
|
2026-01-29 13:04:38 +08:00
|
|
|
|
} catch (e) {
|
2026-02-06 11:06:36 +08:00
|
|
|
|
console.log('[Withdraw] 查询待审核金额失败:', e)
|
2026-01-29 13:04:38 +08:00
|
|
|
|
}
|
2026-01-25 19:37:59 +08:00
|
|
|
|
|
2026-02-06 11:06:36 +08:00
|
|
|
|
// ✅ 修正:可提现金额 = 累计佣金 - 已提现金额 - 待审核金额(三元素完整校验)
|
|
|
|
|
|
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)
|
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-01-31 17:39:21 +08:00
|
|
|
|
// 创建提现记录(微信零钱需保存 wechat_openid 供后台批准时调用商家转账到零钱)
|
2026-01-29 12:44:29 +08:00
|
|
|
|
const withdrawId = `W${Date.now()}`
|
2026-01-29 13:04:38 +08:00
|
|
|
|
const accountType = alipayId ? 'alipay' : 'wechat'
|
|
|
|
|
|
const account = alipayId || wechatId
|
2026-01-29 12:44:29 +08:00
|
|
|
|
|
2026-01-29 13:04:38 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await query(`
|
2026-01-31 17:39:21 +08:00
|
|
|
|
INSERT INTO withdrawals (id, user_id, amount, status, wechat_openid, created_at)
|
|
|
|
|
|
VALUES (?, ?, ?, 'pending', ?, NOW())
|
|
|
|
|
|
`, [withdrawId, userId, amount, accountType === 'wechat' ? openId : null])
|
2026-01-29 13:04:38 +08:00
|
|
|
|
|
2026-01-31 17:39:21 +08:00
|
|
|
|
// 微信零钱由后台批准时调用「商家转账到零钱」;支付宝/无 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])
|
|
|
|
|
|
}
|
2026-01-29 13:04:38 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.log('[Withdraw] 创建提现记录失败:', e)
|
|
|
|
|
|
}
|
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-01-29 12:44:29 +08:00
|
|
|
|
withdrawId,
|
|
|
|
|
|
amount,
|
2026-01-29 13:04:38 +08:00
|
|
|
|
account,
|
2026-02-06 11:06:36 +08:00
|
|
|
|
accountType: accountType === 'alipay' ? '支付宝' : '微信',
|
|
|
|
|
|
status: 'pending'
|
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 })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|