ishop/server.js
2025-08-10 21:54:01 +08:00

416 lines
12 KiB
JavaScript
Raw 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 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 app = express();
const PORT = process.env.PORT || 3000;
// 中间件
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,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
});
// 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
} = 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;
// 服务器端折扣计算验证
if (quantity >= 5) {
expectedTotal = originalTotal * 0.9; // 9折
} else if (quantity >= 2) {
expectedTotal = originalTotal * 0.95; // 9.5折
}
// 价格验证(允许小数点误差)
if (Math.abs(total_amount - expectedTotal) > 0.01) {
return res.status(400).json({ error: '价格计算错误' });
}
const order_id = 'ORDER_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
// 保存订单到数据库
const stmt = db.prepare(`
INSERT INTO orders (order_id, product_name, quantity, unit_price, total_amount,
customer_name, customer_email, customer_phone, shipping_address)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
`);
stmt.run([
order_id, product.name, quantity, expectedTotal / quantity, expectedTotal,
customer_name, customer_email, customer_phone, shipping_address
], function(err) {
if (err) {
console.error(err);
return res.status(500).json({ error: '订单创建失败' });
}
res.json({
success: true,
order_id,
total_amount: expectedTotal,
message: '订单创建成功'
});
});
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],
(err) => {
if (err) {
console.error('Database update error:', err);
return res.status(500).send('FAIL');
}
console.log(`Order ${merchantOrderNo} payment status updated to: ${paymentStatus}`);
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.listen(PORT, () => {
console.log(`服务器运行在 http://localhost:${PORT}`);
});