278 lines
7.4 KiB
JavaScript
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
|
|
}
|
|
|