2026-01-23 05:44:21 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 小程序支付API
|
|
|
|
|
|
* 对接真实微信支付接口
|
|
|
|
|
|
*
|
|
|
|
|
|
* 配置来源: 用户规则
|
|
|
|
|
|
* - 小程序AppID: wxb8bbb2b10dec74aa
|
|
|
|
|
|
* - 商户号: 1318592501
|
|
|
|
|
|
* - API密钥: wx3e31b068be59ddc131b068be59ddc2
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { NextResponse } from 'next/server'
|
|
|
|
|
|
import crypto from 'crypto'
|
2026-02-05 18:45:28 +08:00
|
|
|
|
import { query, getConfig } from '@/lib/db'
|
2026-01-23 05:44:21 +08:00
|
|
|
|
|
2026-01-25 09:57:21 +08:00
|
|
|
|
// 微信支付配置 - 2026-01-25 更新
|
|
|
|
|
|
// 小程序支付绑定状态: 审核中(申请单ID: 201554696918)
|
2026-01-23 05:44:21 +08:00
|
|
|
|
const WECHAT_PAY_CONFIG = {
|
|
|
|
|
|
appId: 'wxb8bbb2b10dec74aa', // 小程序AppID
|
2026-01-25 09:57:21 +08:00
|
|
|
|
appSecret: '3c1fb1f63e6e052222bbcead9d07fe0c', // 小程序AppSecret(已更新)
|
2026-01-23 05:44:21 +08:00
|
|
|
|
mchId: '1318592501', // 商户号
|
2026-01-25 09:57:21 +08:00
|
|
|
|
mchKey: 'wx3e31b068be59ddc131b068be59ddc2', // API密钥(v2)
|
2026-01-23 05:47:09 +08:00
|
|
|
|
notifyUrl: 'https://soul.quwanzhi.com/api/miniprogram/pay/notify', // 支付回调地址
|
2026-01-23 05:44:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成随机字符串
|
|
|
|
|
|
function generateNonceStr(): string {
|
|
|
|
|
|
return crypto.randomBytes(16).toString('hex')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成签名
|
|
|
|
|
|
function generateSign(params: Record<string, string>, key: string): string {
|
|
|
|
|
|
const sortedKeys = Object.keys(params).sort()
|
|
|
|
|
|
const signString = sortedKeys
|
|
|
|
|
|
.filter((k) => params[k] && k !== 'sign')
|
|
|
|
|
|
.map((k) => `${k}=${params[k]}`)
|
|
|
|
|
|
.join('&')
|
|
|
|
|
|
|
|
|
|
|
|
const signWithKey = `${signString}&key=${key}`
|
|
|
|
|
|
return crypto.createHash('md5').update(signWithKey, 'utf8').digest('hex').toUpperCase()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 对象转XML
|
|
|
|
|
|
function dictToXml(data: Record<string, string>): string {
|
|
|
|
|
|
const xml = ['<xml>']
|
|
|
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
|
|
|
|
xml.push(`<${key}><![CDATA[${value}]]></${key}>`)
|
|
|
|
|
|
}
|
|
|
|
|
|
xml.push('</xml>')
|
|
|
|
|
|
return xml.join('')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// XML转对象
|
|
|
|
|
|
function xmlToDict(xml: string): Record<string, string> {
|
|
|
|
|
|
const result: Record<string, string> = {}
|
|
|
|
|
|
const regex = /<(\w+)><!\[CDATA\[(.*?)\]\]><\/\1>/g
|
|
|
|
|
|
let match
|
|
|
|
|
|
while ((match = regex.exec(xml)) !== null) {
|
|
|
|
|
|
result[match[1]] = match[2]
|
|
|
|
|
|
}
|
|
|
|
|
|
const simpleRegex = /<(\w+)>([^<]*)<\/\1>/g
|
|
|
|
|
|
while ((match = simpleRegex.exec(xml)) !== null) {
|
|
|
|
|
|
if (!result[match[1]]) {
|
|
|
|
|
|
result[match[1]] = match[2]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return result
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成订单号
|
|
|
|
|
|
function generateOrderSn(): string {
|
|
|
|
|
|
const now = new Date()
|
|
|
|
|
|
const timestamp = now.getFullYear().toString() +
|
|
|
|
|
|
(now.getMonth() + 1).toString().padStart(2, '0') +
|
|
|
|
|
|
now.getDate().toString().padStart(2, '0') +
|
|
|
|
|
|
now.getHours().toString().padStart(2, '0') +
|
|
|
|
|
|
now.getMinutes().toString().padStart(2, '0') +
|
|
|
|
|
|
now.getSeconds().toString().padStart(2, '0')
|
|
|
|
|
|
const random = Math.floor(Math.random() * 1000000).toString().padStart(6, '0')
|
|
|
|
|
|
return `MP${timestamp}${random}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* POST - 创建小程序支付订单
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function POST(request: Request) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const body = await request.json()
|
|
|
|
|
|
const { openId, productType, productId, amount, description } = body
|
|
|
|
|
|
|
|
|
|
|
|
if (!openId) {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: '缺少openId参数,请先登录'
|
|
|
|
|
|
}, { status: 400 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!amount || amount <= 0) {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: '支付金额无效'
|
|
|
|
|
|
}, { status: 400 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 18:45:28 +08:00
|
|
|
|
// === 根据推广配置计算好友优惠后的实际支付金额 ===
|
|
|
|
|
|
let finalAmount = amount
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 读取推广/分销配置,获取好友优惠比例(如 5 表示 5%)
|
|
|
|
|
|
const referralConfig = await getConfig('referral_config')
|
|
|
|
|
|
const userDiscount = referralConfig?.userDiscount ? Number(referralConfig.userDiscount) : 0
|
|
|
|
|
|
|
|
|
|
|
|
// 若存在有效的推荐码且配置了优惠比例,则给好友打折
|
|
|
|
|
|
if (userDiscount > 0 && body.referralCode) {
|
|
|
|
|
|
const discountRate = userDiscount / 100
|
|
|
|
|
|
const discounted = amount * (1 - discountRate)
|
|
|
|
|
|
// 保证至少 0.01 元,并保留两位小数
|
|
|
|
|
|
finalAmount = Math.max(0.01, Math.round(discounted * 100) / 100)
|
|
|
|
|
|
console.log('[MiniPay] 应用好友优惠:', {
|
|
|
|
|
|
originalAmount: amount,
|
|
|
|
|
|
discountPercent: userDiscount,
|
|
|
|
|
|
finalAmount,
|
|
|
|
|
|
referralCode: body.referralCode,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn('[MiniPay] 读取 referral_config.userDiscount 失败,使用原价金额:', e)
|
|
|
|
|
|
finalAmount = amount
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 05:44:21 +08:00
|
|
|
|
const orderSn = generateOrderSn()
|
2026-02-05 18:45:28 +08:00
|
|
|
|
const totalFee = Math.round(finalAmount * 100) // 转换为分(单位分)
|
2026-01-23 05:44:21 +08:00
|
|
|
|
const goodsBody = description || (productType === 'fullbook' ? '《一场Soul的创业实验》全书' : `章节购买-${productId}`)
|
|
|
|
|
|
|
|
|
|
|
|
// 获取客户端IP
|
|
|
|
|
|
const forwarded = request.headers.get('x-forwarded-for')
|
|
|
|
|
|
const clientIp = forwarded ? forwarded.split(',')[0] : '127.0.0.1'
|
|
|
|
|
|
|
|
|
|
|
|
// 构建统一下单参数
|
|
|
|
|
|
const params: Record<string, string> = {
|
|
|
|
|
|
appid: WECHAT_PAY_CONFIG.appId,
|
|
|
|
|
|
mch_id: WECHAT_PAY_CONFIG.mchId,
|
|
|
|
|
|
nonce_str: generateNonceStr(),
|
|
|
|
|
|
body: goodsBody.slice(0, 128),
|
|
|
|
|
|
out_trade_no: orderSn,
|
|
|
|
|
|
total_fee: totalFee.toString(),
|
|
|
|
|
|
spbill_create_ip: clientIp,
|
|
|
|
|
|
notify_url: WECHAT_PAY_CONFIG.notifyUrl,
|
|
|
|
|
|
trade_type: 'JSAPI',
|
|
|
|
|
|
openid: openId,
|
|
|
|
|
|
attach: JSON.stringify({ productType, productId, userId: body.userId || '' }),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成签名
|
|
|
|
|
|
params.sign = generateSign(params, WECHAT_PAY_CONFIG.mchKey)
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[MiniPay] 创建订单:', {
|
|
|
|
|
|
orderSn,
|
|
|
|
|
|
totalFee,
|
|
|
|
|
|
openId: openId.slice(0, 10) + '...',
|
|
|
|
|
|
productType,
|
|
|
|
|
|
productId,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2026-02-04 21:36:26 +08:00
|
|
|
|
// === ✅ 1. 先插入订单到数据库(无论支付是否成功,都要有订单记录) ===
|
|
|
|
|
|
const userId = body.userId || openId // 优先使用 userId,否则用 openId
|
|
|
|
|
|
let orderCreated = false
|
|
|
|
|
|
|
|
|
|
|
|
// 查询当前用户的有效推荐人(用于订单归属与分销)
|
|
|
|
|
|
let referrerId: string | null = null
|
|
|
|
|
|
try {
|
|
|
|
|
|
const bindings = await query(`
|
|
|
|
|
|
SELECT referrer_id
|
|
|
|
|
|
FROM referral_bindings
|
|
|
|
|
|
WHERE referee_id = ? AND status = 'active' AND expiry_date > NOW()
|
|
|
|
|
|
ORDER BY binding_date DESC
|
|
|
|
|
|
LIMIT 1
|
|
|
|
|
|
`, [userId]) as any[]
|
|
|
|
|
|
if (bindings.length > 0) {
|
|
|
|
|
|
referrerId = bindings[0].referrer_id || null
|
|
|
|
|
|
console.log('[MiniPay] 订单归属推荐人(绑定):', referrerId)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 若绑定未查到且前端传了邀请码,按邀请码解析推荐人
|
|
|
|
|
|
if (!referrerId && body.referralCode) {
|
|
|
|
|
|
const refUsers = await query(`
|
|
|
|
|
|
SELECT id FROM users WHERE referral_code = ? LIMIT 1
|
|
|
|
|
|
`, [String(body.referralCode).trim()]) as any[]
|
|
|
|
|
|
if (refUsers.length > 0) {
|
|
|
|
|
|
referrerId = refUsers[0].id
|
|
|
|
|
|
console.log('[MiniPay] 订单归属推荐人(邀请码):', referrerId)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn('[MiniPay] 查询推荐人失败,继续创建订单:', e)
|
|
|
|
|
|
}
|
2026-02-05 11:35:57 +08:00
|
|
|
|
|
|
|
|
|
|
// 下单时使用的邀请码:优先用请求体,否则用推荐人当前邀请码(便于订单记录对账)
|
|
|
|
|
|
let orderReferralCode: string | null = body.referralCode ? String(body.referralCode).trim() || null : null
|
|
|
|
|
|
if (!orderReferralCode && referrerId) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const refRows = (await query(`SELECT referral_code FROM users WHERE id = ? LIMIT 1`, [referrerId]) as any[])
|
|
|
|
|
|
if (refRows.length > 0 && refRows[0].referral_code) {
|
|
|
|
|
|
orderReferralCode = refRows[0].referral_code
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (_) { /* 忽略 */ }
|
|
|
|
|
|
}
|
2026-02-04 21:36:26 +08:00
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 检查是否已有相同产品的已支付订单
|
|
|
|
|
|
const existingOrders = await query(`
|
|
|
|
|
|
SELECT id FROM orders
|
|
|
|
|
|
WHERE user_id = ?
|
|
|
|
|
|
AND product_type = ?
|
|
|
|
|
|
AND product_id = ?
|
|
|
|
|
|
AND status = 'paid'
|
|
|
|
|
|
LIMIT 1
|
|
|
|
|
|
`, [userId, productType, productId || 'fullbook']) as any[]
|
|
|
|
|
|
|
|
|
|
|
|
if (existingOrders.length > 0) {
|
|
|
|
|
|
console.log('[MiniPay] ⚠️ 用户已购买该产品,但仍创建订单:', {
|
|
|
|
|
|
userId,
|
|
|
|
|
|
productType,
|
|
|
|
|
|
productId
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-05 11:35:57 +08:00
|
|
|
|
// 插入订单(含 referrer_id、referral_code,便于分销归属与对账)
|
2026-02-04 21:36:26 +08:00
|
|
|
|
try {
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
INSERT INTO orders (
|
|
|
|
|
|
id, order_sn, user_id, open_id,
|
|
|
|
|
|
product_type, product_id, amount, description,
|
2026-02-05 11:35:57 +08:00
|
|
|
|
status, transaction_id, referrer_id, referral_code, created_at, updated_at
|
|
|
|
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
2026-02-04 21:36:26 +08:00
|
|
|
|
`, [
|
|
|
|
|
|
orderSn, orderSn, userId, openId,
|
2026-02-05 18:45:28 +08:00
|
|
|
|
productType, productId || 'fullbook', finalAmount, goodsBody,
|
2026-02-05 11:35:57 +08:00
|
|
|
|
'created', null, referrerId, orderReferralCode
|
2026-02-04 21:36:26 +08:00
|
|
|
|
])
|
|
|
|
|
|
} catch (insertErr: any) {
|
2026-02-05 11:35:57 +08:00
|
|
|
|
// 兼容:若表尚无 referrer_id 或 referral_code 列
|
|
|
|
|
|
const msg = (insertErr as any)?.message || ''
|
|
|
|
|
|
const code = (insertErr as any)?.code || ''
|
|
|
|
|
|
if (msg.includes('referrer_id') || msg.includes('referral_code') || code === 'ER_BAD_FIELD_ERROR') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
INSERT INTO orders (
|
|
|
|
|
|
id, order_sn, user_id, open_id,
|
|
|
|
|
|
product_type, product_id, amount, description,
|
|
|
|
|
|
status, transaction_id, referrer_id, created_at, updated_at
|
|
|
|
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
|
|
|
|
|
`, [
|
|
|
|
|
|
orderSn, orderSn, userId, openId,
|
2026-02-05 18:45:28 +08:00
|
|
|
|
productType, productId || 'fullbook', finalAmount, goodsBody,
|
2026-02-05 11:35:57 +08:00
|
|
|
|
'created', null, referrerId
|
|
|
|
|
|
])
|
|
|
|
|
|
console.log('[MiniPay] 订单已插入(未含 referral_code,请执行 scripts/add_orders_referral_code.py)')
|
|
|
|
|
|
} catch (e2: any) {
|
|
|
|
|
|
if (e2?.message?.includes('referrer_id') || e2?.code === 'ER_BAD_FIELD_ERROR') {
|
|
|
|
|
|
await query(`
|
|
|
|
|
|
INSERT INTO orders (
|
|
|
|
|
|
id, order_sn, user_id, open_id,
|
|
|
|
|
|
product_type, product_id, amount, description,
|
|
|
|
|
|
status, transaction_id, created_at, updated_at
|
|
|
|
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
|
|
|
|
|
`, [
|
|
|
|
|
|
orderSn, orderSn, userId, openId,
|
2026-02-05 18:45:28 +08:00
|
|
|
|
productType, productId || 'fullbook', finalAmount, goodsBody,
|
2026-02-05 11:35:57 +08:00
|
|
|
|
'created', null
|
|
|
|
|
|
])
|
|
|
|
|
|
console.log('[MiniPay] 订单已插入(未含 referrer_id/referral_code,请执行迁移脚本)')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw e2
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-02-04 21:36:26 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
throw insertErr
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
orderCreated = true
|
|
|
|
|
|
console.log('[MiniPay] ✅ 订单已插入数据库:', {
|
|
|
|
|
|
orderSn,
|
|
|
|
|
|
userId,
|
|
|
|
|
|
productType,
|
|
|
|
|
|
productId,
|
2026-02-05 18:45:28 +08:00
|
|
|
|
originalAmount: amount,
|
|
|
|
|
|
finalAmount,
|
2026-02-04 21:36:26 +08:00
|
|
|
|
})
|
|
|
|
|
|
} catch (dbError) {
|
|
|
|
|
|
console.error('[MiniPay] ❌ 插入订单失败:', dbError)
|
|
|
|
|
|
// 订单创建失败,但不中断支付流程
|
|
|
|
|
|
// 理由:微信支付成功后仍可以通过回调补记订单
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-23 05:44:21 +08:00
|
|
|
|
// 调用微信统一下单接口
|
|
|
|
|
|
const xmlData = dictToXml(params)
|
|
|
|
|
|
const response = await fetch('https://api.mch.weixin.qq.com/pay/unifiedorder', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/xml' },
|
|
|
|
|
|
body: xmlData,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const responseText = await response.text()
|
|
|
|
|
|
const result = xmlToDict(responseText)
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[MiniPay] 微信响应:', {
|
|
|
|
|
|
return_code: result.return_code,
|
|
|
|
|
|
result_code: result.result_code,
|
|
|
|
|
|
err_code: result.err_code,
|
|
|
|
|
|
err_code_des: result.err_code_des,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 检查返回结果
|
|
|
|
|
|
if (result.return_code !== 'SUCCESS') {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: `微信支付请求失败: ${result.return_msg || '未知错误'}`
|
|
|
|
|
|
}, { status: 500 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (result.result_code !== 'SUCCESS') {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: `微信支付失败: ${result.err_code_des || result.err_code || '未知错误'}`
|
|
|
|
|
|
}, { status: 500 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建小程序支付参数
|
|
|
|
|
|
const prepayId = result.prepay_id
|
|
|
|
|
|
if (!prepayId) {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: '微信支付返回数据异常'
|
|
|
|
|
|
}, { status: 500 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const timestamp = Math.floor(Date.now() / 1000).toString()
|
|
|
|
|
|
const nonceStr = generateNonceStr()
|
|
|
|
|
|
|
|
|
|
|
|
const payParams: Record<string, string> = {
|
|
|
|
|
|
appId: WECHAT_PAY_CONFIG.appId,
|
|
|
|
|
|
timeStamp: timestamp,
|
|
|
|
|
|
nonceStr,
|
|
|
|
|
|
package: `prepay_id=${prepayId}`,
|
|
|
|
|
|
signType: 'MD5',
|
|
|
|
|
|
}
|
|
|
|
|
|
payParams.paySign = generateSign(payParams, WECHAT_PAY_CONFIG.mchKey)
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[MiniPay] 支付参数生成成功:', { orderSn, prepayId: prepayId.slice(0, 20) + '...' })
|
|
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
orderSn,
|
|
|
|
|
|
prepayId,
|
|
|
|
|
|
payParams: {
|
|
|
|
|
|
timeStamp: timestamp,
|
|
|
|
|
|
nonceStr,
|
|
|
|
|
|
package: `prepay_id=${prepayId}`,
|
|
|
|
|
|
signType: 'MD5',
|
|
|
|
|
|
paySign: payParams.paySign,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[MiniPay] 创建订单失败:', error)
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: '创建支付订单失败'
|
|
|
|
|
|
}, { status: 500 })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GET - 查询订单状态
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function GET(request: Request) {
|
|
|
|
|
|
const { searchParams } = new URL(request.url)
|
|
|
|
|
|
const orderSn = searchParams.get('orderSn')
|
|
|
|
|
|
|
|
|
|
|
|
if (!orderSn) {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: '缺少订单号'
|
|
|
|
|
|
}, { status: 400 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const params: Record<string, string> = {
|
|
|
|
|
|
appid: WECHAT_PAY_CONFIG.appId,
|
|
|
|
|
|
mch_id: WECHAT_PAY_CONFIG.mchId,
|
|
|
|
|
|
out_trade_no: orderSn,
|
|
|
|
|
|
nonce_str: generateNonceStr(),
|
|
|
|
|
|
}
|
|
|
|
|
|
params.sign = generateSign(params, WECHAT_PAY_CONFIG.mchKey)
|
|
|
|
|
|
|
|
|
|
|
|
const xmlData = dictToXml(params)
|
|
|
|
|
|
const response = await fetch('https://api.mch.weixin.qq.com/pay/orderquery', {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/xml' },
|
|
|
|
|
|
body: xmlData,
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const responseText = await response.text()
|
|
|
|
|
|
const result = xmlToDict(responseText)
|
|
|
|
|
|
|
|
|
|
|
|
if (result.return_code !== 'SUCCESS' || result.result_code !== 'SUCCESS') {
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: { status: 'unknown', orderSn }
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tradeState = result.trade_state
|
|
|
|
|
|
let status = 'paying'
|
|
|
|
|
|
if (tradeState === 'SUCCESS') status = 'paid'
|
|
|
|
|
|
else if (['CLOSED', 'REVOKED', 'PAYERROR'].includes(tradeState)) status = 'failed'
|
|
|
|
|
|
else if (tradeState === 'REFUND') status = 'refunded'
|
|
|
|
|
|
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
data: {
|
|
|
|
|
|
status,
|
|
|
|
|
|
orderSn,
|
|
|
|
|
|
transactionId: result.transaction_id,
|
|
|
|
|
|
totalFee: parseInt(result.total_fee || '0', 10),
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[MiniPay] 查询订单失败:', error)
|
|
|
|
|
|
return NextResponse.json({
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
error: '查询订单失败'
|
|
|
|
|
|
}, { status: 500 })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|