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

157 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
* 基于 Universal_Payment_Module v4.0 设计
*
* POST /api/payment/wechat/notify
*/
import { type NextRequest, NextResponse } from "next/server"
import { PaymentFactory, SignatureError } from "@/lib/payment"
import { query } from "@/lib/db"
// 确保网关已注册
import "@/lib/payment/wechat"
export async function POST(request: NextRequest) {
try {
// 获取XML原始数据
const xmlData = await request.text()
console.log("[Wechat Notify] 收到回调:", xmlData.slice(0, 200))
// 创建微信支付网关
const gateway = PaymentFactory.create("wechat_native")
try {
// 解析并验证回调数据
const notifyResult = gateway.parseNotify(xmlData)
if (notifyResult.status === "paid") {
console.log("[Wechat Notify] 支付成功:", {
tradeSn: notifyResult.tradeSn,
platformSn: notifyResult.platformSn,
amount: notifyResult.payAmount / 100, // 转换为元
payTime: notifyResult.payTime,
})
// === ✅ 1. 更新订单状态 ===
try {
// 通过 transaction_id 查找订单
const orderRows = await query(`
SELECT id, user_id, amount, product_type, product_id
FROM orders
WHERE transaction_id = ? AND status = 'created'
LIMIT 1
`, [notifyResult.tradeSn]) as any[]
if (orderRows.length === 0) {
console.error('[Wechat Notify] ❌ 订单不存在或已处理:', notifyResult.tradeSn)
} else {
const order = orderRows[0]
const orderId = order.id
const userId = order.user_id
const amount = parseFloat(order.amount)
const productType = order.product_type
const productId = order.product_id
// 更新订单状态为已支付
await query(`
UPDATE orders
SET status = 'paid',
pay_time = ?,
updated_at = NOW()
WHERE id = ?
`, [notifyResult.payTime, orderId])
console.log('[Wechat Notify] ✅ 订单状态已更新:', { orderId, status: 'paid' })
// === ✅ 2. 解锁内容/开通权限 ===
if (productType === 'fullbook') {
// 购买全书
await query('UPDATE users SET has_full_book = 1 WHERE id = ?', [userId])
console.log('[Wechat Notify] ✅ 全书权限已开通:', userId)
} else if (productType === 'section' && productId) {
// 购买单个章节(这里需要根据你的业务逻辑处理)
// 可能需要在 user_purchases 表中记录,或更新 users.purchased_sections
console.log('[Wechat Notify] ✅ 章节权限已开通:', { userId, sectionId: productId })
}
// === ✅ 3. 分配佣金(如果有推荐人) ===
try {
// 查询用户的推荐人(从 referral_bindings
const userRows = await query(`
SELECT u.id, rb.referrer_id, rb.status
FROM users u
LEFT JOIN referral_bindings rb ON rb.referee_id = u.id AND rb.status = 'active' AND rb.expiry_date > NOW()
WHERE u.id = ?
LIMIT 1
`, [userId]) as any[]
if (userRows.length > 0 && userRows[0].referrer_id) {
const referrerId = userRows[0].referrer_id
const commissionRate = 0.9 // 90% 佣金比例
const commissionAmount = parseFloat((amount * commissionRate).toFixed(2))
// 更新推荐人的 pending_earnings
await query(`
UPDATE users
SET pending_earnings = pending_earnings + ?
WHERE id = ?
`, [commissionAmount, referrerId])
// 更新绑定状态为已转化
await query(`
UPDATE referral_bindings
SET status = 'converted',
conversion_date = NOW(),
commission_amount = ?
WHERE referee_id = ? AND status = 'active'
`, [commissionAmount, userId])
console.log('[Wechat Notify] ✅ 佣金已分配:', {
referrerId,
commissionAmount,
orderId
})
} else {
console.log('[Wechat Notify] 该用户无推荐人,无需分配佣金')
}
} catch (commErr) {
console.error('[Wechat Notify] ❌ 分配佣金失败:', commErr)
// 不中断主流程
}
}
} catch (error) {
console.error('[Wechat Notify] ❌ 订单处理失败:', error)
// 不中断,继续返回成功响应给微信(避免重复回调)
}
} else {
console.log("[Wechat Notify] 支付失败:", notifyResult)
}
// 返回成功响应
return new NextResponse(gateway.successResponse(), {
headers: { "Content-Type": "application/xml" },
})
} catch (error) {
if (error instanceof SignatureError) {
console.error("[Wechat Notify] 签名验证失败")
return new NextResponse(gateway.failResponse(), {
headers: { "Content-Type": "application/xml" },
})
}
throw error
}
} catch (error) {
console.error("[Wechat Notify] 处理失败:", error)
// 返回失败响应
const failXml = '<xml><return_code><![CDATA[FAIL]]></return_code><return_msg><![CDATA[系统错误]]></return_msg></xml>'
return new NextResponse(failXml, {
headers: { "Content-Type": "application/xml" },
})
}
}