ai-c/utils/payment.js
2026-02-02 18:21:32 +08:00

322 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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.

/**
* 微信支付工具类
* 封装微信支付相关功能
*/
const api = require('./api')
const config = require('../config/index')
/**
* 发起微信支付
* @param {string} orderId - 订单ID
* @param {string} orderType - 订单类型: recharge/vip/companion/gift
* @returns {Promise}
*/
const requestPayment = async (orderId, orderType = 'recharge') => {
try {
console.log('[Payment] ========== 开始支付流程 ==========')
console.log('[Payment] 订单ID:', orderId)
console.log('[Payment] 订单类型:', orderType)
// 1. 调用后端API获取支付参数
wx.showLoading({ title: '正在调起支付...', mask: true })
const paymentParams = await api.payment.prepay({
orderId,
orderType
})
console.log('[Payment] 获取支付参数成功')
console.log('[Payment] 完整响应:', JSON.stringify(paymentParams))
// 后端返回格式: { success: true, data: { timeStamp, nonceStr, package, signType, paySign, ... } }
if (!paymentParams.success || !paymentParams.data) {
throw new Error(paymentParams.error || paymentParams.message || '获取支付参数失败')
}
// 只提取微信支付需要的5个参数忽略其他字段如 total_fee, orderId 等)
const {
timeStamp,
nonceStr,
package: packageValue,
signType,
paySign,
// 以下字段仅用于日志,不传递给 wx.requestPayment
total_fee,
orderId: responseOrderId,
orderNo
} = paymentParams.data
console.log('[Payment] 支付参数解析:')
console.log(' - timeStamp:', timeStamp)
console.log(' - nonceStr:', nonceStr)
console.log(' - package:', packageValue)
console.log(' - signType:', signType)
console.log(' - paySign:', paySign ? paySign.substring(0, 20) + '...' : 'null')
console.log(' - total_fee (仅日志):', total_fee)
console.log(' - orderId (仅日志):', responseOrderId)
console.log(' - orderNo (仅日志):', orderNo)
// 验证必要参数
if (!timeStamp || !nonceStr || !packageValue || !paySign) {
console.error('[Payment] 支付参数不完整')
throw new Error('支付参数不完整')
}
// 2. 调用微信支付API
wx.hideLoading()
console.log('[Payment] 调用wx.requestPayment')
console.log('[Payment] ⚠️ 注意只传递5个必需参数不传递 total_fee')
// 构造支付参数对象确保只包含5个必需字段
const paymentRequest = {
timeStamp: String(timeStamp),
nonceStr: String(nonceStr),
package: String(packageValue),
signType: signType || 'MD5',
paySign: String(paySign)
}
console.log('[Payment] 最终支付参数:', JSON.stringify(paymentRequest))
return new Promise((resolve, reject) => {
wx.requestPayment({
...paymentRequest,
success: (res) => {
console.log('[Payment] ✓ 支付成功:', res)
resolve({ success: true, message: '支付成功' })
},
fail: (err) => {
console.error('[Payment] ✗ 支付失败:', err)
console.error('[Payment] 错误详情:', JSON.stringify(err))
// 用户取消支付
if (err.errMsg === 'requestPayment:fail cancel') {
reject({ code: 'USER_CANCEL', message: '您已取消支付' })
}
// 支付失败
else {
reject({
code: 'PAYMENT_FAIL',
message: err.errMsg || '支付失败,请稍后重试',
detail: err
})
}
}
})
})
} catch (error) {
wx.hideLoading()
console.error('[Payment] ✗ 支付流程错误:', error)
throw error
}
}
/**
* 查询订单支付状态
* @param {string} orderId - 订单ID
* @param {number} confirm - 是否主动确认 (1/0)
* @returns {Promise}
*/
const queryOrderStatus = async (orderId, confirm = 0) => {
try {
console.log('[Payment] 查询订单状态:', orderId, 'confirm:', confirm)
const res = await api.payment.queryOrder(orderId, { confirm })
console.log('[Payment] 订单状态:', res.data?.status)
return res
} catch (error) {
console.error('[Payment] 查询订单状态失败:', error)
throw error
}
}
/**
* 轮询查询订单状态
* @param {string} orderId - 订单ID
* @param {number} maxRetries - 最大重试次数
* @param {number} interval - 轮询间隔(ms)
* @returns {Promise}
*/
const pollOrderStatus = (orderId, maxRetries = 30, interval = 2000) => {
return new Promise((resolve, reject) => {
let retries = 0
console.log('[Payment] 开始轮询订单状态')
console.log('[Payment] 最大重试次数:', maxRetries)
console.log('[Payment] 轮询间隔:', interval, 'ms')
const poll = async () => {
try {
retries++
console.log(`[Payment] 第 ${retries}/${maxRetries} 次查询`)
// 前3次查询带 confirm=1 参数,促使后端主动向微信查询状态
const confirm = retries <= 3 ? 1 : 0
const res = await queryOrderStatus(orderId, confirm)
if (res.success && res.data) {
const status = res.data.status
console.log(`[Payment] 订单状态: ${status}`)
// 支付成功 - 后端状态: completed
if (status === 'completed') {
console.log('[Payment] ✓ 订单支付成功并已完成')
resolve({ success: true, data: res.data, status: 'completed' })
return
}
// 已支付但未完成 - 后端状态: paid
if (status === 'paid') {
console.log('[Payment] ✓ 订单已支付,等待完成')
// 继续轮询,等待变为 completed
}
// 支付失败 - 后端状态: cancelled
if (status === 'cancelled') {
console.log('[Payment] ✗ 订单已取消')
reject({ code: 'ORDER_CANCELLED', message: '订单已取消' })
return
}
// 继续轮询
if (retries < maxRetries) {
console.log('[Payment] 订单状态为', status, ',继续轮询...')
setTimeout(poll, interval)
} else {
console.log('[Payment] ✗ 查询超时')
reject({
code: 'TIMEOUT',
message: '支付结果查询超时,请稍后在订单列表中查看',
data: res.data
})
}
} else {
reject({ code: 'QUERY_FAIL', message: res.error || res.message || '查询订单失败' })
}
} catch (error) {
console.error('[Payment] 轮询查询失败:', error)
// 如果是网络错误,继续重试
if (retries < maxRetries) {
console.log('[Payment] 查询出错,继续重试...')
setTimeout(poll, interval)
} else {
reject(error)
}
}
}
// 开始第一次查询
poll()
})
}
/**
* 完整支付流程(创建订单 + 支付 + 查询状态)
* @param {object} orderData - 订单数据
* @param {string} orderType - 订单类型
* @returns {Promise}
*/
const completePayment = async (orderData, orderType) => {
try {
console.log('[Payment] ========== 完整支付流程开始 ==========')
console.log('[Payment] 测试模式:', config.TEST_MODE ? '开启' : '关闭')
// 1. 创建订单
wx.showLoading({ title: '创建订单中...', mask: true })
let orderRes
switch (orderType) {
case 'recharge':
orderRes = await api.payment.createRechargeOrder(orderData)
break
case 'vip':
orderRes = await api.payment.createVipOrder(orderData)
break
case 'companion':
orderRes = await api.companion.createOrder(orderData)
break
default:
throw new Error('不支持的订单类型')
}
if (!orderRes.success || !orderRes.data) {
throw new Error(orderRes.message || '创建订单失败')
}
const orderId = orderRes.data.orderId
console.log('[Payment] ✓ 订单创建成功:', orderId)
wx.hideLoading()
// 测试模式:跳过微信支付,直接模拟支付成功
if (config.TEST_MODE) {
console.log('[Payment] ⚠️ 测试模式:跳过微信支付,直接模拟支付成功')
wx.showLoading({ title: '模拟支付中...', mask: true })
// 延迟1秒模拟支付过程
await new Promise(resolve => setTimeout(resolve, 1000))
// 调用后端测试支付接口,直接标记订单为已支付
try {
const testPayRes = await api.payment.testPay({ orderId })
console.log('[Payment] ✓ 测试支付成功:', testPayRes)
} catch (error) {
console.error('[Payment] ✗ 测试支付失败:', error)
// 即使测试支付接口失败,也继续流程(可能后端没有此接口)
}
wx.hideLoading()
// 显示成功提示
wx.showToast({
title: '支付成功(测试)',
icon: 'success',
duration: 2000
})
console.log('[Payment] ========== 支付流程完成(测试模式)==========')
return {
success: true,
orderId,
testMode: true,
message: '测试模式支付成功'
}
}
// 正式模式:走真实微信支付流程
// 2. 发起支付
await requestPayment(orderId, orderType)
// 3. 查询订单状态
wx.showLoading({ title: '支付成功,处理中...', mask: true })
const statusRes = await pollOrderStatus(orderId)
wx.hideLoading()
console.log('[Payment] ========== 支付流程完成 ==========')
return {
success: true,
orderId,
orderData: statusRes.data
}
} catch (error) {
wx.hideLoading()
console.error('[Payment] ========== 支付流程失败 ==========')
throw error
}
}
module.exports = {
requestPayment,
queryOrderStatus,
pollOrderStatus,
completePayment
}