Files
soul-yongping/next-project/app/api/admin/withdrawals/route.ts
2026-02-09 14:43:35 +08:00

223 lines
8.0 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
* GET: 查提现记录(包含用户信息、收款账号=微信号)
* PUT: 审批提现 = 调用微信打款 + 更新状态 + 更新用户已提现金额
*/
import { NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { requireAdminResponse } from '@/lib/admin-auth'
import { createTransferUserConfirm } from '@/lib/wechat-transfer'
// ========== GET查询提现记录带用户信息、收款账号=微信号)==========
export async function GET(request: Request) {
console.log('[Withdrawals] GET 开始')
try {
const authErr = requireAdminResponse(request)
if (authErr) return authErr
const withdrawalsData = await prisma.withdrawals.findMany({
take: 100,
orderBy: { created_at: 'desc' },
select: {
id: true,
user_id: true,
amount: true,
status: true,
created_at: true,
wechat_id: true,
}
})
const userIds = [...new Set(withdrawalsData.map(w => w.user_id))]
const users = await prisma.users.findMany({
where: { id: { in: userIds } },
select: { id: true, nickname: true, avatar: true, wechat_id: true }
})
const userMap = new Map(users.map(u => [u.id, u]))
const withdrawals = withdrawalsData.map(w => {
const user = userMap.get(w.user_id)
return {
id: w.id,
user_id: w.user_id,
user_name: user?.nickname || '未知用户',
userAvatar: user?.avatar,
amount: Number(w.amount),
status: w.status === 'success' ? 'completed' :
w.status === 'failed' ? 'rejected' :
w.status === 'pending_confirm' ? 'pending_confirm' :
w.status,
created_at: w.created_at,
method: 'wechat',
account: w.wechat_id || user?.wechat_id || '未绑定微信号',
}
})
return NextResponse.json({
success: true,
withdrawals,
stats: { total: withdrawals.length },
})
} catch (error: any) {
console.error('[Withdrawals] GET 失败:', error?.message)
return NextResponse.json(
{ success: false, error: '获取提现记录失败: ' + (error?.message || String(error)) },
{ status: 500 }
)
}
}
// ========== PUT审批提现使用 Prisma 事务)==========
export async function PUT(request: Request) {
const STEP = '[Withdrawals PUT]'
try {
console.log(STEP, '1. 开始')
const authErr = requireAdminResponse(request)
if (authErr) return authErr
const body = await request.json()
const { id, action, errorMessage, reason } = body
const rejectReason = errorMessage || reason || '管理员拒绝'
if (!id || !action) {
return NextResponse.json({ success: false, error: '缺少 id 或 action' }, { status: 400 })
}
console.log(STEP, '2. id/action 有效', id, action)
const withdrawal = await prisma.withdrawals.findUnique({
where: { id },
select: { id: true, user_id: true, amount: true, status: true, wechat_openid: true }
})
if (!withdrawal) {
return NextResponse.json({ success: false, error: '提现记录不存在' }, { status: 404 })
}
if (withdrawal.status !== 'pending') {
return NextResponse.json({ success: false, error: '该记录已处理,不可重复审批' }, { status: 400 })
}
const amount = Number(withdrawal.amount)
const userId = withdrawal.user_id
const openid = withdrawal.wechat_openid
if (action === 'approve') {
console.log(STEP, '3. 发起转账(用户确认模式)')
console.log(STEP, '审核传入: id=', id, 'userId=', userId, 'amount=', amount, 'openid=', openid ? `${openid.slice(0, 8)}...` : '(空)')
if (!openid) {
return NextResponse.json({
success: false,
error: '该提现记录无微信 openid无法打款请线下处理'
}, { status: 400 })
}
const amountFen = Math.round(amount * 100)
console.log(STEP, '调用 createTransferUserConfirm: outBillNo=', id, 'amountFen=', amountFen, 'openid 长度=', openid.length)
const transferResult = await createTransferUserConfirm({
openid,
amountFen,
outBillNo: id,
transferRemark: '提现',
})
if (!transferResult.success) {
console.error(STEP, '发起转账失败:', transferResult.errorCode, transferResult.errorMessage)
await prisma.withdrawals.update({
where: { id },
data: {
status: 'failed',
processed_at: new Date(),
error_message: transferResult.errorMessage || transferResult.errorCode || '发起失败'
}
})
return NextResponse.json({
success: false,
error: '发起转账失败: ' + (transferResult.errorMessage || transferResult.errorCode || '未知错误')
}, { status: 500 })
}
const state = transferResult.state || ''
const hasPackage = !!(transferResult.packageInfo && (state === 'WAIT_USER_CONFIRM' || state === 'TRANSFERING' || state === 'ACCEPTED' || state === 'PROCESSING'))
if (hasPackage && transferResult.packageInfo) {
console.log(STEP, '4. 待用户确认收款,保存 package_info')
await prisma.withdrawals.update({
where: { id },
data: {
status: 'pending_confirm',
transfer_bill_no: transferResult.transferBillNo || null,
package_info: transferResult.packageInfo,
transaction_id: transferResult.transferBillNo || undefined,
}
})
return NextResponse.json({
success: true,
message: '已发起转账,请通知用户在小程序「分销中心」或「我的」中点击「确认收款」完成到账'
})
}
if (state === 'SUCCESS') {
console.log(STEP, '4. 微信直接返回成功,更新数据库')
await prisma.$transaction(async (tx) => {
await tx.withdrawals.update({
where: { id },
data: {
status: 'success',
processed_at: new Date(),
transaction_id: transferResult.transferBillNo || undefined,
transfer_bill_no: transferResult.transferBillNo || undefined,
}
})
const user = await tx.users.findUnique({
where: { id: userId },
select: { withdrawn_earnings: true }
})
await tx.users.update({
where: { id: userId },
data: {
withdrawn_earnings: Number(user?.withdrawn_earnings || 0) + amount
}
})
})
return NextResponse.json({
success: true,
message: '已批准并打款成功,款项将到账用户微信零钱'
})
}
await prisma.withdrawals.update({
where: { id },
data: {
status: 'pending_confirm',
transfer_bill_no: transferResult.transferBillNo || null,
package_info: transferResult.packageInfo || null,
transaction_id: transferResult.transferBillNo || undefined,
}
})
return NextResponse.json({
success: true,
message: '已发起转账,请通知用户在小程序内「确认收款」完成到账'
})
}
if (action === 'reject') {
console.log(STEP, '5. 执行拒绝操作')
await prisma.withdrawals.update({
where: { id },
data: {
status: 'failed',
processed_at: new Date(),
error_message: rejectReason
}
})
console.log(STEP, '6. 拒绝完成')
return NextResponse.json({ success: true, message: '已拒绝该提现申请' })
}
return NextResponse.json({ success: false, error: '无效的 action' }, { status: 400 })
} catch (error: any) {
console.error(STEP, '!!! 崩溃 !!!', error?.message)
console.error(STEP, '堆栈:', error?.stack)
return NextResponse.json(
{ success: false, error: '审批操作失败: ' + (error?.message || String(error)) },
{ status: 500 }
)
}
}