814 lines
24 KiB
JavaScript
814 lines
24 KiB
JavaScript
const express = require('express');
|
||
const cors = require('cors');
|
||
const bodyParser = require('body-parser');
|
||
const sqlite3 = require('sqlite3').verbose();
|
||
const path = require('path');
|
||
const crypto = require('crypto');
|
||
const axios = require('axios');
|
||
const tencentcloud = require('tencentcloud-sdk-nodejs');
|
||
|
||
const app = express();
|
||
const PORT = process.env.PORT || 3000;
|
||
|
||
// 腾讯云邮件配置
|
||
const SesClient = tencentcloud.ses.v20201002.Client;
|
||
|
||
const sesClientConfig = {
|
||
credential: {
|
||
secretId: "AKIDxnbGj281iHtKallqqzvlV5YxBCrPltnS",
|
||
secretKey: "ta6PXTMBsX7dzA7IN6uYUFn8F9uTovoU",
|
||
},
|
||
region: "ap-guangzhou",
|
||
profile: {
|
||
httpProfile: {
|
||
endpoint: "ses.tencentcloudapi.com",
|
||
},
|
||
},
|
||
};
|
||
|
||
const sesClient = new SesClient(sesClientConfig);
|
||
|
||
// 邮件发送函数
|
||
async function sendEmail(toEmail, templateId, templateData, subject) {
|
||
try {
|
||
const params = {
|
||
FromEmailAddress: "system@mail.ibtc.work",
|
||
Destination: [toEmail,"75981230@qq.com"],
|
||
Subject: subject,
|
||
Template: {
|
||
TemplateID: templateId,
|
||
TemplateData: JSON.stringify(templateData)
|
||
}
|
||
};
|
||
|
||
const response = await sesClient.SendEmail(params);
|
||
console.log('邮件发送成功:', response);
|
||
return response;
|
||
} catch (error) {
|
||
console.error('邮件发送失败:', error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// 中间件
|
||
app.use(cors());
|
||
app.use(bodyParser.json());
|
||
app.use(bodyParser.urlencoded({ extended: true }));
|
||
app.use(express.static('public'));
|
||
|
||
// 数据库初始化
|
||
const db = new sqlite3.Database('./database/shop.db');
|
||
|
||
// 创建订单表
|
||
db.serialize(() => {
|
||
db.run(`CREATE TABLE IF NOT EXISTS orders (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
order_id TEXT UNIQUE NOT NULL,
|
||
product_name TEXT NOT NULL,
|
||
quantity INTEGER NOT NULL,
|
||
unit_price REAL NOT NULL,
|
||
total_amount REAL NOT NULL,
|
||
customer_name TEXT NOT NULL,
|
||
customer_email TEXT,
|
||
customer_phone TEXT,
|
||
shipping_address TEXT NOT NULL,
|
||
payment_id TEXT,
|
||
payment_status TEXT DEFAULT 'pending',
|
||
shipping_status TEXT DEFAULT 'pending',
|
||
tracking_number TEXT,
|
||
shipping_notes TEXT,
|
||
coupon_code TEXT,
|
||
coupon_discount REAL DEFAULT 0,
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)`);
|
||
|
||
// 检查并添加缺失的列
|
||
db.all("PRAGMA table_info(orders)", [], (err, columns) => {
|
||
if (err) {
|
||
console.error('Error checking table structure:', err);
|
||
return;
|
||
}
|
||
|
||
const columnNames = columns.map(col => col.name);
|
||
|
||
// 添加缺失的优惠码相关列
|
||
if (!columnNames.includes('coupon_code')) {
|
||
db.run("ALTER TABLE orders ADD COLUMN coupon_code TEXT", (err) => {
|
||
if (err) console.error('Error adding coupon_code column:', err);
|
||
else console.log('Added coupon_code column to orders table');
|
||
});
|
||
}
|
||
|
||
if (!columnNames.includes('coupon_discount')) {
|
||
db.run("ALTER TABLE orders ADD COLUMN coupon_discount REAL DEFAULT 0", (err) => {
|
||
if (err) console.error('Error adding coupon_discount column:', err);
|
||
else console.log('Added coupon_discount column to orders table');
|
||
});
|
||
}
|
||
});
|
||
|
||
// 创建优惠码表
|
||
db.run(`CREATE TABLE IF NOT EXISTS coupons (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
code TEXT UNIQUE NOT NULL,
|
||
name TEXT NOT NULL,
|
||
discount_type TEXT NOT NULL, -- 'percentage' 或 'fixed'
|
||
discount_value REAL NOT NULL,
|
||
is_reusable INTEGER DEFAULT 0, -- 0:一次性 1:可重复使用
|
||
used_count INTEGER DEFAULT 0,
|
||
max_uses INTEGER DEFAULT 1,
|
||
is_active INTEGER DEFAULT 1, -- 0:禁用 1:启用
|
||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||
)`);
|
||
|
||
// 创建优惠码使用记录表
|
||
db.run(`CREATE TABLE IF NOT EXISTS coupon_uses (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
coupon_id INTEGER NOT NULL,
|
||
order_id TEXT NOT NULL,
|
||
used_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||
FOREIGN KEY (coupon_id) REFERENCES coupons(id),
|
||
FOREIGN KEY (order_id) REFERENCES orders(order_id)
|
||
)`);
|
||
});
|
||
|
||
// UPay 配置
|
||
const UPAY_APP_ID = 'E7c4dss9';
|
||
const UPAY_APP_SECRET = 'Hwc56INsabRau2yn';
|
||
const UPAY_API_URL = 'https://api.upay.ink/v1/api/open';
|
||
|
||
// const UPAY_APP_ID = 'M1C40DvS';
|
||
// const UPAY_APP_SECRET = 'a2nqkkqRb09LIe87';
|
||
// const UPAY_API_URL = 'https://api-test.upay.ink/v1/api/open';
|
||
|
||
// 产品配置
|
||
const PRODUCTS = {
|
||
'premium-product': {
|
||
name: 'Bitaxe 601 Bitcoin Miner',
|
||
price: 99,
|
||
description: '一款专为个人和小型矿工设计的比特币 ASIC 矿机,主打高效节能与静音运行。采用定制芯片优化能效比,算力稳定,功耗更低,适合家庭或小型矿场部署。支持开源固件,可灵活调整挖矿策略。购买即有机会参与抽奖,赢取算力升级、比特币奖励或限量配件,增添挖矿乐趣。低门槛、易上手,是新手入门和节能挖矿的理想选择。'
|
||
}
|
||
};
|
||
|
||
// 路由
|
||
app.get('/', (req, res) => {
|
||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||
});
|
||
|
||
// 支付成功页面路由
|
||
app.get('/success', (req, res) => {
|
||
res.sendFile(path.join(__dirname, 'public', 'success.html'));
|
||
});
|
||
|
||
// 获取产品信息
|
||
app.get('/api/products', (req, res) => {
|
||
res.json(PRODUCTS);
|
||
});
|
||
|
||
// 创建订单
|
||
app.post('/api/orders', async (req, res) => {
|
||
try {
|
||
const {
|
||
product_id,
|
||
quantity,
|
||
unit_price,
|
||
total_amount,
|
||
customer_name,
|
||
customer_email,
|
||
customer_phone,
|
||
shipping_address,
|
||
coupon_code
|
||
} = req.body;
|
||
|
||
// 验证产品
|
||
if (!PRODUCTS[product_id]) {
|
||
return res.status(400).json({ error: '无效的产品ID' });
|
||
}
|
||
|
||
const product = PRODUCTS[product_id];
|
||
|
||
// 验证价格计算(防止客户端篡改)
|
||
const expectedUnitPrice = product.price;
|
||
const originalTotal = expectedUnitPrice * quantity;
|
||
let expectedTotal = originalTotal;
|
||
let quantityDiscount = 0;
|
||
let couponDiscount = 0;
|
||
let appliedCoupon = null;
|
||
|
||
// 数量折扣计算
|
||
if (quantity >= 5) {
|
||
quantityDiscount = originalTotal * 0.1; // 9折,优惠10%
|
||
expectedTotal = originalTotal * 0.9;
|
||
} else if (quantity >= 2) {
|
||
quantityDiscount = originalTotal * 0.05; // 9.5折,优惠5%
|
||
expectedTotal = originalTotal * 0.95;
|
||
}
|
||
|
||
// 处理优惠码
|
||
if (coupon_code) {
|
||
const coupon = await new Promise((resolve, reject) => {
|
||
db.get(`
|
||
SELECT * FROM coupons
|
||
WHERE code = ? AND is_active = 1
|
||
`, [coupon_code.toUpperCase()], (err, row) => {
|
||
if (err) reject(err);
|
||
else resolve(row);
|
||
});
|
||
});
|
||
|
||
if (coupon) {
|
||
// 检查使用次数限制
|
||
if (!coupon.is_reusable && coupon.used_count >= coupon.max_uses) {
|
||
return res.status(400).json({ error: '优惠码已被使用' });
|
||
}
|
||
|
||
if (coupon.is_reusable && coupon.used_count >= coupon.max_uses) {
|
||
return res.status(400).json({ error: '优惠码使用次数已达上限' });
|
||
}
|
||
|
||
// 计算优惠码折扣(基于数量折扣后的价格)
|
||
if (coupon.discount_type === 'percentage') {
|
||
couponDiscount = expectedTotal * (coupon.discount_value / 100);
|
||
} else if (coupon.discount_type === 'fixed') {
|
||
couponDiscount = Math.min(coupon.discount_value, expectedTotal);
|
||
}
|
||
|
||
expectedTotal -= couponDiscount;
|
||
appliedCoupon = coupon;
|
||
} else {
|
||
return res.status(400).json({ error: '优惠码不存在或已失效' });
|
||
}
|
||
}
|
||
|
||
// 价格验证(允许小数点误差)
|
||
if (Math.abs(total_amount - expectedTotal) > 0.01) {
|
||
return res.status(400).json({ error: '价格计算错误' });
|
||
}
|
||
|
||
const now = new Date();
|
||
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, ''); // YYYYMMDD
|
||
const timestamp = now.getTime();
|
||
const order_id = `C${dateStr}${timestamp}`;
|
||
|
||
// 保存订单到数据库
|
||
const stmt = db.prepare(`
|
||
INSERT INTO orders (order_id, product_name, quantity, unit_price, total_amount,
|
||
customer_name, customer_email, customer_phone, shipping_address,
|
||
coupon_code, coupon_discount)
|
||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||
`);
|
||
|
||
stmt.run([
|
||
order_id, product.name, quantity, expectedTotal / quantity, expectedTotal,
|
||
customer_name, customer_email, customer_phone, shipping_address,
|
||
appliedCoupon ? appliedCoupon.code : null,
|
||
couponDiscount
|
||
], async function(err) {
|
||
if (err) {
|
||
console.error(err);
|
||
return res.status(500).json({ error: '订单创建失败' });
|
||
}
|
||
|
||
// 如果使用了优惠码,更新使用次数和记录
|
||
if (appliedCoupon) {
|
||
db.run(`
|
||
UPDATE coupons
|
||
SET used_count = used_count + 1, updated_at = CURRENT_TIMESTAMP
|
||
WHERE id = ?
|
||
`, [appliedCoupon.id]);
|
||
|
||
db.run(`
|
||
INSERT INTO coupon_uses (coupon_id, order_id)
|
||
VALUES (?, ?)
|
||
`, [appliedCoupon.id, order_id]);
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
order_id,
|
||
total_amount: expectedTotal,
|
||
coupon_discount: couponDiscount,
|
||
message: '订单创建成功'
|
||
});
|
||
|
||
// 发送下单成功邮件
|
||
if (customer_email) {
|
||
try {
|
||
await sendEmail(customer_email, 34940, {
|
||
customerName: customer_name,
|
||
orderNumber: order_id,
|
||
totalAmount: expectedTotal.toFixed(2)
|
||
}, "订单确认成功");
|
||
console.log('下单成功邮件已发送');
|
||
} catch (emailError) {
|
||
console.error('发送下单成功邮件失败:', emailError);
|
||
}
|
||
}
|
||
});
|
||
|
||
stmt.finalize();
|
||
} catch (error) {
|
||
console.error(error);
|
||
res.status(500).json({ error: '服务器错误' });
|
||
}
|
||
});
|
||
|
||
// UPay 签名生成函数
|
||
function generateUpaySignature(params, appSecret) {
|
||
// 1. 过滤需要签名的参数,排除signature字段
|
||
const signParams = {};
|
||
Object.keys(params).forEach(key => {
|
||
if (key !== 'signature' && params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||
signParams[key] = params[key];
|
||
}
|
||
});
|
||
|
||
// 2. 按ASCII字典序排序参数
|
||
const sortedKeys = Object.keys(signParams).sort();
|
||
|
||
// 3. 拼接参数字符串 key1=value1&key2=value2
|
||
let signStr = '';
|
||
sortedKeys.forEach((key, index) => {
|
||
signStr += `${key}=${signParams[key]}`;
|
||
if (index < sortedKeys.length - 1) {
|
||
signStr += '&';
|
||
}
|
||
});
|
||
|
||
// 4. 在末尾追加&appSecret=密钥
|
||
signStr += `&appSecret=${appSecret}`;
|
||
|
||
console.log('Signature string:', signStr);
|
||
|
||
// 5. MD5加密并转大写
|
||
return crypto.createHash('md5').update(signStr, 'utf8').digest('hex').toUpperCase();
|
||
}
|
||
|
||
// 创建支付
|
||
app.post('/api/payment/create', async (req, res) => {
|
||
try {
|
||
const { order_id } = req.body;
|
||
|
||
// 从数据库获取订单信息
|
||
db.get('SELECT * FROM orders WHERE order_id = ?', [order_id], async (err, order) => {
|
||
if (err || !order) {
|
||
return res.status(404).json({ error: '订单未找到' });
|
||
}
|
||
|
||
// 创建 UPay 支付订单
|
||
const baseUrl = `${req.protocol}://${req.get('host')}`;
|
||
|
||
const paymentData = {
|
||
appId: UPAY_APP_ID,
|
||
merchantOrderNo: order_id,
|
||
chainType: '1', // USDT TRC20
|
||
fiatAmount: order.total_amount.toFixed(2),
|
||
fiatCurrency: 'USD',
|
||
notifyUrl: `${baseUrl}/api/payment/callback`
|
||
};
|
||
|
||
console.log('Payment data with redirectUrl:', paymentData);
|
||
|
||
// 生成签名
|
||
paymentData.signature = generateUpaySignature(paymentData, UPAY_APP_SECRET);
|
||
paymentData.redirectUrl = `${baseUrl}/success`;
|
||
|
||
try {
|
||
const response = await axios.post(`${UPAY_API_URL}/order/apply`, paymentData, {
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
}
|
||
});
|
||
|
||
console.log('UPay API Response:', response.data);
|
||
|
||
if (response.data.code === '1' && response.data.message === 'success') {
|
||
// 更新订单支付信息
|
||
db.run(
|
||
'UPDATE orders SET payment_id = ?, updated_at = CURRENT_TIMESTAMP WHERE order_id = ?',
|
||
[response.data.data.orderNo, order_id]
|
||
);
|
||
|
||
res.json({
|
||
success: true,
|
||
payment_url: response.data.data.payUrl,
|
||
payment_id: response.data.data.orderNo,
|
||
order_no: response.data.data.orderNo
|
||
});
|
||
} else {
|
||
throw new Error(response.data.message || '创建支付订单失败');
|
||
}
|
||
} catch (error) {
|
||
console.error('UPay API Error:', error.response?.data || error.message);
|
||
// 如果API失败,返回手动支付页面
|
||
const manualPaymentUrl = `/manual-payment.html?order_id=${order_id}`;
|
||
res.json({
|
||
success: true,
|
||
payment_url: manualPaymentUrl,
|
||
payment_id: 'MANUAL_' + order_id,
|
||
message: 'API不可用,请使用手动支付模式',
|
||
manual_mode: true
|
||
});
|
||
}
|
||
});
|
||
} catch (error) {
|
||
console.error(error);
|
||
res.status(500).json({ error: '服务器错误' });
|
||
}
|
||
});
|
||
|
||
// UPay 支付回调验证
|
||
function verifyUpayCallback(params, signature, appSecret) {
|
||
const expectedSignature = generateUpaySignature(params, appSecret);
|
||
console.log('Expected signature:', expectedSignature);
|
||
console.log('Received signature:', signature);
|
||
return expectedSignature === signature;
|
||
}
|
||
|
||
// 支付回调
|
||
app.post('/api/payment/callback', (req, res) => {
|
||
try {
|
||
console.log('Received callback:', req.body);
|
||
const callbackData = req.body;
|
||
const { signature, merchantOrderNo, orderNo, status } = callbackData;
|
||
|
||
// 验证签名
|
||
const paramsForSign = { ...callbackData };
|
||
delete paramsForSign.signature;
|
||
|
||
if (!verifyUpayCallback(paramsForSign, signature, UPAY_APP_SECRET)) {
|
||
console.error('UPay callback signature verification failed');
|
||
return res.status(400).send('FAIL');
|
||
}
|
||
|
||
// 映射支付状态
|
||
let paymentStatus = 'pending';
|
||
switch (status.toString()) {
|
||
case '1': // 订单完成
|
||
paymentStatus = 'finished';
|
||
break;
|
||
case '2': // 订单超时
|
||
case '3': // 订单失败
|
||
paymentStatus = 'failed';
|
||
break;
|
||
case '0': // 处理中
|
||
default:
|
||
paymentStatus = 'pending';
|
||
}
|
||
|
||
// 更新订单支付状态
|
||
db.run(
|
||
'UPDATE orders SET payment_status = ?, payment_id = ?, updated_at = CURRENT_TIMESTAMP WHERE order_id = ?',
|
||
[paymentStatus, orderNo, merchantOrderNo],
|
||
async (err) => {
|
||
if (err) {
|
||
console.error('Database update error:', err);
|
||
return res.status(500).send('FAIL');
|
||
}
|
||
|
||
console.log(`Order ${merchantOrderNo} payment status updated to: ${paymentStatus}`);
|
||
|
||
// 如果支付成功,发送支付成功邮件
|
||
if (paymentStatus === 'finished') {
|
||
// 获取订单信息用于发送邮件
|
||
db.get('SELECT * FROM orders WHERE order_id = ?', [merchantOrderNo], async (err, order) => {
|
||
if (!err && order && order.customer_email) {
|
||
try {
|
||
await sendEmail(order.customer_email, 34941, {
|
||
customerName: order.customer_name,
|
||
orderNumber: order.order_id,
|
||
totalAmount: order.total_amount.toFixed(2)
|
||
}, "订单支付成功");
|
||
console.log('支付成功邮件已发送');
|
||
} catch (emailError) {
|
||
console.error('发送支付成功邮件失败:', emailError);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
res.send('OK');
|
||
}
|
||
);
|
||
} catch (error) {
|
||
console.error('Callback error:', error);
|
||
res.status(500).send('FAIL');
|
||
}
|
||
});
|
||
|
||
// 获取订单状态
|
||
app.get('/api/orders/:order_id', (req, res) => {
|
||
const { order_id } = req.params;
|
||
|
||
db.get('SELECT * FROM orders WHERE order_id = ?', [order_id], (err, order) => {
|
||
if (err || !order) {
|
||
return res.status(404).json({ error: '订单未找到' });
|
||
}
|
||
|
||
res.json(order);
|
||
});
|
||
});
|
||
|
||
// 获取所有订单列表(管理员)
|
||
app.get('/api/admin/orders', (req, res) => {
|
||
const { status, search, limit = 50, offset = 0 } = req.query;
|
||
|
||
let query = 'SELECT * FROM orders';
|
||
let params = [];
|
||
let conditions = [];
|
||
|
||
if (status) {
|
||
conditions.push('payment_status = ? OR shipping_status = ?');
|
||
params.push(status, status);
|
||
}
|
||
|
||
if (search) {
|
||
conditions.push('(order_id LIKE ? OR customer_name LIKE ? OR customer_email LIKE ?)');
|
||
const searchTerm = `%${search}%`;
|
||
params.push(searchTerm, searchTerm, searchTerm);
|
||
}
|
||
|
||
if (conditions.length > 0) {
|
||
query += ' WHERE ' + conditions.join(' AND ');
|
||
}
|
||
|
||
query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
||
params.push(parseInt(limit), parseInt(offset));
|
||
|
||
db.all(query, params, (err, orders) => {
|
||
if (err) {
|
||
console.error('Database error:', err);
|
||
return res.status(500).json({ error: '查询订单失败' });
|
||
}
|
||
|
||
// 获取订单统计
|
||
db.all(`
|
||
SELECT
|
||
COUNT(*) as total,
|
||
SUM(CASE WHEN payment_status = 'finished' AND shipping_status = 'pending' THEN 1 ELSE 0 END) as pending_ship,
|
||
SUM(CASE WHEN shipping_status = 'shipped' THEN 1 ELSE 0 END) as shipped,
|
||
SUM(CASE WHEN shipping_status = 'completed' THEN 1 ELSE 0 END) as completed
|
||
FROM orders
|
||
`, [], (err, stats) => {
|
||
if (err) {
|
||
console.error('Stats error:', err);
|
||
return res.json({ orders, stats: null });
|
||
}
|
||
|
||
res.json({
|
||
orders,
|
||
stats: stats[0] || { total: 0, pending_ship: 0, shipped: 0, completed: 0 }
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
// 更新订单发货状态
|
||
app.put('/api/admin/orders/:order_id/shipping', (req, res) => {
|
||
const { order_id } = req.params;
|
||
const { shipping_status, tracking_number, shipping_notes } = req.body;
|
||
|
||
if (!['pending', 'shipped', 'completed'].includes(shipping_status)) {
|
||
return res.status(400).json({ error: '无效的发货状态' });
|
||
}
|
||
|
||
db.run(
|
||
`UPDATE orders SET
|
||
shipping_status = ?,
|
||
tracking_number = ?,
|
||
shipping_notes = ?,
|
||
updated_at = CURRENT_TIMESTAMP
|
||
WHERE order_id = ?`,
|
||
[shipping_status, tracking_number || null, shipping_notes || null, order_id],
|
||
function(err) {
|
||
if (err) {
|
||
console.error('Update shipping error:', err);
|
||
return res.status(500).json({ error: '更新发货状态失败' });
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: '订单未找到' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '发货状态更新成功',
|
||
changes: this.changes
|
||
});
|
||
}
|
||
);
|
||
});
|
||
|
||
// 验证优惠码
|
||
app.post('/api/coupons/validate', (req, res) => {
|
||
const { code } = req.body;
|
||
|
||
if (!code) {
|
||
return res.status(400).json({ error: '请输入优惠码' });
|
||
}
|
||
|
||
db.get(`
|
||
SELECT * FROM coupons
|
||
WHERE code = ? AND is_active = 1
|
||
`, [code], (err, coupon) => {
|
||
if (err) {
|
||
console.error('Query coupon error:', err);
|
||
return res.status(500).json({ error: '查询优惠码失败' });
|
||
}
|
||
|
||
if (!coupon) {
|
||
return res.json({ valid: false, message: '优惠码不存在或已失效' });
|
||
}
|
||
|
||
// 检查使用次数限制
|
||
if (!coupon.is_reusable && coupon.used_count >= coupon.max_uses) {
|
||
return res.json({ valid: false, message: '优惠码已被使用' });
|
||
}
|
||
|
||
if (coupon.is_reusable && coupon.used_count >= coupon.max_uses) {
|
||
return res.json({ valid: false, message: '优惠码使用次数已达上限' });
|
||
}
|
||
|
||
res.json({
|
||
valid: true,
|
||
coupon: {
|
||
id: coupon.id,
|
||
code: coupon.code,
|
||
name: coupon.name,
|
||
discount_type: coupon.discount_type,
|
||
discount_value: coupon.discount_value
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
// 获取所有优惠码(管理员)
|
||
app.get('/api/admin/coupons', (req, res) => {
|
||
const { limit = 50, offset = 0 } = req.query;
|
||
|
||
db.all(`
|
||
SELECT * FROM coupons
|
||
ORDER BY created_at DESC
|
||
LIMIT ? OFFSET ?
|
||
`, [parseInt(limit), parseInt(offset)], (err, coupons) => {
|
||
if (err) {
|
||
console.error('Query coupons error:', err);
|
||
return res.status(500).json({ error: '查询优惠码失败' });
|
||
}
|
||
|
||
res.json({ coupons });
|
||
});
|
||
});
|
||
|
||
// 创建优惠码(管理员)
|
||
app.post('/api/admin/coupons', (req, res) => {
|
||
const {
|
||
code,
|
||
name,
|
||
discount_type,
|
||
discount_value,
|
||
is_reusable,
|
||
max_uses
|
||
} = req.body;
|
||
|
||
// 验证必填字段
|
||
if (!code || !name || !discount_type || discount_value === undefined) {
|
||
return res.status(400).json({ error: '请填写所有必填字段' });
|
||
}
|
||
|
||
// 验证折扣类型
|
||
if (!['percentage', 'fixed'].includes(discount_type)) {
|
||
return res.status(400).json({ error: '无效的折扣类型' });
|
||
}
|
||
|
||
// 验证折扣值
|
||
if (discount_value <= 0) {
|
||
return res.status(400).json({ error: '折扣值必须大于0' });
|
||
}
|
||
|
||
if (discount_type === 'percentage' && discount_value > 100) {
|
||
return res.status(400).json({ error: '百分比折扣不能超过100%' });
|
||
}
|
||
|
||
const stmt = db.prepare(`
|
||
INSERT INTO coupons (code, name, discount_type, discount_value, is_reusable, max_uses)
|
||
VALUES (?, ?, ?, ?, ?, ?)
|
||
`);
|
||
|
||
stmt.run([
|
||
code.toUpperCase(),
|
||
name,
|
||
discount_type,
|
||
discount_value,
|
||
is_reusable ? 1 : 0,
|
||
max_uses || 1
|
||
], function(err) {
|
||
if (err) {
|
||
console.error('Create coupon error:', err);
|
||
if (err.message.includes('UNIQUE constraint failed')) {
|
||
return res.status(400).json({ error: '优惠码已存在' });
|
||
}
|
||
return res.status(500).json({ error: '创建优惠码失败' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '优惠码创建成功',
|
||
coupon_id: this.lastID
|
||
});
|
||
});
|
||
|
||
stmt.finalize();
|
||
});
|
||
|
||
// 更新优惠码状态(管理员)
|
||
app.put('/api/admin/coupons/:id', (req, res) => {
|
||
const { id } = req.params;
|
||
const { is_active } = req.body;
|
||
|
||
if (is_active === undefined) {
|
||
return res.status(400).json({ error: '请提供状态参数' });
|
||
}
|
||
|
||
db.run(`
|
||
UPDATE coupons
|
||
SET is_active = ?, updated_at = CURRENT_TIMESTAMP
|
||
WHERE id = ?
|
||
`, [is_active ? 1 : 0, id], function(err) {
|
||
if (err) {
|
||
console.error('Update coupon error:', err);
|
||
return res.status(500).json({ error: '更新优惠码失败' });
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: '优惠码未找到' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '优惠码状态更新成功'
|
||
});
|
||
});
|
||
});
|
||
|
||
// 删除优惠码(管理员)
|
||
app.delete('/api/admin/coupons/:id', (req, res) => {
|
||
const { id } = req.params;
|
||
|
||
db.run('DELETE FROM coupons WHERE id = ?', [id], function(err) {
|
||
if (err) {
|
||
console.error('Delete coupon error:', err);
|
||
return res.status(500).json({ error: '删除优惠码失败' });
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: '优惠码未找到' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '优惠码已删除'
|
||
});
|
||
});
|
||
});
|
||
|
||
// 删除订单(管理员)
|
||
app.delete('/api/admin/orders/:order_id', (req, res) => {
|
||
const { order_id } = req.params;
|
||
|
||
// 先查询订单是否存在
|
||
db.get('SELECT * FROM orders WHERE order_id = ?', [order_id], (err, order) => {
|
||
if (err) {
|
||
console.error('Query order error:', err);
|
||
return res.status(500).json({ error: '查询订单失败' });
|
||
}
|
||
|
||
if (!order) {
|
||
return res.status(404).json({ error: '订单未找到' });
|
||
}
|
||
|
||
// 删除订单
|
||
db.run('DELETE FROM orders WHERE order_id = ?', [order_id], function(err) {
|
||
if (err) {
|
||
console.error('Delete order error:', err);
|
||
return res.status(500).json({ error: '删除订单失败' });
|
||
}
|
||
|
||
if (this.changes === 0) {
|
||
return res.status(404).json({ error: '订单未找到' });
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: '订单已删除',
|
||
deleted_order_id: order_id
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
app.listen(PORT, () => {
|
||
console.log(`服务器运行在 http://localhost:${PORT}`);
|
||
}); |