import { NextRequest, NextResponse } from 'next/server' import { getServerSession } from 'next-auth/next' import { authOptions } from '@/lib/authOptions' import { db } from '@/lib/db' import crypto from 'crypto' // 产品配置 const PRODUCTS = { standard: { name: 'FLNXI标准版', price: 599, }, deployment: { name: '远程部署服务', price: 299, }, custom: { name: '定制版咨询', price: 0, // 需要沟通报价 }, test: { name: '支付测试', price: 0.1, // 测试用,金额0.1元 }, } // 生成订单号 function generateOrderNo(): string { const timestamp = Date.now() const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0') return `FLNXI${timestamp}${random}` } // 生成嘟噜支付MD5签名 function generateSign(params: Record, key: string): string { // 1. 获取所有非空请求参数,剔除sign和sign_type字段 const filtered: Record = {} const sortedKeys = Object.keys(params).sort() // 按照ASCII码递增排序 sortedKeys.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' ) { // 确保所有值都是字符串(不trim,保持原始值) filtered[k] = String(params[k]) } }) // 2. 将排序后的参数组合成"参数=参数值"的格式,用&连接 // 注意:签名时使用原始参数值(不编码),因为表单提交时浏览器会自动处理编码 // 但支付平台验证签名时使用的是收到的原始值(浏览器解码后的值) const sortedFilteredKeys = Object.keys(filtered).sort() // 使用原始参数值(不编码)进行签名 const signStr = sortedFilteredKeys .map((k) => `${k}=${filtered[k]}`) .join('&') // 3. 在末尾加上密钥(易支付常见方式:直接拼接密钥,没有&key=前缀) const signStrWithKey = `${signStr}${key}` // 对比:如果使用 &key=密钥 的方式 const signStrWithKeyPrefix = `${signStr}&key=${key}` // 详细调试日志 console.log('\n========== 签名参数调试信息 ==========') console.log('📋 原始参数:') Object.keys(params).sort().forEach(k => { console.log(` ${k} = ${params[k]} (类型: ${typeof params[k]})`) }) console.log('\n✅ 参与签名的参数(已过滤和排序):') sortedFilteredKeys.forEach((k, index) => { const encodedValue = encodeURIComponent(filtered[k]) console.log(` ${index + 1}. ${k} = "${filtered[k]}"`) if (encodedValue !== filtered[k]) { console.log(` (URL编码后: "${encodedValue}")`) } }) console.log('\n🔗 待签名字符串(参数值不进行URL编码):') console.log(` ${signStr}`) console.log('\n🔑 密钥信息:') console.log(` 长度: ${key.length} 字符`) console.log(` 前3个字符: ${key.substring(0, 3)}...`) console.log(` 后3个字符: ...${key.substring(key.length - 3)}`) console.log('\n🔐 完整签名字符串(直接拼接密钥,无&key=前缀):') console.log(` ${signStrWithKey}`) console.log(` 长度: ${signStrWithKey.length} 字符`) console.log(' 格式:sign = md5(a=b&c=d&e=f + KEY)') // 4. MD5加密 // 根据官方文档:sign = md5(a=b&c=d&e=f + KEY),MD5结果为小写 // 方式A:直接拼接密钥(官方文档标准方式) const signatureA = crypto.createHash('md5').update(signStrWithKey, 'utf8').digest('hex') // 小写 // 方式B:使用&key=前缀(对比) const signatureB = crypto.createHash('md5').update(signStrWithKeyPrefix, 'utf8').digest('hex') // 小写 // 默认使用方式A(直接拼接密钥,官方文档标准方式) const signature = signatureA console.log('\n📝 生成的签名(根据官方文档):') console.log(' [方式A - 直接拼接密钥(官方文档标准方式)] (当前使用):') console.log(` 签名字符串: ${signStrWithKey}`) console.log(` 签名(小写): ${signatureA}`) console.log(' [方式B - 使用&key=前缀(对比)]:') console.log(` 签名字符串: ${signStrWithKeyPrefix}`) console.log(` 签名(小写): ${signatureB}`) console.log('\n✅ 当前使用方式A(直接拼接密钥,MD5结果为小写)') console.log(' 根据官方文档:sign = md5(a=b&c=d&e=f + KEY),MD5结果为小写') console.log('========================================\n') return signature } export async function POST(request: NextRequest) { try { // 验证登录状态 const session = await getServerSession(authOptions) if (!session || !session.user?.id) { return NextResponse.json( { message: '请先登录' }, { status: 401 } ) } const body = await request.json() const { productType, paymentMethod = 'alipay' } = body // 验证产品类型 if (!productType || !PRODUCTS[productType as keyof typeof PRODUCTS]) { return NextResponse.json( { message: '无效的产品类型' }, { status: 400 } ) } const product = PRODUCTS[productType as keyof typeof PRODUCTS] // 定制版需要特殊处理 if (productType === 'custom') { return NextResponse.json( { message: '定制版请联系客服咨询报价', redirect: '#contact' }, { status: 200 } ) } // 检查嘟噜支付配置 const epayPid = process.env.EPAY_PID const epayKey = process.env.EPAY_KEY // MD5密钥 const epayApiUrl = process.env.EPAY_API_URL || 'https://www.dulupay.com/api/pay' if (!epayPid || !epayKey || !epayApiUrl) { return NextResponse.json( { message: '支付系统未配置,请联系管理员' }, { status: 500 } ) } // 生成订单号 const orderNo = generateOrderNo() const userId = parseInt(session.user.id) // 创建订单 const order = await db.createOrder( orderNo, userId, productType, product.name, product.price ) // 获取回调地址 const baseUrl = process.env.NEXTAUTH_URL || request.headers.get('origin') || 'http://localhost:23000' const notifyUrl = `${baseUrl}/api/payment/callback` const returnUrl = `${baseUrl}/payment/success?order_no=${orderNo}` // 构建嘟噜支付参数(根据官方文档) // 注意:参数名需要与易支付平台要求的一致 const epayParams: Record = { pid: String(epayPid), // 商户ID type: String(paymentMethod), // 支付方式:alipay, wxpay, qqpay等 out_trade_no: orderNo, // 商户订单号 notify_url: notifyUrl, // 异步通知地址 return_url: returnUrl, // 同步跳转地址 name: product.name, // 商品名称 money: product.price.toFixed(2), // 金额(保留2位小数) } // 调试:输出参数名对比 console.log('\n📝 参数名检查(易支付标准参数名,根据官方文档):') console.log(' pid = 商户ID(必填)') console.log(' type = 支付方式(可选)') console.log(' out_trade_no = 商户订单号(必填)') console.log(' notify_url = 异步通知地址(必填)') console.log(' return_url = 同步跳转地址(必填)') console.log(' name = 商品名称(必填)') console.log(' money = 金额(必填,保留2位小数)') console.log(' 注意:sign、sign_type、和空值不参与签名\n') // 生成MD5签名 const sign = generateSign(epayParams, epayKey) epayParams.sign = sign epayParams.sign_type = 'MD5' // 嘟噜支付使用MD5签名 console.log('✅ 创建支付订单:', { orderNo, userId, product: product.name, amount: product.price, }) console.log('📋 支付参数:', epayParams) // 输出完整的测试信息,方便对比 console.log('\n🔍 完整测试信息(可用于对比Go项目):') console.log('参数列表(按ASCII排序):') Object.keys(epayParams).sort().forEach(k => { console.log(` ${k} = "${epayParams[k]}"`) }) console.log('\n建议:') console.log('1. 查看Go项目实际发送给支付平台的参数(网络抓包或日志)') console.log('2. 对比参数名是否一致') console.log('3. 对比参数值是否一致') console.log('4. 查看Go项目实际生成的签名') console.log('5. 如果Go项目能正常工作,请提供Go项目的实际参数和签名用于对比') console.log('6. 或联系嘟噜支付技术支持,获取签名示例代码\n') // 返回支付参数,前端提交表单到嘟噜支付 return NextResponse.json({ success: true, orderNo, orderId: order.id, paymentUrl: epayApiUrl, paymentParams: epayParams, }) } catch (error) { console.error('创建支付订单错误:', error) return NextResponse.json( { message: '创建订单失败,请稍后重试' }, { status: 500 } ) } }