const axios = require('axios') const config = require('../config.json') const db = require('../plugin/DataBase/db') const { insertLedgerRecord } = require('./Lepao/CountLedger') const REFUND_WINDOW_MS = 7 * 24 * 60 * 60 * 1000 const ORDER_STATE_COMPLETED = 2 const ORDER_STATE_REFUNDED = 5 function evaluateRefundEligibility({ state, payTime, userLepaoCount, goodsLepaoCount, skipTimeLimit = false }) { if (Number(state) === ORDER_STATE_REFUNDED) { return { canRefund: false, reason: '订单已退款' } } if (Number(state) !== ORDER_STATE_COMPLETED) { return { canRefund: false, reason: '仅已完成订单可申请退款' } } if (!payTime) { return { canRefund: false, reason: '订单支付时间异常' } } if (!skipTimeLimit && Date.now() - Number(payTime) > REFUND_WINDOW_MS) { return { canRefund: false, reason: '已超过7天退款期限' } } const purchasedCount = Number(goodsLepaoCount || 0) const remainingCount = Number(userLepaoCount || 0) if (purchasedCount > 0 && remainingCount <= purchasedCount) { return { canRefund: false, reason: '账户剩余次数需大于订单购买次数才可退款' } } return { canRefund: true, reason: '' } } function parsePaymentResponseBody(data) { if (data && typeof data === 'object') return data if (typeof data !== 'string') return null const text = data.trim() if (!text) return null try { return JSON.parse(text) } catch (_) { return null } } function formatPaymentRefundError(response, fallbackMessage) { const status = Number(response?.status || 0) const parsed = parsePaymentResponseBody(response?.data) if (parsed?.msg) return String(parsed.msg) if (status === 503) return '支付平台暂时不可用,请稍后重试' if (status >= 500) return `支付平台异常(${status}),请稍后重试` if (status >= 400) return `支付平台拒绝退款(${status})` return fallbackMessage || '支付平台退款失败' } async function requestPaymentRefund({ orderId, tradeNo, money, logger }) { const paymentConfig = config.pay || {} if (!paymentConfig.url || !paymentConfig.pid || !paymentConfig.key) { throw new Error('支付配置错误') } const params = new URLSearchParams() params.append('pid', String(paymentConfig.pid)) params.append('key', paymentConfig.key) if (tradeNo) { params.append('trade_no', tradeNo) } else { params.append('out_trade_no', orderId) } params.append('money', String(money)) const refundUrl = `${paymentConfig.url}/api.php?act=refund` let response try { response = await axios.post(refundUrl, params.toString(), { headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, timeout: 30000, validateStatus: () => true }) } catch (error) { logger?.error?.(`易支付退款请求失败 订单号:${orderId}:${error.stack || error}`) if (error.code === 'ECONNABORTED') { throw new Error('支付平台响应超时,请稍后重试') } throw new Error('无法连接支付平台,请稍后重试') } const result = parsePaymentResponseBody(response.data) logger?.info?.(`易支付退款响应 订单号:${orderId},HTTP ${response.status},结果:${JSON.stringify(result ?? response.data)}`) if (response.status >= 400) { throw new Error(formatPaymentRefundError(response, '支付平台退款失败')) } if (!result || Number(result.code) !== 1) { throw new Error(result?.msg || '支付平台退款失败') } return result } async function loadRefundContext(orderId) { const rows = await db.query( `SELECT o.orderId, o.state, o.price, o.pay_id, o.pay_time, o.create_user, g.lepao_count, g.ic_count FROM orders o LEFT JOIN goods g ON o.goods_id = g.id WHERE o.orderId = ? LIMIT 1`, [orderId] ) if (!rows || rows.length !== 1) { return null } const order = rows[0] const userRows = await db.query( 'SELECT lepao_count, ic_count FROM users WHERE uuid = ? LIMIT 1', [order.create_user] ) if (!userRows || userRows.length !== 1) { return null } return { order, user: userRows[0] } } async function executeOrderRefund({ orderId, operatorUuid = null, skipTimeLimit = false, logger }) { try { const context = await loadRefundContext(orderId) if (!context) { return { ok: false, msg: '订单或用户不存在' } } const { order, user } = context const eligibility = evaluateRefundEligibility({ state: order.state, payTime: order.pay_time, userLepaoCount: user.lepao_count, goodsLepaoCount: order.lepao_count, skipTimeLimit }) if (!eligibility.canRefund) { return { ok: false, msg: eligibility.reason } } const deductLepao = Number(order.lepao_count || 0) const deductIc = Number(order.ic_count || 0) if (deductLepao > 0 && Number(user.lepao_count || 0) < deductLepao) { return { ok: false, msg: '账户乐跑次数不足,无法完成退款' } } await requestPaymentRefund({ orderId: order.orderId, tradeNo: order.pay_id, money: order.price, logger }) const conn = await db.connect() try { await conn.beginTransaction() const [orderRows] = await conn.execute( `SELECT o.orderId, o.state, o.create_user, g.lepao_count, g.ic_count FROM orders o LEFT JOIN goods g ON o.goods_id = g.id WHERE o.orderId = ? FOR UPDATE`, [orderId] ) if (!orderRows || orderRows.length !== 1) { await conn.rollback() return { ok: false, msg: '订单不存在' } } const lockedOrder = orderRows[0] if (Number(lockedOrder.state) !== ORDER_STATE_COMPLETED) { await conn.rollback() return { ok: false, msg: '订单状态已变更,请刷新后重试' } } const [userRows] = await conn.execute( 'SELECT lepao_count, ic_count FROM users WHERE uuid = ? FOR UPDATE', [lockedOrder.create_user] ) if (!userRows || userRows.length !== 1) { await conn.rollback() return { ok: false, msg: '用户不存在' } } const lockedUser = userRows[0] const lockedDeductLepao = Number(lockedOrder.lepao_count || 0) const lockedDeductIc = Number(lockedOrder.ic_count || 0) const beforeLepao = Number(lockedUser.lepao_count || 0) const beforeIc = Number(lockedUser.ic_count || 0) if (lockedDeductLepao > 0 && beforeLepao < lockedDeductLepao) { await conn.rollback() logger?.error?.(`退款支付已成功但扣次失败,需人工处理,订单号:${orderId}`) return { ok: false, msg: '支付已退款但扣减次数失败,请联系客服处理' } } const afterLepao = beforeLepao - lockedDeductLepao const afterIc = Math.max(0, beforeIc - lockedDeductIc) const [updateUserRes] = await conn.execute( 'UPDATE users SET lepao_count = ?, ic_count = ? WHERE uuid = ?', [afterLepao, afterIc, lockedOrder.create_user] ) if (!updateUserRes || updateUserRes.affectedRows !== 1) { await conn.rollback() logger?.error?.(`退款支付已成功但更新用户失败,需人工处理,订单号:${orderId}`) return { ok: false, msg: '支付已退款但更新账户失败,请联系客服处理' } } const [updateOrderRes] = await conn.execute( 'UPDATE orders SET state = ? WHERE orderId = ? AND state = ?', [ORDER_STATE_REFUNDED, orderId, ORDER_STATE_COMPLETED] ) if (!updateOrderRes || updateOrderRes.affectedRows !== 1) { await conn.rollback() logger?.error?.(`退款支付已成功但更新订单失败,需人工处理,订单号:${orderId}`) return { ok: false, msg: '支付已退款但更新订单失败,请联系客服处理' } } if (lockedDeductLepao !== 0) { await insertLedgerRecord({ executor: conn, userUuid: lockedOrder.create_user, delta: -lockedDeductLepao, balanceBefore: beforeLepao, balanceAfter: afterLepao, bizType: 'purchase_refund', bizId: orderId, operatorUuid, remark: `订单退款:${orderId}` }) } await conn.commit() return { ok: true, msg: '退款成功' } } catch (dbError) { try { await conn.rollback() } catch (_) { } logger?.error?.(`退款入账失败 ${orderId}: ${dbError.stack || dbError}`) return { ok: false, msg: '支付已退款但入账失败,请联系客服处理' } } } catch (error) { logger?.error?.(`订单退款失败 ${orderId}: ${error.stack || error}`) return { ok: false, msg: error.message || '退款失败,请稍后再试' } } } module.exports = { REFUND_WINDOW_MS, ORDER_STATE_REFUNDED, evaluateRefundEligibility, executeOrderRefund }