390 lines
11 KiB
JavaScript
390 lines
11 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 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 dbPath = process.env.DB_PATH || './database/shop.db';
|
||
const db = new sqlite3.Database(dbPath);
|
||
|
||
// 创建订单表
|
||
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 = process.env.UPAY_APP_ID || 'E7c4dss9';
|
||
// const UPAY_APP_SECRET = process.env.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: '比特币彩票抽奖机',
|
||
price: 100,
|
||
description: '这是我们的比特币彩票抽奖机,质量优秀'
|
||
}
|
||
};
|
||
|
||
// 路由
|
||
app.get('/', (req, res) => {
|
||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||
});
|
||
|
||
// 获取产品信息
|
||
app.get('/api/products', (req, res) => {
|
||
res.json(PRODUCTS);
|
||
});
|
||
|
||
// 创建订单
|
||
app.post('/api/orders', async (req, res) => {
|
||
try {
|
||
const {
|
||
product_id,
|
||
quantity,
|
||
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 total_amount = product.price * quantity;
|
||
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, product.price, total_amount,
|
||
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,
|
||
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 paymentData = {
|
||
appId: UPAY_APP_ID,
|
||
merchantOrderNo: order_id,
|
||
chainType: '1', // USDT TRC20
|
||
fiatAmount: order.total_amount.toFixed(2),
|
||
fiatCurrency: 'USD',
|
||
notifyUrl: `${req.protocol}://${req.get('host')}/api/payment/callback`,
|
||
redirectUrl: `${req.protocol}://${req.get('host')}/success.html?order_id=${order_id}`
|
||
};
|
||
|
||
// 生成签名
|
||
paymentData.signature = generateUpaySignature(paymentData, UPAY_APP_SECRET);
|
||
|
||
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}`);
|
||
}); |