Files
soul-yongping/next-project/app/api/payment/wechat/transfer/notify/route.ts
2026-02-09 14:43:35 +08:00

103 lines
3.4 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.

/**
* 微信支付 - 商家转账到零钱 结果通知
* 文档: 开发文档/提现功能完整技术文档.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 })
}
}