Files
jurong_circle_black/routes/orders.js
2025-10-24 16:16:21 +08:00

1558 lines
49 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 express = require('express');
const { getDB } = require('../database');
const { auth, adminAuth } = require('../middleware/auth');
const router = express.Router();
// 订单管理路由
// 获取订单列表
router.get('/', auth, async (req, res) => {
try {
const { page = 1, limit = 10, search = '', orderNumber = '', username = '', status = '', startDate = '', endDate = '' } = req.query;
// 确保参数为有效数字
const pageNum = parseInt(page) || 1;
const limitNum = parseInt(limit) || 10;
const offset = (pageNum - 1) * limitNum;
const isAdmin = req.user.role === 'admin';
let whereClause = 'WHERE 1=1';
const params = [];
// 非管理员只能查看自己的订单
if (!isAdmin) {
whereClause += ' AND o.user_id = ?';
params.push(req.user.id);
}
if (search) {
whereClause += ' AND (o.order_no LIKE ? OR u.username LIKE ?)';
params.push(`%${search}%`, `%${search}%`);
}
if (orderNumber) {
whereClause += ' AND o.order_no LIKE ?';
params.push(`%${orderNumber}%`);
}
if (username) {
whereClause += ' AND u.username LIKE ?';
params.push(`%${username}%`);
}
if (status && status.trim()) {
whereClause += ' AND o.status = ?';
params.push(status);
}
if (startDate && startDate.trim()) {
whereClause += ' AND DATE(o.created_at) >= ?';
params.push(startDate);
}
if (endDate && endDate.trim()) {
whereClause += ' AND DATE(o.created_at) <= ?';
params.push(endDate);
}
// 获取总数
const countQuery = `
SELECT COUNT(*) as total
FROM orders as o
LEFT JOIN users u ON o.user_id = u.id
${whereClause}
`;
console.log(countQuery, params);
const [countResult] = await getDB().execute(countQuery, params);
const total = countResult[0].total;
console.log(total, '数量');
// 获取订单列表
const query = `
SELECT
o.id, o.order_no, o.user_id, o.total_amount, o.total_points,
o.status, o.address, o.created_at, o.updated_at,o.total_rongdou,
u.username, o.real_rongdou, o.real_points
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
${whereClause}
ORDER BY o.created_at DESC
LIMIT ${limitNum} OFFSET ${offset}
`;
const [orders] = await getDB().execute(query, [...params]);
// 为每个订单获取商品详情
for (const order of orders) {
const [orderItems] = await getDB().execute(
`SELECT
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
oi.spec_combination_id,
p.name as product_name, p.image_url, p.description,
psc.spec_values as spec_info
FROM order_items oi
LEFT JOIN products p ON oi.product_id = p.id
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
WHERE oi.order_id = ?`,
[order.id]
);
// 处理规格信息
for (const item of orderItems) {
if (item.spec_info) {
try {
item.spec_info = JSON.parse(item.spec_info);
} catch (e) {
item.spec_info = null;
}
}
}
// 处理地址信息
// console.log(order.address,'order.address');
if (order.address) {
try {
order.address = order.address;
} catch (e) {
order.address = null;
}
}
order.items = orderItems;
}
res.json({
success: true,
data: {
orders,
pagination: {
page: pageNum,
limit: limitNum,
total,
pages: Math.ceil(total / limitNum)
}
}
});
router.post('/confirm', auth, async (req, res) => {
const connection = await getDB().getConnection();
try {
await connection.beginTransaction();
const { pre_order_id, address } = req.body;
const userId = req.user.id;
// 验证必填字段
if (!pre_order_id || !address) {
return res.status(400).json({ success: false, message: '预订单ID和收货地址为必填项' });
}
const { recipient_name, phone, province, city, district, detail_address } = address;
if (!recipient_name || !phone || !province || !city || !district || !detail_address) {
return res.status(400).json({ success: false, message: '收货地址信息不完整' });
}
// 获取预订单信息
const [preOrders] = await connection.execute(
`SELECT id, order_no, user_id, total_amount, total_points, total_rongdou, status
FROM orders WHERE id = ? AND user_id = ? AND status = 'pre_order'`,
[pre_order_id, userId]
);
if (preOrders.length === 0) {
await connection.rollback();
return res.status(404).json({ success: false, message: '预订单不存在或已处理' });
}
const preOrder = preOrders[0];
// 获取用户当前积分和融豆
const [users] = await connection.execute(
'SELECT points, rongdou FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
await connection.rollback();
return res.status(404).json({ success: false, message: '用户不存在' });
}
const user = users[0];
// 检查积分和融豆是否足够
if (preOrder.total_points > 0 && user.points < preOrder.total_points) {
await connection.rollback();
return res.status(400).json({ success: false, message: '积分不足' });
}
if (preOrder.total_rongdou > 0 && user.rongdou < preOrder.total_rongdou) {
await connection.rollback();
return res.status(400).json({ success: false, message: '融豆不足' });
}
// 扣除积分
if (preOrder.total_points > 0) {
await connection.execute(
'UPDATE users SET points = points - ? WHERE id = ?',
[preOrder.total_points, userId]
);
// 记录积分变动历史
await connection.execute(
`INSERT INTO points_history (user_id, type, amount, description, order_id)
VALUES (?, 'spend', ?, ?, ?)`,
[userId, preOrder.total_points, `订单消费 - ${preOrder.order_no}`, pre_order_id]
);
}
// 扣除融豆
if (preOrder.total_rongdou > 0) {
await connection.execute(
'UPDATE users SET rongdou = rongdou - ? WHERE id = ?',
[preOrder.total_rongdou, userId]
);
// 记录融豆变动历史
await connection.execute(
`INSERT INTO rongdou_history (user_id, type, amount, description, order_id)
VALUES (?, 'spend', ?, ?, ?)`,
[userId, preOrder.total_rongdou, `订单消费 - ${preOrder.order_no}`, pre_order_id]
);
}
// 更新订单状态和收货地址
const addressStr = JSON.stringify({
recipient_name,
phone,
province,
city,
district,
detail_address
});
await connection.execute(
`UPDATE orders SET status = 'pending', address = ?, updated_at = NOW()
WHERE id = ?`,
[addressStr, pre_order_id]
);
await connection.commit();
res.json({
success: true,
message: '订单确认成功',
data: {
order_id: pre_order_id,
order_no: preOrder.order_no
}
});
} catch (error) {
await connection.rollback();
console.error('确认下单失败:', error);
res.status(500).json({ success: false, message: '确认下单失败' });
} finally {
connection.release();
}
});
router.get('/pre-order/:id', auth, async (req, res) => {
try {
const preOrderId = req.params.id;
const userId = req.user.id;
// 获取预订单基本信息
const [orders] = await getDB().execute(
`SELECT id, order_no, user_id, total_amount, total_points, total_rongdou,
status, created_at FROM orders WHERE id = ? AND user_id = ? AND status = 'pre_order'`,
[preOrderId, userId]
);
if (orders.length === 0) {
return res.status(404).json({ success: false, message: '预订单不存在' });
}
const order = orders[0];
// 获取预订单商品详情
const [orderItems] = await getDB().execute(
`SELECT
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
oi.spec_combination_id,
p.name as product_name, p.image_url, p.description,
psc.spec_values as spec_info
FROM order_items oi
LEFT JOIN products p ON oi.product_id = p.id
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
WHERE oi.order_id = ?`,
[preOrderId]
);
// 处理规格信息
for (const item of orderItems) {
if (item.spec_info) {
try {
item.spec_info = JSON.parse(item.spec_info);
} catch (e) {
item.spec_info = null;
}
}
}
res.json({
success: true,
data: {
...order,
items: orderItems
}
});
} catch (error) {
console.error('获取预订单详情失败:', error);
res.status(500).json({ success: false, message: '获取预订单详情失败' });
}
});
} catch (error) {
console.error('获取订单列表失败:', error);
res.status(500).json({ success: false, message: '获取订单列表失败' });
}
});
router.get('/:id', auth, async (req, res) => {
try {
const { id } = req.params;
const isAdmin = req.user.role === 'admin';
let whereClause = 'WHERE o.id = ?';
const params = [id];
// 非管理员只能查看自己的订单
if (!isAdmin) {
whereClause += ' AND o.user_id = ?';
params.push(req.user.id);
}
const query = `
SELECT
o.id, o.order_no, o.user_id, o.total_amount, o.total_points,
o.status, o.address, o.created_at, o.updated_at, o.total_rongdou,
o.real_rongdou, o.real_points,
u.username, u.phone
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
${whereClause}
`;
const [orders] = await getDB().execute(query, params);
if (orders.length === 0) {
return res.status(404).json({ success: false, message: '订单不存在' });
}
const order = orders[0];
// 获取订单商品详情
const [orderItems] = await getDB().execute(
`SELECT
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
oi.spec_combination_id,
p.name as product_name, p.image_url, p.description,
psc.spec_values as spec_info
FROM order_items oi
LEFT JOIN products p ON oi.product_id = p.id
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
WHERE oi.order_id = ?`,
[order.id]
);
// 处理规格信息
for (const item of orderItems) {
if (item.spec_info) {
try {
item.spec_info = JSON.parse(item.spec_info);
} catch (e) {
item.spec_info = null;
}
}
}
// 处理地址信息
// console.log(order.address,'order.address');
if (order.address) {
try {
order.address = order.address;
} catch (e) {
order.address = null;
}
}
order.items = orderItems;
res.json({
success: true,
data: { order }
});
} catch (error) {
console.error('获取订单详情失败:', error);
res.status(500).json({ success: false, message: '获取订单详情失败' });
}
});
// 创建预订单
router.post('/create-from-cart', auth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
try {
const { cart_item_ids } = req.body;
const user_id = req.user.id;
// 验证必填字段
if (!cart_item_ids || !Array.isArray(cart_item_ids) || cart_item_ids.length === 0) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '请选择要购买的商品' });
}
// 获取购物车商品信息和支付方式
const placeholders = cart_item_ids.map(() => '?').join(',');
const cartQuery = `
SELECT
c.id, c.product_id, c.quantity, c.specification_id,
p.name, p.price, p.points_price, p.rongdou_price, p.stock, p.status, p.payment_methods,
psc.price_adjustment, psc.points_adjustment, psc.rongdou_adjustment, psc.stock as spec_stock
FROM cart_items c
LEFT JOIN products p ON c.product_id = p.id
LEFT JOIN product_spec_combinations psc ON c.specification_id = psc.id
WHERE c.id IN (${placeholders}) AND c.user_id = ?
`;
const [cartItems] = await db.execute(cartQuery, [...cart_item_ids, user_id]);
if (cartItems.length === 0) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '购物车商品不存在' });
}
for (const item of cartItems) {
const [stock] = await getDB().execute(
`SELECT stock FROM product_spec_combinations WHERE id = ?`,
[item.specification_id]
);
if (stock[0].stock < item.quantity) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` });
}
await db.execute(
`UPDATE product_spec_combinations SET stock = stock - ? WHERE id = ?`,
[item.quantity, item.specification_id]
);
await db.execute(
`UPDATE products SET stock = stock - ? WHERE id = ?`,
[item.quantity, item.product_id]
);
}
// 验证商品状态和库存,计算总价和支付方式
let totalAmount = 0;
let totalPoints = 0;
let totalRongdou = 0;
let allPaymentMethods = [];
for (const item of cartItems) {
if (item.status !== 'active') {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: `商品 ${item.name} 已下架` });
}
const availableStock = item.specification_id ? item.spec_stock : item.stock;
if (availableStock < item.quantity) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: `商品 ${item.name} 库存不足` });
}
// 解析商品支付方式
let productPaymentMethods = ['rongdou']; // 默认支付方式
if (item.payment_methods) {
try {
productPaymentMethods = JSON.parse(item.payment_methods);
} catch (e) {
console.error('解析商品支付方式失败:', e);
}
}
console.log(productPaymentMethods,'productPaymentMethods');
allPaymentMethods = allPaymentMethods.concat(productPaymentMethods);
const finalPrice = item.price + (item.price_adjustment || 0);
const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0);
totalAmount += finalPrice * item.quantity;
// 根据支付方式计算积分和融豆需求
// const hasPoints = productPaymentMethods.includes('points') || productPaymentMethods.includes('points_rongdou');
// const hasRongdou = productPaymentMethods.includes('rongdou') || productPaymentMethods.includes('points_rongdou');
// if (hasPoints && !hasRongdou) {
// // 仅积分支付按10000积分=1融豆计算
// totalPoints += finalRongdouPrice * item.quantity * 10000;
// totalRongdou += finalRongdouPrice * item.quantity;
// } else if (!hasPoints && hasRongdou) {
// // 仅融豆支付
// totalRongdou += finalRongdouPrice * item.quantity;
// } else {
// // 组合支付或默认:记录融豆价格,前端可选择支付方式
// totalRongdou += finalRongdouPrice * item.quantity;
// }
totalPoints += item.points_price * item.quantity;
totalRongdou += item.rongdou_price * item.quantity;
// console.log(1111,item)
}
// 去重支付方式
const uniquePaymentMethods = [...new Set(allPaymentMethods)];
console.log('订单支付方式:', uniquePaymentMethods);
// 生成预订单号
const orderNumber = 'PRE' + Date.now() + Math.random().toString(36).substr(2, 5).toUpperCase();
// 创建预订单状态为pre_order
const [orderResult] = await db.execute(
`INSERT INTO orders (order_no, user_id, total_amount, total_points, total_rongdou,
status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 'pre_order', NOW(), NOW())`,
[orderNumber, user_id, totalAmount, totalPoints, totalRongdou]
);
const preOrderId = orderResult.insertId;
// 创建预订单项
for (const item of cartItems) {
const finalPrice = item.price + (item.price_adjustment || 0);
const finalPointsPrice = item.points_price + (item.points_adjustment || 0);
const finalRongdouPrice = item.rongdou_price + (item.rongdou_adjustment || 0);
await db.execute(
`INSERT INTO order_items (order_id, product_id, spec_combination_id, quantity,
price, points, points_price, rongdou, rongdou_price, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())`,
[preOrderId, item.product_id, item.specification_id, item.quantity,
finalPrice, finalPointsPrice * item.quantity, finalPointsPrice, finalRongdouPrice * item.quantity, finalRongdouPrice]
);
}
// 删除购物车中的商品
await db.execute(
`DELETE FROM cart_items WHERE id IN (${placeholders})`,
cart_item_ids
);
await db.query('COMMIT');
res.status(201).json({
success: true,
message: '预订单创建成功',
data: {
preOrderId,
orderNumber,
totalAmount,
totalPoints,
totalRongdou,
paymentMethods: uniquePaymentMethods
}
});
} catch (error) {
await db.query('ROLLBACK');
console.error('创建预订单失败:', error);
res.status(500).json({ success: false, message: '创建预订单失败' });
}
});
/**
* @swagger
* /api/orders/{id}/cancel:
* put:
* summary: 用户取消订单
* tags: [Orders]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 订单ID
* responses:
* 200:
* description: 订单取消成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 400:
* description: 只能取消待处理的订单
* 401:
* description: 未授权
* 404:
* description: 订单不存在
* 500:
* description: 服务器错误
*/
router.put('/:id/cancel', auth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
try {
const orderId = req.params.id;
const userId = req.user.id;
// 检查订单是否存在且属于当前用户
const [orders] = await db.execute(
'SELECT id, user_id, total_points, status FROM orders WHERE id = ? AND user_id = ?',
[orderId, userId]
);
if (orders.length === 0) {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '订单不存在' });
}
const order = orders[0];
if (order.status !== 'pending' && order.status !== 'pre_order') {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '只能取消待处理或待支付的订单' });
}
// 退还用户积分
await db.execute(
'UPDATE users SET points = points + ? WHERE id = ?',
[order.total_points, userId]
);
// 记录积分历史
await db.execute(
`INSERT INTO points_history (user_id, amount, type, description, created_at)
VALUES (?, ?, 'refund', '订单取消退还积分', NOW())`,
[userId, order.total_points]
);
console.log(12345,order.id);
const [orderDetails] = await db.execute(
'SELECT * FROM order_items WHERE order_id = ?',
[order.id]
);
console.log(12345,orderDetails);
// 返还库存
await db.execute(
'UPDATE product_spec_combinations SET stock = stock + ? WHERE id = ?',
[orderDetails[0].quantity, orderDetails[0].spec_combination_id]
);
// 返还销售量
await db.execute(
'UPDATE products SET stock = stock + ? WHERE id = ?',
[orderDetails[0].quantity, orderDetails[0].product_id]
);
// 更新订单状态
await db.execute(
'UPDATE orders SET status = "cancelled", updated_at = NOW() WHERE id = ?',
[orderId]
);
await db.query('COMMIT');
res.json({ success: true, message: '订单已取消' });
} catch (error) {
await db.query('ROLLBACK');
console.error('取消订单失败:', error);
res.status(500).json({ success: false, message: '取消订单失败' });
}
});
/**
* @swagger
* /api/orders/{id}/confirm:
* put:
* summary: 确认收货
* tags: [Orders]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 订单ID
* responses:
* 200:
* description: 确认收货成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 400:
* description: 只能确认已发货的订单
* 401:
* description: 未授权
* 404:
* description: 订单不存在
* 500:
* description: 服务器错误
*/
router.put('/:id/confirm', auth, async (req, res) => {
try {
const orderId = req.params.id;
const userId = req.user.id;
// 检查订单是否存在且属于当前用户
const [orders] = await getDB().execute(
'SELECT * FROM orders WHERE id = ? AND user_id = ?',
[orderId, userId]
);
if (orders.length === 0) {
return res.status(404).json({ success: false, message: '订单不存在' });
}
const order = orders[0];
if (order.status !== 'shipped') {
return res.status(400).json({ success: false, message: '只能确认已发货的订单' });
}
// 佣金分配
const [products] = await getDB().execute(
'SELECT * FROM order_items WHERE order_id = ?',
[orderId]
);
for (const product of products) {
const [producersResult] = await getDB().execute(
'SELECT * FROM products WHERE id = ?',
[product.product_id]
);
await getDB().execute(
'UPDATE products SET sales = sales + ? WHERE id = ?',
[product.quantity, product.product_id]
);
if (producersResult[0].shop_name) {
await getDB().execute(
'UPDATE users SET income = income + ? WHERE id = ?',
[producersResult[0].price * product.quantity, parseInt(producersResult[0].shop_name)]
);
}
}
// 更新订单状态
await getDB().execute(
'UPDATE orders SET status = "completed", updated_at = NOW() WHERE id = ?',
[orderId]
);
res.json({ success: true, message: '确认收货成功' });
} catch (error) {
console.error('确认收货失败:', error);
res.status(500).json({ success: false, message: '确认收货失败' });
}
});
/**
* @swagger
* /api/orders/{id}/status:
* put:
* summary: 更新订单状态(管理员)
* tags: [Orders]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 订单ID
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: string
* enum: [pending, shipped, completed, cancelled]
* description: 订单状态
* required:
* - status
* responses:
* 200:
* description: 订单状态更新成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* message:
* type: string
* 400:
* description: 无效的订单状态
* 401:
* description: 未授权
* 403:
* description: 无管理员权限
* 404:
* description: 订单不存在
* 500:
* description: 服务器错误
*/
router.put('/:id/status', auth, adminAuth, async (req, res) => {
const db = getDB();
await db.query('START TRANSACTION');
try {
const orderId = req.params.id;
const { status } = req.body;
const validStatuses = ['pending', 'shipped', 'completed', 'cancelled'];
if (!validStatuses.includes(status)) {
await db.query('ROLLBACK');
return res.status(400).json({ success: false, message: '无效的订单状态' });
}
// 检查订单是否存在
const [orders] = await db.execute(
'SELECT id, user_id, total_points, total_rongdou, status FROM orders WHERE id = ?',
[orderId]
);
if (orders.length === 0) {
await db.query('ROLLBACK');
return res.status(404).json({ success: false, message: '订单不存在' });
}
const order = orders[0];
// 如果是取消订单,需要退还积分和融豆
if (status === 'cancelled' && order.status !== 'cancelled' && order.status !== 'pre_order') {
// 退还用户积分
if (order.total_points > 0) {
await db.execute(
'UPDATE users SET points = points + ? WHERE id = ?',
[order.total_points, order.user_id]
);
// 记录积分历史
await db.execute(
`INSERT INTO points_history (user_id, amount, type, description, created_at)
VALUES (?, ?, 'earn', '订单取消退还积分', NOW())`,
[order.user_id, order.total_points]
);
}
// 退还用户融豆
if (order.total_rongdou > 0) {
await db.execute(
'UPDATE users SET balance = balance - ? WHERE id = ?',
[order.total_rongdou, order.user_id]
);
// 记录融豆历史
await db.execute(
`INSERT INTO rongdou_history (user_id, amount, type, description, created_at)
VALUES (?, ?, 'earn', '订单取消退还融豆', NOW())`,
[order.user_id, order.total_rongdou]
);
}
}
// 更新订单状态
await db.execute(
'UPDATE orders SET status = ?, updated_at = NOW() WHERE id = ?',
[status, orderId]
);
await db.query('COMMIT');
res.json({ success: true, message: '订单状态已更新' });
} catch (error) {
await db.query('ROLLBACK');
console.error('更新订单状态失败:', error);
res.status(500).json({ success: false, message: '更新订单状态失败' });
}
});
/**
* @swagger
* /api/orders/pending-payment/{id}:
* get:
* summary: 获取待支付预订单详情
* tags: [Orders]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: integer
* description: 预订单ID
* responses:
* 200:
* description: 获取预订单详情成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* id:
* type: integer
* order_no:
* type: string
* total_amount:
* type: integer
* total_points:
* type: integer
* total_rongdou:
* type: integer
* status:
* type: string
* created_at:
* type: string
* items:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* product_id:
* type: integer
* product_name:
* type: string
* quantity:
* type: integer
* price:
* type: integer
* points_price:
* type: integer
* rongdou_price:
* type: integer
* spec_info:
* type: object
* 401:
* description: 未授权
* 404:
* description: 预订单不存在
* 500:
* description: 服务器错误
*/
router.get('/pending-payment/:id', auth, async (req, res) => {
try {
const preOrderId = req.params.id;
const userId = req.user.id;
// 获取预订单基本信息
const [orders] = await getDB().execute(
`SELECT id, order_no, user_id, total_amount, total_points, total_rongdou,
status, created_at FROM orders WHERE id = ? AND user_id = ? AND status = 'pre_order'`,
[preOrderId, userId]
);
if (orders.length === 0) {
return res.status(404).json({ success: false, message: '预订单不存在' });
}
const order = orders[0];
// 获取预订单商品详情
const [orderItems] = await getDB().execute(
`SELECT
oi.id, oi.product_id, oi.quantity, oi.price, oi.points_price, oi.rongdou_price,
oi.spec_combination_id,
p.name as product_name, p.image_url, p.description, p.payment_methods,
psc.spec_values as spec_info
FROM order_items oi
LEFT JOIN products p ON oi.product_id = p.id
LEFT JOIN product_spec_combinations psc ON oi.spec_combination_id = psc.id
WHERE oi.order_id = ?`,
[preOrderId]
);
// 处理规格信息
for (const item of orderItems) {
if (item.spec_info) {
try {
item.spec_info = JSON.parse(item.spec_info);
} catch (e) {
item.spec_info = null;
}
}
}
res.json({
success: true,
data: {
...order,
items: orderItems.map(item => ({
...item,
payment_methods: JSON.parse(item.payment_methods)
}))
}
});
} catch (error) {
console.error('获取预订单详情失败:', error);
res.status(500).json({ success: false, message: '获取预订单详情失败' });
}
});
/**
* @swagger
* /api/orders/confirm-payment:
* post:
* summary: 确认支付订单
* description: |
* 根据商品支付方式确认订单支付:
* - 仅积分支付按10000积分=1融豆的比例扣除积分
* - 仅融豆支付:直接扣除融豆
* - 组合支付优先扣除积分按10000:1转换不足部分扣除融豆
* tags: [Orders]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - order_id
* - address_id
* properties:
* order_id:
* type: integer
* description: 订单ID
* example: 123
* address_id:
* type: integer
* description: 收货地址ID
* example: 456
* responses:
* 200:
* description: 确认支付成功
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* message:
* type: string
* example: "订单支付成功"
* data:
* type: object
* properties:
* order_id:
* type: integer
* example: 123
* order_no:
* type: string
* example: "ORD20240101123456"
* 400:
* description: 请求参数错误或余额不足
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* enum: ["订单ID和收货地址ID为必填项", "积分不足", "融豆不足", "积分和融豆余额不足", "商品支付方式配置错误"]
* 401:
* description: 未授权
* 404:
* description: 订单或地址不存在
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* enum: ["订单不存在或已处理", "收货地址不存在", "用户不存在"]
* 500:
* description: 服务器错误
*/
router.post('/confirm-payment', auth, async (req, res) => {
const connection = await getDB().getConnection();
try {
await connection.beginTransaction();
const { orderId: order_id, addressId: address_id, couponRecordId, paymentMethod, beansAmount, pointsAmount } = req.body;
const userId = req.user.id;
// 验证必填字段
if (!order_id || !address_id) {
return res.status(400).json({ success: false, message: '订单ID和收货地址ID为必填项' });
}
// 获取订单信息和商品支付方式
const [orders] = await connection.execute(
`SELECT o.id, o.order_no, o.user_id, o.total_amount, o.total_points, o.total_rongdou, o.status,
GROUP_CONCAT(DISTINCT p.payment_methods) as payment_methods_list
FROM orders o
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE o.id = ? AND o.user_id = ? AND o.status = 'pre_order'
GROUP BY o.id`,
[order_id, userId]
);
if (orders.length === 0) {
await connection.rollback();
return res.status(404).json({ success: false, message: '订单不存在或已处理' });
}
const order = orders[0];
// 解析支付方式
// let allPaymentMethods = [];
// // console.log(typeof order.payment_methods_list);
// if (order.payment_methods_list) {
// try {
// // 数据库中存储的是序列化的JSON字符串直接解析
// allPaymentMethods = JSON.parse(JSON.parse(order.payment_methods_list));
// } catch (e) {
// console.error('解析支付方式失败:', e, 'raw data:', order.payment_methods_list);
// allPaymentMethods = [];
// }
// }
// // 去重支付方式
// allPaymentMethods = [...new Set(allPaymentMethods)];
// // 判断支付方式类型
// const hasPoints = allPaymentMethods.includes('points') || allPaymentMethods.includes('points_rongdou');
// const hasRongdou = allPaymentMethods.includes('rongdou') || allPaymentMethods.includes('points_rongdou');
// const isComboPayment = allPaymentMethods.includes('points_rongdou');
// console.log('订单支付方式:', allPaymentMethods, { hasPoints, hasRongdou, isComboPayment });
// 获取收货地址信息
const [addresses] = await connection.execute(
'SELECT id, receiver_name, receiver_phone, province, city, district, detailed_address as detail_address FROM user_addresses WHERE id = ? AND user_id = ?',
[address_id, userId]
);
if (addresses.length === 0) {
await connection.rollback();
return res.status(404).json({ success: false, message: '收货地址不存在' });
}
const address = addresses[0];
// 获取用户当前积分和融豆
const [users] = await connection.execute(
'SELECT points, balance FROM users WHERE id = ?',
[userId]
);
if (users.length === 0) {
await connection.rollback();
return res.status(404).json({ success: false, message: '用户不存在' });
}
const user = users[0];
if (user.balance > 0) {
return res.status(400).json({ success: false, message: '融豆不足' });
}
user.balance = Math.abs(user.balance);
console.log(123456,paymentMethod)
// 开始扣钱
switch (paymentMethod) {
case 'points':
// 积分支付逻辑
if (user.points < order.total_points) {
await connection.rollback();
return res.status(400).json({ success: false, message: '积分不足' });
}
console.log(1234567,paymentMethod,userId,order.total_points)
await connection.execute(
'UPDATE users SET points = points - ? WHERE id = ?',
[order.total_points, userId]
);
// 记录积分变动历史
await connection.execute(
`INSERT INTO points_history (user_id, type, amount, description, order_id)
VALUES (?, 'spend', ?, ?, ?)`,
[userId, order.total_points, `订单支付 - ${order.order_no}`, order_id]
);
// 供应商分佣
// await connection.execute(
// 'UPDATE users SET points = points + ? WHERE id = ?',
// [order.total_points * 0.1, order.shop_id]
// );
// console.log(123,order)
break;
case 'beans':
// 融豆支付逻辑
if (user.balance < order.total_rongdou) {
await connection.rollback();
return res.status(400).json({ success: false, message: '融豆不足' });
}
await connection.execute(
'UPDATE users SET balance = balance + ? WHERE id = ?',
[order.total_rongdou, userId]
);
// 记录融豆变动历史
await connection.execute(
`INSERT INTO points_history (user_id, type, amount, description, order_id)
VALUES (?, 'spend', ?, ?, ?)`,
[userId, order.total_rongdou, `订单支付 - ${order.order_no}`, order_id]
);
break;
case 'mixed':
// 积分和融豆组合支付逻辑
if(user.points < order.total_points) {
const needPoints = (user.points/10000).floor(0) * 10000;
const needBeans = order.total_rongdou - needPoints/10000;
if(user.balance < needBeans) {
await connection.rollback();
return res.status(400).json({ success: false, message: '融豆不足' });
}
await connection.execute(
'UPDATE users SET points = points - ? WHERE id = ?',
[needPoints, userId]
);
// 记录积分变动历史
await connection.execute(
`INSERT INTO points_history (user_id, type, amount, description, order_id)
VALUES (?, 'spend', ?, ?, ?)`,
[userId, needPoints, `订单支付 - ${order.order_no}`, order_id]
);
await connection.execute(
'UPDATE users SET balance = balance + ? WHERE id = ?',
[needBeans, userId]
);
// 记录融豆变动历史
await connection.execute(
`INSERT INTO points_history (user_id, type, amount, description, order_id)
VALUES (?, 'spend', ?, ?, ?)`,
[userId, needBeans, `订单支付 - ${order.order_no}`, order_id]
);
} else {
await connection.execute(
'UPDATE users SET points = points - ? WHERE id = ?',
[order.total_points, userId]
);
// 记录积分变动历史
await connection.execute(
`INSERT INTO points_history (user_id, type, amount, description, order_id)
VALUES (?, 'spend', ?, ?, ?)`,
[userId, order.total_points, `订单支付 - ${order.order_no}`, order_id]
);
}
break;
case 'alipay_wap':
console.log(123465)
break;
default:
await connection.rollback();
return res.status(400).json({ success: false, message: '支付方式配置错误' });
}
// // 根据支付方式处理扣费逻辑
// let totalRongdouNeeded = order.total_rongdou; // 需要的融豆总数
// let pointsToDeduct = 0; // 需要扣除的积分
// let rongdouToDeduct = 0; // 需要扣除的融豆
// if (!hasRongdou && !hasPoints) {
// await connection.rollback();
// return res.status(400).json({ success: false, message: '商品支付方式配置错误' });
// }
// if (hasPoints && !hasRongdou) {
// // 只支持积分支付按10000积分=1融豆转换
// const pointsNeeded = totalRongdouNeeded * 10000;
// if (user.points < pointsNeeded) {
// await connection.rollback();
// return res.status(400).json({ success: false, message: '积分不足' });
// }
// pointsToDeduct = pointsNeeded;
// rongdouToDeduct = 0;
// } else if (!hasPoints && hasRongdou) {
// // 只支持融豆支付
// if (user.balance < totalRongdouNeeded) {
// await connection.rollback();
// return res.status(400).json({ success: false, message: '融豆不足' });
// }
// pointsToDeduct = 0;
// rongdouToDeduct = totalRongdouNeeded;
// } else if (hasPoints && hasRongdou) {
// // 组合支付:先扣积分,不足部分用融豆
// const availablePointsInRongdou = Math.floor(user.points / 10000); // 积分可转换的融豆数
// if (availablePointsInRongdou >= totalRongdouNeeded) {
// // 积分足够支付全部
// pointsToDeduct = totalRongdouNeeded * 10000;
// rongdouToDeduct = 0;
// } else {
// // 积分不够,需要组合支付
// pointsToDeduct = availablePointsInRongdou * 10000;
// rongdouToDeduct = totalRongdouNeeded - availablePointsInRongdou;
// if (user.balance < rongdouToDeduct) {
// await connection.rollback();
// return res.status(400).json({ success: false, message: '积分和融豆余额不足' });
// }
// }
// }
// console.log('扣费计算:', { totalRongdouNeeded, pointsToDeduct, rongdouToDeduct, userPoints: user.points, userBalance: user.balance });
// 扣除积分
// if (pointsToDeduct > 0) {
// await connection.execute(
// 'UPDATE users SET points = points - ? WHERE id = ?',
// [pointsToDeduct, userId]
// );
// // 记录积分变动历史
// await connection.execute(
// `INSERT INTO points_history (user_id, type, amount, description, order_id)
// VALUES (?, 'spend', ?, ?, ?)`,
// [userId, pointsToDeduct, `订单支付 - ${order.order_no}`, order_id]
// );
// }
// // 扣除融豆
// if (rongdouToDeduct > 0) {
// await connection.execute(
// 'UPDATE users SET balance = balance + ? WHERE id = ?',
// [rongdouToDeduct, userId]
// );
// // 记录融豆变动历史
// await connection.execute(
// `INSERT INTO rongdou_history (user_id, type, amount, description, order_id)
// VALUES (?, 'spend', ?, ?, ?)`,
// [userId, rongdouToDeduct, `订单支付 - ${order.order_no}`, order_id]
// );
// }
// 更新优惠券记录
if (couponRecordId) {
await connection.execute(
'UPDATE coupon_use SET use_time = NOW(), order_id = ? WHERE id = ?',
[order_id, couponRecordId]
);
}
if (beansAmount > 0) {
await connection.execute(
'UPDATE orders SET real_rongdou = ? WHERE id = ?',
[beansAmount, order_id]
);
}
if (pointsAmount > 0) {
await connection.execute(
'UPDATE orders SET real_points = ? WHERE id = ?',
[pointsAmount, order_id]
);
}
// 更新订单状态和收货地址
const addressStr = JSON.stringify({
recipient_name: address.receiver_name,
phone: address.receiver_phone,
province: address.province,
city: address.city,
district: address.district,
detail_address: address.detail_address
});
await connection.execute(
`UPDATE orders SET status = 'pending', address = ?, updated_at = NOW(), coupon_record_id = ?, payment_method = ?
WHERE id = ?`,
[addressStr, couponRecordId === undefined ? null : couponRecordId, paymentMethod, order_id]
);
// 减组合库存
// await connection.execute(
// 'UPDATE product_spec_combinations SET stock = stock - 1 WHERE id = ?',
// [order.product_combination_id]
// );
await connection.commit();
res.json({
success: true,
message: '订单支付成功',
data: {
order_id: order_id,
order_no: order.order_no
}
});
} catch (error) {
await connection.rollback();
console.error('确认支付失败:', error);
res.status(500).json({ success: false, message: '确认支付失败' });
} finally {
connection.release();
}
});
/**
* @swagger
* /api/orders/stats:
* get:
* summary: 获取订单统计信息(管理员权限)
* tags: [Orders]
* security:
* - bearerAuth: []
* responses:
* 200:
* description: 成功获取订单统计信息
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* data:
* type: object
* properties:
* totalOrders:
* type: integer
* description: 总订单数
* pendingOrders:
* type: integer
* description: 待发货订单数
* completedOrders:
* type: integer
* description: 已完成订单数
* monthOrders:
* type: integer
* description: 本月新增订单数
* monthGrowthRate:
* type: number
* description: 月增长率
* totalPointsConsumed:
* type: number
* description: 总积分消费
* 401:
* description: 未授权
* 403:
* description: 无管理员权限
* 500:
* description: 服务器错误
*/
router.get('/stats', auth, adminAuth, async (req, res) => {
try {
// 总订单数
const [totalOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders');
// 待发货订单数
const [pendingOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "pending"');
// 已完成订单数
const [completedOrders] = await getDB().execute('SELECT COUNT(*) as count FROM orders WHERE status = "completed"');
// 本月新增订单
const [monthOrders] = await getDB().execute(
'SELECT COUNT(*) as count FROM orders WHERE YEAR(created_at) = YEAR(NOW()) AND MONTH(created_at) = MONTH(NOW())'
);
// 上月订单数(用于计算增长率)
const [lastMonthOrders] = await getDB().execute(
'SELECT COUNT(*) as count FROM orders WHERE YEAR(created_at) = YEAR(DATE_SUB(NOW(), INTERVAL 1 MONTH)) AND MONTH(created_at) = MONTH(DATE_SUB(NOW(), INTERVAL 1 MONTH))'
);
// 计算月增长率
const lastMonthCount = lastMonthOrders[0].count;
const currentMonthCount = monthOrders[0].count;
let monthGrowthRate = 0;
if (lastMonthCount > 0) {
monthGrowthRate = ((currentMonthCount - lastMonthCount) / lastMonthCount * 100).toFixed(1);
}
// 总积分消费
const [totalPointsConsumed] = await getDB().execute('SELECT SUM(points_cost) as total FROM orders WHERE status != "cancelled"');
res.json({
success: true,
data: {
totalOrders: totalOrders[0].count,
pendingOrders: pendingOrders[0].count,
completedOrders: completedOrders[0].count,
monthOrders: monthOrders[0].count,
monthGrowthRate: parseFloat(monthGrowthRate),
totalPointsConsumed: totalPointsConsumed[0].total || 0
}
});
} catch (error) {
console.error('获取订单统计失败:', error);
res.status(500).json({ success: false, message: '获取订单统计失败' });
}
});
module.exports = router;