import { NextRequest, NextResponse } from 'next/server' import { db } from '@/lib/db' import crypto from 'crypto' const LICENSE_API_URL = process.env.LICENSE_API_URL || 'http://45.249.208.118:9527/api/license/generate' const LICENSE_API_KEY = process.env.LICENSE_API_KEY // 验证嘟噜支付回调MD5签名 function verifySign(params: Record, key: string): boolean { const sign = params.sign if (!sign) return false // 1. 获取所有非空请求参数,剔除sign和sign_type字段 const filtered: Record = {} Object.keys(params) .sort() // 按照ASCII码递增排序 .forEach((k) => { // 排除空值、数组、字节类型参数,以及sign和sign_type字段 if ( k !== 'sign' && k !== 'sign_type' && params[k] !== '' && params[k] !== null && params[k] !== undefined && !Array.isArray(params[k]) && typeof params[k] !== 'object' ) { filtered[k] = params[k] } }) // 2. 将排序后的参数组合成"参数=参数值"的格式,用&连接 // 注意:参数值不要进行URL编码(根据官方文档) const signStr = Object.keys(filtered) .map((k) => `${k}=${filtered[k]}`) .join('&') // 3. 直接拼接密钥(无&key=前缀) // 根据官方文档:sign = md5(a=b&c=d&e=f + KEY) const signStrWithKey = `${signStr}${key}` // 4. MD5加密(结果为小写,根据官方文档) const calculatedSign = crypto.createHash('md5').update(signStrWithKey, 'utf8').digest('hex') return calculatedSign === sign.toLowerCase() } function parseCallbackData(raw?: string | null): Record { if (!raw) return {} try { return JSON.parse(raw) } catch (e) { return { raw } } } function mergeCallbackData(raw: string | null | undefined, patch: Record) { const base = parseCallbackData(raw) return JSON.stringify({ ...base, ...patch }) } // 映射本地商品类型到许可证类型(按文档:STANDARD / PRO) const productPlanMap: Record = { standard: 'STANDARD', test: 'STANDARD', } async function generateLicense(order: any) { if (!LICENSE_API_KEY) { console.warn('⚠️ LICENSE_API_KEY 未配置,跳过激活码生成') return null } const plan = productPlanMap[order.product_type] if (!plan) { console.log('ℹ️ 当前商品不需要生成激活码,product_type:', order.product_type) return null } const timestamp = Date.now() const signStr = `${order.order_no}${timestamp}${LICENSE_API_KEY}` const sign = crypto.createHash('md5').update(signStr, 'utf8').digest('hex') const user = await db.findUserById(order.user_id).catch(() => null) const payload: Record = { orderNo: order.order_no, plan, customerEmail: user?.email, customerName: user?.name, contactInfo: user?.email, timestamp, sign, } const res = await fetch(LICENSE_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': LICENSE_API_KEY, }, body: JSON.stringify(payload), }) let json: any = null try { json = await res.json() } catch (err) { console.error('❌ 激活码接口返回解析失败:', err) } if (!res.ok || !json?.success) { console.error('❌ 激活码生成失败:', { status: res.status, statusText: res.statusText, response: json, }) throw new Error(json?.error || res.statusText) } return { ...json.data, plan, requestedAt: new Date().toISOString(), } } export async function POST(request: NextRequest) { try { const formData = await request.formData() const params: Record = {} // 将FormData转换为对象 formData.forEach((value, key) => { params[key] = value.toString() }) // 也支持JSON格式 if (Object.keys(params).length === 0) { try { const json = await request.json() Object.assign(params, json) } catch { // 忽略JSON解析错误 } } console.log('📥 收到支付回调:', params) const epayKey = process.env.EPAY_KEY // MD5密钥 if (!epayKey) { console.error('❌ 嘟噜支付MD5密钥未配置') return new NextResponse('fail', { status: 500 }) } // 验证签名 if (!verifySign(params, epayKey)) { console.error('❌ 签名验证失败:', params) return new NextResponse('fail', { status: 400 }) } // 获取订单信息 const orderNo = params.out_trade_no const tradeNo = params.trade_no const tradeStatus = params.trade_status const paymentMethod = params.type || params.payment_method if (!orderNo) { console.error('❌ 订单号缺失') return new NextResponse('fail', { status: 400 }) } // 查找订单 const order = await db.findOrderByOrderNo(orderNo) if (!order) { console.error('❌ 订单不存在:', orderNo) return new NextResponse('fail', { status: 404 }) } // 检查订单状态 if (order.status === 'paid') { console.log('✅ 订单已支付,跳过处理:', orderNo) return new NextResponse('success') } // 验证支付状态 // 易支付通常 trade_status = 'TRADE_SUCCESS' 表示支付成功 if (tradeStatus === 'TRADE_SUCCESS' || params.status === 'success' || params.code === '1') { // 已有元数据 const existingMeta = parseCallbackData(order.callback_data) // 生成激活码(仅指定商品类型) let licenseData = existingMeta.license let licenseError = existingMeta.licenseError if (!licenseData) { try { licenseData = await generateLicense(order) } catch (err) { console.error('❌ 激活码生成异常:', err) licenseError = { message: (err as any)?.message || '激活码生成失败', at: new Date().toISOString(), } } } const mergedMeta = mergeCallbackData(order.callback_data, { payment: { receivedAt: new Date().toISOString(), tradeStatus, params, }, ...(licenseData ? { license: licenseData } : {}), ...(licenseError ? { licenseError } : {}), }) // 更新订单状态 await db.updateOrderStatus(orderNo, 'paid', tradeNo, paymentMethod, mergedMeta) console.log('✅ 订单支付成功:', { orderNo, tradeNo, amount: order.amount, paymentMethod, licenseGenerated: !!licenseData, }) // TODO: 这里可以添加其他业务逻辑,比如: // - 发送邮件通知 // - 更新用户权限 // - 记录日志等 return new NextResponse('success') } else { // 支付失败 await db.updateOrderStatus( orderNo, 'failed', tradeNo, paymentMethod, JSON.stringify(params) ) console.log('❌ 订单支付失败:', { orderNo, tradeStatus, params, }) return new NextResponse('success') // 即使失败也要返回SUCCESS,避免重复通知 } } catch (error) { console.error('处理支付回调错误:', error) return new NextResponse('FAIL', { status: 500 }) } } // 也支持GET请求(某些支付平台可能使用GET) export async function GET(request: NextRequest) { try { const searchParams = request.nextUrl.searchParams const params: Record = {} searchParams.forEach((value, key) => { params[key] = value }) console.log('📥 收到支付回调(GET):', params) const epayKey = process.env.EPAY_KEY // MD5密钥 if (!epayKey) { console.error('❌ 嘟噜支付MD5密钥未配置') return new NextResponse('fail', { status: 500 }) } // 验证签名 if (!verifySign(params, epayKey)) { console.error('❌ 签名验证失败:', params) return new NextResponse('fail', { status: 400 }) } // 获取订单信息 const orderNo = params.out_trade_no const tradeNo = params.trade_no const tradeStatus = params.trade_status const paymentMethod = params.type || params.payment_method if (!orderNo) { console.error('❌ 订单号缺失') return new NextResponse('fail', { status: 400 }) } // 查找订单 const order = await db.findOrderByOrderNo(orderNo) if (!order) { console.error('❌ 订单不存在:', orderNo) return new NextResponse('fail', { status: 404 }) } // 检查订单状态 if (order.status === 'paid') { console.log('✅ 订单已支付,跳过处理:', orderNo) return new NextResponse('success') } // 验证支付状态 if (tradeStatus === 'TRADE_SUCCESS' || params.status === 'success' || params.code === '1') { const existingMeta = parseCallbackData(order.callback_data) let licenseData = existingMeta.license if (!licenseData) { try { licenseData = await generateLicense(order) } catch (err) { console.error('❌ 激活码生成异常:', err) } } const mergedMeta = mergeCallbackData(order.callback_data, { payment: { receivedAt: new Date().toISOString(), tradeStatus, params, }, ...(licenseData ? { license: licenseData } : {}), }) // 更新订单状态 await db.updateOrderStatus(orderNo, 'paid', tradeNo, paymentMethod, mergedMeta) console.log('✅ 订单支付成功:', { orderNo, tradeNo, amount: order.amount, paymentMethod, }) return new NextResponse('success') } else { // 支付失败 await db.updateOrderStatus( orderNo, 'failed', tradeNo, paymentMethod, JSON.stringify(params) ) console.log('❌ 订单支付失败:', { orderNo, tradeStatus, params, }) return new NextResponse('success') } } catch (error) { console.error('处理支付回调错误:', error) return new NextResponse('FAIL', { status: 500 }) } }