Files
2026-06-03 14:15:55 +08:00

278 lines
7.4 KiB
JavaScript

const { query, transaction } = require('../config/database')
const { paginate } = require('../utils/pagination')
const REFUNDABLE_STATUSES = ['paid', 'completed']
function currentUserId(ctx) {
return ctx.state.user ? ctx.state.user.id : null
}
function currentUser(ctx) {
return ctx.state.user
}
async function getRefunds(ctx) {
const { page, pageSize, status } = ctx.query
let sql = `
SELECT
r.*,
o.status as order_status,
o.total_price as order_amount,
u.phone as user_phone,
u.name as user_name
FROM refunds r
LEFT JOIN orders o ON r.order_id = o.id
LEFT JOIN users u ON r.user_id = u.id
WHERE 1=1
`
const params = []
if (status !== undefined && status !== '') {
sql += ' AND r.status = ?'
params.push(parseInt(status))
}
sql += ' ORDER BY r.created_at DESC'
const result = await paginate(query, sql, params, ctx.query.page, ctx.query.pageSize)
const rows = (result.data || []).map(item => ({
id: item.id,
orderId: item.order_id,
userId: item.user_id,
userPhone: item.user_phone,
userName: item.user_name,
type: item.type,
reason: item.reason,
amount: parseFloat(item.amount),
status: item.status,
adminRemark: item.admin_remark,
orderStatus: item.order_status,
orderAmount: parseFloat(item.order_amount),
processedAt: item.processed_at,
createdAt: item.created_at
}))
result.data = rows
ctx.body = {
code: 200,
...result
}
}
async function getRefundById(ctx) {
const refundId = parseInt(ctx.params.id)
const refunds = await query(`
SELECT
r.*,
o.status as order_status,
o.total_price as order_amount,
u.phone as user_phone,
u.name as user_name
FROM refunds r
LEFT JOIN orders o ON r.order_id = o.id
LEFT JOIN users u ON r.user_id = u.id
WHERE r.id = ?
`, [refundId])
if (refunds.length === 0) {
ctx.status = 404
ctx.body = { code: 404, message: '退款申请不存在' }
return
}
const item = refunds[0]
const user = currentUser(ctx)
if (user.role !== 2 && user.role !== 1 && item.user_id !== user.id) {
ctx.status = 403
ctx.body = { code: 403, message: '无权查看该退款' }
return
}
ctx.body = {
code: 200,
data: {
id: item.id,
orderId: item.order_id,
userId: item.user_id,
userPhone: item.user_phone,
userName: item.user_name,
type: item.type,
reason: item.reason,
amount: parseFloat(item.amount),
status: item.status,
adminRemark: item.admin_remark,
orderStatus: item.order_status,
orderAmount: parseFloat(item.order_amount),
processedAt: item.processed_at,
createdAt: item.created_at
}
}
}
async function createRefund(ctx) {
const user = currentUser(ctx)
if (!user) {
ctx.status = 401
ctx.body = { code: 401, message: '未登录' }
return
}
const { orderId, type, reason, amount } = ctx.request.body || {}
if (!orderId || !reason) {
ctx.body = { code: 400, message: '缺少必要参数' }
return
}
const userId = user.id
const orders = await query('SELECT * FROM orders WHERE id = ? AND user_id = ?', [orderId, userId])
if (orders.length === 0) {
ctx.body = { code: 404, message: '订单不存在' }
return
}
const order = orders[0]
if (!REFUNDABLE_STATUSES.includes(order.status)) {
ctx.body = { code: 400, message: `订单当前状态(${order.status})不可申请退款` }
return
}
const existingRefund = await query('SELECT * FROM refunds WHERE order_id = ? AND status = 0', [orderId])
if (existingRefund.length > 0) {
ctx.body = { code: 400, message: '该订单已有待处理的退款申请' }
return
}
const orderTotal = parseFloat(order.total_price)
let refundAmount = orderTotal
if (amount !== undefined && amount !== null) {
const parsed = parseFloat(amount)
if (isNaN(parsed) || parsed <= 0) {
ctx.body = { code: 400, message: '退款金额无效' }
return
}
if (parsed > orderTotal) {
ctx.body = { code: 400, message: `退款金额不能超过订单金额 ¥${orderTotal.toFixed(2)}` }
return
}
refundAmount = parsed
}
const result = await transaction(async (conn) => {
const [refundResult] = await conn.execute(
'INSERT INTO refunds (order_id, user_id, type, reason, amount) VALUES (?, ?, ?, ?, ?)',
[orderId, userId, type || 1, reason, refundAmount]
)
await conn.execute("UPDATE orders SET status = 'refunding' WHERE id = ?", [orderId])
return refundResult.insertId
})
ctx.body = {
code: 200,
message: '退款申请已提交',
data: { id: result, orderId, amount: refundAmount }
}
}
async function processRefund(ctx) {
const refundId = parseInt(ctx.params.id)
const { status, adminRemark } = ctx.request.body || {}
if (status !== 1 && status !== 2) {
ctx.body = { code: 400, message: '请选择正确的处理结果' }
return
}
const refunds = await query('SELECT * FROM refunds WHERE id = ?', [refundId])
if (refunds.length === 0) {
ctx.body = { code: 404, message: '退款申请不存在' }
return
}
const refund = refunds[0]
if (refund.status !== 0) {
ctx.body = { code: 400, message: '该退款申请已处理' }
return
}
await transaction(async (conn) => {
await conn.execute(
'UPDATE refunds SET status = ?, admin_remark = ?, processed_at = NOW() WHERE id = ?',
[status, adminRemark || '', refundId]
)
if (status === 1) {
await conn.execute("UPDATE orders SET status = 'refunded' WHERE id = ?", [refund.order_id])
const [userRows] = await conn.execute('SELECT points FROM users WHERE id = ?', [refund.user_id])
if (userRows.length > 0) {
const deductPoints = Math.min(Math.floor(refund.amount), userRows[0].points)
if (deductPoints > 0) {
const newPoints = userRows[0].points - deductPoints
await conn.execute('UPDATE users SET points = ? WHERE id = ?', [newPoints, refund.user_id])
await conn.execute(
'INSERT INTO points_logs (user_id, type, amount, description) VALUES (?, ?, ?, ?)',
[refund.user_id, 'spend', deductPoints, `订单退款扣除积分: ${refund.order_id}`]
)
}
}
} else {
await conn.execute("UPDATE orders SET status = 'completed' WHERE id = ?", [refund.order_id])
}
})
ctx.body = {
code: 200,
message: status === 1 ? '已同意退款' : '已拒绝退款'
}
}
async function getUserRefunds(ctx) {
const user = currentUser(ctx)
if (!user) {
ctx.status = 401
ctx.body = { code: 401, message: '未登录' }
return
}
const requestedId = parseInt(ctx.query.userId)
const userId = (user.role === 2 || user.role === 1) && requestedId ? requestedId : user.id
const refunds = await query(`
SELECT
r.*,
o.status as order_status
FROM refunds r
LEFT JOIN orders o ON r.order_id = o.id
WHERE r.user_id = ?
ORDER BY r.created_at DESC
`, [userId])
const rows = refunds.map(item => ({
id: item.id,
orderId: item.order_id,
type: item.type,
reason: item.reason,
amount: parseFloat(item.amount),
status: item.status,
adminRemark: item.admin_remark,
orderStatus: item.order_status,
processedAt: item.processed_at,
createdAt: item.created_at
}))
ctx.body = {
code: 200,
data: rows
}
}
module.exports = {
getRefunds,
getRefundById,
createRefund,
processRefund,
getUserRefunds
}