2026-01-21 15:49:12 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 微信支付回调通知 API
|
|
|
|
|
|
* 基于 Universal_Payment_Module v4.0 设计
|
|
|
|
|
|
*
|
|
|
|
|
|
* POST /api/payment/wechat/notify
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2026-01-09 11:58:08 +08:00
|
|
|
|
import { type NextRequest, NextResponse } from "next/server"
|
2026-01-21 15:49:12 +08:00
|
|
|
|
import { PaymentFactory, SignatureError } from "@/lib/payment"
|
2026-02-04 21:36:26 +08:00
|
|
|
|
import { query } from "@/lib/db"
|
2026-01-21 15:49:12 +08:00
|
|
|
|
|
|
|
|
|
|
// 确保网关已注册
|
|
|
|
|
|
import "@/lib/payment/wechat"
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
|
|
|
|
|
export async function POST(request: NextRequest) {
|
|
|
|
|
|
try {
|
2026-01-21 15:49:12 +08:00
|
|
|
|
// 获取XML原始数据
|
2026-01-09 11:58:08 +08:00
|
|
|
|
const xmlData = await request.text()
|
|
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
console.log("[Wechat Notify] 收到回调:", xmlData.slice(0, 200))
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
// 创建微信支付网关
|
|
|
|
|
|
const gateway = PaymentFactory.create("wechat_native")
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
try {
|
|
|
|
|
|
// 解析并验证回调数据
|
|
|
|
|
|
const notifyResult = gateway.parseNotify(xmlData)
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
if (notifyResult.status === "paid") {
|
|
|
|
|
|
console.log("[Wechat Notify] 支付成功:", {
|
|
|
|
|
|
tradeSn: notifyResult.tradeSn,
|
|
|
|
|
|
platformSn: notifyResult.platformSn,
|
|
|
|
|
|
amount: notifyResult.payAmount / 100, // 转换为元
|
|
|
|
|
|
payTime: notifyResult.payTime,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-02-04 21:36:26 +08:00
|
|
|
|
// === ✅ 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[]
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-02-04 21:36:26 +08:00
|
|
|
|
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
|
2026-01-09 11:58:08 +08:00
|
|
|
|
|
2026-02-04 21:36:26 +08:00
|
|
|
|
// 更新订单状态为已支付
|
|
|
|
|
|
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 {
|
2026-02-05 21:08:28 +08:00
|
|
|
|
// 查询用户的推荐人(从 referral_bindings)
|
2026-02-04 21:36:26 +08:00
|
|
|
|
const userRows = await query(`
|
2026-02-05 21:08:28 +08:00
|
|
|
|
SELECT u.id, rb.referrer_id, rb.status
|
2026-02-04 21:36:26 +08:00
|
|
|
|
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)
|
|
|
|
|
|
// 不中断,继续返回成功响应给微信(避免重复回调)
|
|
|
|
|
|
}
|
2026-01-21 15:49:12 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
console.log("[Wechat Notify] 支付失败:", notifyResult)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回成功响应
|
|
|
|
|
|
return new NextResponse(gateway.successResponse(), {
|
|
|
|
|
|
headers: { "Content-Type": "application/xml" },
|
2026-01-09 11:58:08 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
2026-01-21 15:49:12 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
if (error instanceof SignatureError) {
|
|
|
|
|
|
console.error("[Wechat Notify] 签名验证失败")
|
|
|
|
|
|
return new NextResponse(gateway.failResponse(), {
|
|
|
|
|
|
headers: { "Content-Type": "application/xml" },
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
throw error
|
2026-01-09 11:58:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2026-01-21 15:49:12 +08:00
|
|
|
|
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" },
|
|
|
|
|
|
})
|
2026-01-09 11:58:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|