322 lines
10 KiB
JavaScript
322 lines
10 KiB
JavaScript
/**
|
||
* 微信支付工具类
|
||
* 封装微信支付相关功能
|
||
*/
|
||
|
||
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
|
||
}
|