103 lines
3.4 KiB
TypeScript
103 lines
3.4 KiB
TypeScript
/**
|
||
* 微信支付 - 商家转账到零钱 结果通知
|
||
* 文档: 开发文档/提现功能完整技术文档.md
|
||
* 支持:processing(原批量转账)、pending_confirm(用户确认模式)终态后更新为 success 并增加用户已提现
|
||
*/
|
||
|
||
import { NextRequest, NextResponse } from 'next/server'
|
||
import { decryptResource } from '@/lib/wechat-transfer'
|
||
import { prisma } from '@/lib/prisma'
|
||
|
||
const cfg = {
|
||
apiV3Key: process.env.WECHAT_API_V3_KEY || process.env.WECHAT_MCH_KEY || '',
|
||
}
|
||
|
||
export async function POST(request: NextRequest) {
|
||
try {
|
||
const rawBody = await request.text()
|
||
const data = JSON.parse(rawBody) as {
|
||
event_type?: string
|
||
resource?: { ciphertext: string; nonce: string; associated_data: string }
|
||
}
|
||
if (data.event_type !== 'MCHTRANSFER.BILL.FINISHED' || !data.resource) {
|
||
return NextResponse.json({ code: 'SUCCESS' })
|
||
}
|
||
const { ciphertext, nonce, associated_data } = data.resource
|
||
const decrypted = decryptResource(
|
||
ciphertext,
|
||
nonce,
|
||
associated_data,
|
||
cfg.apiV3Key
|
||
) as { out_bill_no?: string; state?: string; transfer_bill_no?: string }
|
||
const outBillNo = decrypted.out_bill_no
|
||
const state = decrypted.state
|
||
const transferBillNo = decrypted.transfer_bill_no || ''
|
||
if (!outBillNo) {
|
||
return NextResponse.json({ code: 'SUCCESS' })
|
||
}
|
||
|
||
const w = await prisma.withdrawals.findUnique({
|
||
where: { id: outBillNo },
|
||
select: { id: true, user_id: true, amount: true, status: true },
|
||
})
|
||
if (!w) {
|
||
return NextResponse.json({ code: 'SUCCESS' })
|
||
}
|
||
// 仅处理「处理中」或「待用户确认」的终态回调
|
||
if (w.status !== 'processing' && w.status !== 'pending_confirm') {
|
||
return NextResponse.json({ code: 'SUCCESS' })
|
||
}
|
||
|
||
const amount = Number(w.amount)
|
||
|
||
if (state === 'SUCCESS') {
|
||
await prisma.$transaction(async (tx) => {
|
||
await tx.withdrawals.update({
|
||
where: { id: outBillNo },
|
||
data: {
|
||
status: 'success',
|
||
processed_at: new Date(),
|
||
transaction_id: transferBillNo,
|
||
},
|
||
})
|
||
const user = await tx.users.findUnique({
|
||
where: { id: w.user_id },
|
||
select: { withdrawn_earnings: true, pending_earnings: true },
|
||
})
|
||
const curWithdrawn = Number(user?.withdrawn_earnings ?? 0)
|
||
const curPending = Number(user?.pending_earnings ?? 0)
|
||
await tx.users.update({
|
||
where: { id: w.user_id },
|
||
data: {
|
||
withdrawn_earnings: curWithdrawn + amount,
|
||
pending_earnings: Math.max(0, curPending - amount),
|
||
},
|
||
})
|
||
})
|
||
} else {
|
||
await prisma.withdrawals.update({
|
||
where: { id: outBillNo },
|
||
data: {
|
||
status: 'failed',
|
||
processed_at: new Date(),
|
||
error_message: state || '转账失败',
|
||
},
|
||
})
|
||
const user = await prisma.users.findUnique({
|
||
where: { id: w.user_id },
|
||
select: { pending_earnings: true },
|
||
})
|
||
const curPending = Number(user?.pending_earnings ?? 0)
|
||
await prisma.users.update({
|
||
where: { id: w.user_id },
|
||
data: { pending_earnings: curPending + amount },
|
||
})
|
||
}
|
||
|
||
return NextResponse.json({ code: 'SUCCESS' })
|
||
} catch (e) {
|
||
console.error('[WechatTransferNotify]', e)
|
||
return NextResponse.json({ code: 'FAIL', message: '处理失败' }, { status: 500 })
|
||
}
|
||
}
|