commit 7a7a9759cafe7a2358bb5f4916cda0cd6091563d Author: aaron <> Date: Sun Aug 10 12:20:27 2025 +0800 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e4e243a --- /dev/null +++ b/.env.example @@ -0,0 +1,11 @@ +# 环境变量配置文件 +# UPay 配置 +UPAY_APP_ID=E7c4dss9 +UPAY_APP_SECRET=Hwc56INsabRau2yn + +# 数据库配置 +DB_PATH=./database/shop.db + +# 服务器配置 +PORT=3000 +NODE_ENV=development \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d4218e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,94 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ + +# Dependency directories +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Database files +*.db +*.sqlite +database/*.db + +# Logs +logs/ +*.log + +# Runtime data +tmp/ +temp/ + +# macOS +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Linux +*~ + +# Editor directories and files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Payment config files (if any) +config/payment-keys.json +config/secrets.json +payment-config.json + +# Backup files +*.bak +*.backup \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..eca666a --- /dev/null +++ b/README.md @@ -0,0 +1,117 @@ +# USDT 支付商城 + +这是一个基于 Node.js 和 UPay API 的 USDT 支付商城系统。 + +## 功能特性 + +- 📱 现代化响应式设计 +- 💰 USDT 加密货币支付 +- 🛒 简洁的购买流程 +- 📦 订单管理系统 +- 🔒 安全的支付处理 +- 📊 实时支付状态跟踪 + +## 技术栈 + +- **后端**: Node.js + Express +- **数据库**: SQLite3 +- **支付**: UPay API (USDT支付) +- **前端**: 原生 JavaScript + CSS3 +- **样式**: CSS Grid + Flexbox + +## 安装和运行 + +1. 克隆项目并安装依赖: +```bash +npm install +``` + +2. 配置 UPay API: +```bash +# 设置环境变量 +export UPAY_APP_ID=your-upay-app-id +export UPAY_APP_SECRET=your-upay-app-secret +``` + +3. 启动服务器: +```bash +# 开发模式 +npm run dev + +# 生产模式 +npm start +``` + +4. 访问 http://localhost:3000 + +## 项目结构 + +``` +myusdtshop/ +├── server.js # 主服务器文件 +├── package.json # 项目配置 +├── database/ # SQLite 数据库文件 +├── public/ # 前端静态文件 +│ ├── index.html # 主页面 +│ ├── css/ +│ │ └── style.css # 样式文件 +│ ├── js/ +│ │ └── main.js # JavaScript 逻辑 +│ └── images/ # 图片资源 +``` + +## API 接口 + +- `GET /api/products` - 获取产品信息 +- `POST /api/orders` - 创建订单 +- `POST /api/payment/create` - 创建支付 +- `POST /api/payment/callback` - 支付回调 +- `GET /api/orders/:order_id` - 获取订单状态 + +## 使用说明 + +### 配置 UPay + +1. 在 [UPay](https://upay.ink) 注册商户账户 +2. 获取 App ID 和 App Secret +3. 设置环境变量或修改 server.js 中的配置 + +### 自定义产品 + +修改 server.js 中的 `PRODUCTS` 对象: + +```javascript +const PRODUCTS = { + 'your-product-id': { + name: '您的产品名称', + price: 99.99, + description: '产品描述' + } +}; +``` + +### 支付流程 + +1. 用户选择产品和数量 +2. 填写收货信息 +3. 创建订单 +4. 跳转到支付页面完成 USDT 支付 +5. 系统通过回调自动确认支付状态 + +## UPay 集成特性 + +- 支持 USDT 直接转账到商户钱包 +- 实时支付状态回调通知 +- MD5 签名验证确保安全 +- 支持多种支付状态处理 + +## 安全注意事项 + +- 请勿将 App Secret 提交到版本控制 +- 生产环境请使用 HTTPS +- 定期备份数据库 +- 验证所有支付回调签名 + +## 许可证 + +ISC License \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..dda5dd5 --- /dev/null +++ b/package.json @@ -0,0 +1,24 @@ +{ + "name": "usdt-shop", + "version": "1.0.0", + "description": "USDT Payment Product Sales Website", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js" + }, + "dependencies": { + "express": "^4.18.2", + "sqlite3": "^5.1.6", + "axios": "^1.6.0", + "cors": "^2.8.5", + "body-parser": "^1.20.2", + "crypto": "^1.0.1" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "keywords": ["usdt", "payment", "shop", "nowpayments"], + "author": "Aaron", + "license": "ISC" +} \ No newline at end of file diff --git a/public/admin.html b/public/admin.html new file mode 100644 index 0000000..e10344b --- /dev/null +++ b/public/admin.html @@ -0,0 +1,116 @@ + + + + + + 订单管理 - USDT商城 + + + + +
+
+

订单管理系统

+

管理所有订单信息和发货状态

+
+ +
+
+

总订单数

+ 0 +
+
+

已支付未发货

+ 0 +
+
+

已发货

+ 0 +
+
+ +
+
+

订单列表

+
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + +
订单号客户信息产品金额支付状态发货状态创建时间操作
加载中...
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/public/css/admin.css b/public/css/admin.css new file mode 100644 index 0000000..f071409 --- /dev/null +++ b/public/css/admin.css @@ -0,0 +1,455 @@ +/* 订单管理页面样式 */ + +/* 管理员导航 - 已移除 */ + +/* 统计卡片 */ +.stats-section { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.stat-card { + background: linear-gradient(145deg, #1a1a1a, #2d2d2d); + padding: 25px; + border-radius: 15px; + text-align: center; + border: 1px solid #444; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); + position: relative; + overflow: hidden; +} + +.stat-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, #ffd700, #ffed4a); +} + +.stat-card h3 { + color: #b0b0b0; + font-size: 1.1em; + margin-bottom: 10px; + font-weight: 500; +} + +.stat-card span { + color: #ffd700; + font-size: 2.2em; + font-weight: bold; + display: block; + text-shadow: 0 0 20px rgba(255, 215, 0, 0.3); +} + +/* 订单表格区域 */ +.orders-section { + background: linear-gradient(145deg, #1a1a1a, #2d2d2d); + border-radius: 15px; + padding: 30px; + border: 1px solid #444; + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 25px; + padding-bottom: 15px; + border-bottom: 1px solid #333; + flex-wrap: wrap; + gap: 15px; +} + +.section-header h2 { + color: #ffffff; + font-size: 1.5em; + margin: 0; +} + +.header-controls { + display: flex; + align-items: center; + gap: 15px; + flex-wrap: wrap; +} + +.search-box { + display: flex; + gap: 10px; +} + +.search-box input { + padding: 10px 15px; + background: #0a0a0a; + border: 2px solid #444; + border-radius: 8px; + color: #e0e0e0; + width: 250px; + font-size: 14px; +} + +.search-box input:focus { + outline: none; + border-color: #ffd700; + box-shadow: 0 0 15px rgba(255, 215, 0, 0.2); +} + +.search-btn { + padding: 10px 18px; + background: linear-gradient(135deg, #ffd700, #ffed4a); + color: #000; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: bold; + transition: all 0.3s ease; +} + +.search-btn:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(255, 215, 0, 0.3); +} + +.section-header select { + padding: 10px 15px; + background: #0a0a0a; + border: 2px solid #444; + border-radius: 8px; + color: #e0e0e0; + font-size: 14px; + min-width: 120px; +} + +.section-header select:focus { + outline: none; + border-color: #ffd700; +} + +/* 表格容器 */ +.orders-table-container { + overflow-x: auto; + background: #0a0a0a; + border-radius: 10px; + border: 1px solid #333; +} + +.orders-table { + width: 100%; + border-collapse: collapse; + font-size: 14px; +} + +.orders-table th { + background: linear-gradient(145deg, #2d2d2d, #3d3d3d); + color: #ffd700; + padding: 15px 12px; + text-align: left; + font-weight: bold; + border-bottom: 2px solid #444; + position: sticky; + top: 0; + z-index: 10; +} + +.orders-table td { + padding: 12px; + border-bottom: 1px solid #333; + color: #e0e0e0; + vertical-align: middle; +} + +.orders-table tbody tr { + transition: background-color 0.3s ease; +} + +.orders-table tbody tr:hover { + background-color: rgba(255, 215, 0, 0.05); +} + +.loading { + text-align: center; + color: #b0b0b0; + font-style: italic; + padding: 50px !important; +} + +/* 状态标签 */ +.status-badge { + display: inline-block; + padding: 4px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.status-pending { + background: rgba(255, 193, 7, 0.2); + color: #ffc107; + border: 1px solid #ffc107; +} + +.status-finished { + background: rgba(40, 167, 69, 0.2); + color: #28a745; + border: 1px solid #28a745; +} + +.status-shipped { + background: rgba(0, 123, 255, 0.2); + color: #007bff; + border: 1px solid #007bff; +} + +.status-completed { + background: rgba(108, 117, 125, 0.2); + color: #6c757d; + border: 1px solid #6c757d; +} + +.status-failed { + background: rgba(220, 53, 69, 0.2); + color: #dc3545; + border: 1px solid #dc3545; +} + +/* 操作按钮 */ +.action-btn { + padding: 6px 12px; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + font-weight: bold; + margin-right: 5px; + margin-bottom: 4px; + transition: all 0.3s ease; + display: inline-block; +} + +.btn-copy { + background: linear-gradient(135deg, #3498db, #2980b9); + color: #fff; + border: 1px solid #3498db; +} + +.btn-copy:hover { + background: linear-gradient(135deg, #2980b9, #1f639a); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(52, 152, 219, 0.4); +} + +.btn-ship { + background: linear-gradient(135deg, #ffd700, #ffed4a); + color: #000; + border: 1px solid #ffd700; +} + +.btn-ship:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(255, 215, 0, 0.4); +} + +/* 订单详情模态框 */ +.order-detail-content { + color: #e0e0e0; +} + +.detail-section { + margin-bottom: 25px; + padding: 20px; + background: linear-gradient(145deg, #0a0a0a, #1a1a1a); + border-radius: 10px; + border: 1px solid #333; +} + +.detail-section h4 { + color: #ffd700; + margin-bottom: 15px; + font-size: 1.1em; + padding-bottom: 8px; + border-bottom: 1px solid #333; +} + +.detail-row { + display: flex; + justify-content: space-between; + margin-bottom: 10px; + padding: 8px 0; +} + +.detail-row:last-child { + margin-bottom: 0; +} + +.detail-label { + font-weight: bold; + color: #b0b0b0; +} + +.detail-value { + color: #e0e0e0; +} + +/* 发货表单 */ +.shipping-form .form-group { + margin-bottom: 20px; +} + +.shipping-form label { + display: block; + margin-bottom: 8px; + color: #ffffff; + font-weight: 600; +} + +.shipping-form input, +.shipping-form select, +.shipping-form textarea { + width: 100%; + padding: 12px; + background: #0a0a0a; + border: 2px solid #444; + border-radius: 8px; + color: #e0e0e0; + font-size: 14px; +} + +.shipping-form input:focus, +.shipping-form select:focus, +.shipping-form textarea:focus { + outline: none; + border-color: #ffd700; + box-shadow: 0 0 15px rgba(255, 215, 0, 0.2); +} + +.form-actions { + display: flex; + gap: 15px; + justify-content: flex-end; + margin-top: 25px; +} + +.btn-primary { + padding: 12px 25px; + background: linear-gradient(135deg, #ffd700, #ffed4a); + color: #000; + border: none; + border-radius: 8px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(255, 215, 0, 0.4); +} + +.btn-secondary { + padding: 12px 25px; + background: linear-gradient(145deg, #333, #444); + color: #e0e0e0; + border: 1px solid #555; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; +} + +.btn-secondary:hover { + background: linear-gradient(145deg, #444, #555); + transform: translateY(-2px); +} + +/* 复制按钮样式 */ +.copy-btn { + background: none; + border: none; + color: #ffd700; + cursor: pointer; + font-size: 12px; + margin-left: 8px; + padding: 2px 4px; + border-radius: 3px; + transition: all 0.3s ease; + opacity: 0.7; +} + +.copy-btn:hover { + opacity: 1; + background: rgba(255, 215, 0, 0.1); + transform: scale(1.1); +} + +/* 复制成功提示 */ +.copy-toast { + position: fixed; + top: 20px; + right: 20px; + background: linear-gradient(135deg, #00ff88, #00cc6a); + color: #000; + padding: 12px 20px; + border-radius: 8px; + font-weight: bold; + font-size: 14px; + z-index: 9999; + box-shadow: 0 8px 25px rgba(0, 255, 136, 0.3); + animation: slideIn 0.3s ease-out; +} + +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .stats-section { + grid-template-columns: repeat(2, 1fr); + } + + .section-header { + flex-direction: column; + align-items: stretch; + } + + .header-controls { + justify-content: space-between; + } + + .search-box input { + width: 180px; + } + + .orders-table { + font-size: 12px; + } + + .orders-table th, + .orders-table td { + padding: 8px 6px; + } + + .action-btn { + padding: 4px 8px; + font-size: 11px; + } + + .copy-btn { + font-size: 10px; + margin-left: 4px; + } +} \ No newline at end of file diff --git a/public/css/spinner.css b/public/css/spinner.css new file mode 100644 index 0000000..3142828 --- /dev/null +++ b/public/css/spinner.css @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..acd1164 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,611 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Arial', 'Microsoft YaHei', sans-serif; + line-height: 1.6; + color: #e0e0e0; + background: #0a0a0a; + min-height: 100vh; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +main { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 40px; + align-items: start; + position: relative; +} + +/* 订单管理按钮 */ +.admin-floating-btn { + position: fixed; + top: 30px; + right: 30px; + z-index: 1000; + background: linear-gradient(135deg, #ffd700, #ffed4a); + color: #000; + border: none; + border-radius: 50px; + padding: 12px 25px; + font-size: 14px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; + box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3); +} + +.admin-floating-btn:hover { + transform: translateY(-3px); + box-shadow: 0 8px 25px rgba(255, 215, 0, 0.5); +} + +/* 产品卡片 */ +.product-section { + background: linear-gradient(145deg, #1a1a1a 0%, #2d2d2d 100%); + border-radius: 20px; + padding: 30px; + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.4), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + border: 1px solid #333; +} + +.product-card { + text-align: center; +} + +.product-image { + width: 100%; + height: 280px; + background: linear-gradient(135deg, #333 0%, #444 100%); + border-radius: 15px; + margin-bottom: 25px; + display: flex; + align-items: center; + justify-content: center; + overflow: hidden; + border: 2px solid #444; + position: relative; +} + +.product-image::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(45deg, transparent 49%, rgba(255, 215, 0, 0.1) 50%, transparent 51%); + pointer-events: none; +} + +.product-image img { + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + border-radius: 10px; +} + +.product-info h2 { + color: #ffffff; + margin-bottom: 15px; + font-size: 2em; + font-weight: 600; +} + +.product-description { + color: #b0b0b0; + margin-bottom: 25px; + line-height: 1.6; + font-size: 1.1em; +} + +.price { + font-size: 2.2em; + font-weight: bold; + margin-bottom: 20px; + background: linear-gradient(45deg, #ffd700, #ffed4a); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 20px rgba(255, 215, 0, 0.3); +} + +.price .currency, +.price .unit { + font-size: 0.7em; + color: #888; +} + +/* 订单表单 */ +.order-section { + background: linear-gradient(145deg, #1a1a1a 0%, #2d2d2d 100%); + border-radius: 20px; + padding: 30px; + box-shadow: + 0 20px 40px rgba(0, 0, 0, 0.4), + inset 0 1px 0 rgba(255, 255, 255, 0.1); + border: 1px solid #333; +} + +.form-group { + margin-bottom: 25px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #ffffff; + font-size: 1.1em; +} + +.form-group input, +.form-group textarea { + width: 100%; + padding: 15px; + border: 2px solid #444; + border-radius: 10px; + font-size: 16px; + background: #0a0a0a; + color: #e0e0e0; + transition: all 0.3s ease; +} + +.form-group input:focus, +.form-group textarea:focus { + outline: none; + border-color: #ffd700; + box-shadow: 0 0 20px rgba(255, 215, 0, 0.2); + background: #1a1a1a; +} + +.form-group input::placeholder, +.form-group textarea::placeholder { + color: #666; +} + +/* 数量控制 */ +.quantity-controls { + display: flex; + align-items: center; + gap: 15px; + margin-bottom: 20px; + justify-content: center; +} + +.quantity-controls button { + width: 45px; + height: 45px; + border: 2px solid #ffd700; + background: linear-gradient(145deg, #1a1a1a, #2d2d2d); + color: #ffd700; + border-radius: 50%; + font-size: 20px; + cursor: pointer; + transition: all 0.3s ease; + font-weight: bold; +} + +.quantity-controls button:hover { + background: linear-gradient(145deg, #ffd700, #ffed4a); + color: #000; + transform: scale(1.05); + box-shadow: 0 8px 20px rgba(255, 215, 0, 0.3); +} + +.quantity-controls input { + width: 80px; + text-align: center; + margin: 0; + font-weight: bold; + font-size: 18px; +} + +.total-price { + font-size: 1.3em; + font-weight: bold; + text-align: center; + padding: 15px; + background: linear-gradient(145deg, #0a0a0a, #1a1a1a); + border-radius: 10px; + border: 2px solid #333; + background: linear-gradient(45deg, #ffd700, #ffed4a); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* 按钮 */ +.order-btn { + width: 100%; + padding: 18px; + background: linear-gradient(135deg, #ffd700 0%, #ffed4a 100%); + color: #000; + border: none; + border-radius: 12px; + font-size: 18px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; + position: relative; + overflow: hidden; +} + +.order-btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s; +} + +.order-btn:hover::before { + left: 100%; +} + +.order-btn:hover { + transform: translateY(-3px); + box-shadow: 0 15px 35px rgba(255, 215, 0, 0.4); +} + +.order-btn:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; +} + +/* 模态框 */ +.modal { + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.8); + display: flex; + justify-content: center; + align-items: center; + backdrop-filter: blur(5px); +} + +.modal-content { + background: linear-gradient(145deg, #1a1a1a, #2d2d2d); + padding: 0; + border-radius: 20px; + width: 90%; + max-width: 500px; + max-height: 90vh; + overflow: hidden; + border: 1px solid #444; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); +} + +.modal-header { + background: linear-gradient(135deg, #ffd700 0%, #ffed4a 100%); + color: #000; + padding: 25px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h3 { + margin: 0; + font-size: 1.4em; + font-weight: bold; +} + +.close { + color: #000; + font-size: 28px; + font-weight: bold; + cursor: pointer; + transition: opacity 0.3s; +} + +.close:hover { + opacity: 0.7; +} + +.modal-body { + padding: 30px; + background: #1a1a1a; +} + +.order-summary { + margin-bottom: 30px; +} + +.order-summary h4 { + color: #ffffff; + margin-bottom: 20px; + font-size: 1.3em; + font-weight: 600; +} + +.summary-item { + display: flex; + justify-content: space-between; + margin-bottom: 12px; + padding: 10px 0; + border-bottom: 1px solid #333; + color: #e0e0e0; +} + +.summary-item:last-child { + border-bottom: none; + font-weight: bold; + color: #ffd700; + font-size: 1.1em; +} + +.payment-section { + text-align: center; + padding: 25px; + background: linear-gradient(145deg, #0a0a0a, #1a1a1a); + border-radius: 12px; + border: 1px solid #333; +} + +.payment-section p { + color: #b0b0b0; + margin-bottom: 20px; + font-size: 1.1em; +} + +.pay-btn { + background: linear-gradient(135deg, #ffd700, #ffed4a); + color: #000; + border: none; + padding: 15px 35px; + border-radius: 10px; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.pay-btn:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(255, 215, 0, 0.4); +} + +/* 支付状态 */ +.payment-status { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #0a0a0a; + z-index: 2000; + display: flex; + justify-content: center; + align-items: center; +} + +.status-content { + text-align: center; + padding: 50px; + background: linear-gradient(145deg, #1a1a1a, #2d2d2d); + border-radius: 20px; + border: 1px solid #444; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5); +} + +.status-icon { + width: 100px; + height: 100px; + margin: 0 auto 30px; +} + +.loading-spinner { + width: 100px; + height: 100px; + border: 6px solid #333; + border-top: 6px solid #ffd700; + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.status-content h3 { + color: #ffffff; + margin-bottom: 15px; + font-size: 2em; +} + +.status-content p { + color: #b0b0b0; + margin-bottom: 30px; + font-size: 1.2em; +} + +.order-info { + background: linear-gradient(145deg, #0a0a0a, #1a1a1a); + padding: 25px; + border-radius: 12px; + margin-bottom: 30px; + border: 1px solid #333; +} + +.order-info p { + margin-bottom: 12px; + color: #e0e0e0; + font-size: 1.1em; +} + +.status-btn { + background: linear-gradient(135deg, #ffd700, #ffed4a); + color: #000; + border: none; + padding: 15px 35px; + border-radius: 10px; + font-size: 16px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.status-btn:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(255, 215, 0, 0.4); +} + +/* 成功/失败图标 */ +.success-icon { + color: #00ff88; + font-size: 100px; + text-shadow: 0 0 30px rgba(0, 255, 136, 0.5); +} + +.error-icon { + color: #ff4757; + font-size: 100px; + text-shadow: 0 0 30px rgba(255, 71, 87, 0.5); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + main { + grid-template-columns: 1fr; + gap: 30px; + } + + .product-section, + .order-section { + padding: 25px; + } + + .modal-content { + width: 95%; + margin: 10px; + } + + .modal-body { + padding: 25px; + } + + .status-content { + padding: 30px; + margin: 20px; + } + + .admin-floating-btn { + top: 20px; + right: 20px; + padding: 10px 20px; + font-size: 12px; + } +} + +/* 管理员登录表单样式 */ +.login-form .form-group { + margin-bottom: 20px; +} + +.login-form label { + display: block; + margin-bottom: 8px; + color: #ffffff; + font-weight: 600; +} + +.login-form input { + width: 100%; + padding: 12px 15px; + background: #0a0a0a; + border: 2px solid #444; + border-radius: 8px; + color: #e0e0e0; + font-size: 16px; + transition: all 0.3s ease; +} + +.login-form input:focus { + outline: none; + border-color: #ffd700; + box-shadow: 0 0 15px rgba(255, 215, 0, 0.2); + background: #1a1a1a; +} + +.login-form .form-actions { + display: flex; + gap: 15px; + justify-content: flex-end; + margin-top: 25px; +} + +.login-form .btn-primary { + padding: 12px 25px; + background: linear-gradient(135deg, #ffd700, #ffed4a); + color: #000; + border: none; + border-radius: 8px; + font-weight: bold; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.login-form .btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(255, 215, 0, 0.4); +} + +.login-form .btn-secondary { + padding: 12px 25px; + background: linear-gradient(145deg, #333, #444); + color: #e0e0e0; + border: 1px solid #555; + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.login-form .btn-secondary:hover { + background: linear-gradient(145deg, #444, #555); + transform: translateY(-2px); +} + +.error-message { + color: #ff4757; + font-size: 14px; + margin-top: 10px; + padding: 8px 12px; + background: rgba(255, 71, 87, 0.1); + border: 1px solid #ff4757; + border-radius: 6px; + text-align: center; +} \ No newline at end of file diff --git a/public/images/image01.jpg b/public/images/image01.jpg new file mode 100644 index 0000000..14ebe9f Binary files /dev/null and b/public/images/image01.jpg differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..7fd3027 --- /dev/null +++ b/public/index.html @@ -0,0 +1,163 @@ + + + + + + USDT 商城 + + + + +
+ + + +
+ +
+
+
+ 产品图片 +
+
+

高级产品

+

这是我们的高级产品,质量优秀

+
+ $ + 99.99 + USDT +
+
+
+
+ + +
+
+ +
+ +
+ + + +
+
+ 总价:$99.99 USDT +
+
+ + +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ + + +
+
+
+ + + + + + +
+ + + + + + + \ No newline at end of file diff --git a/public/js/admin.js b/public/js/admin.js new file mode 100644 index 0000000..b50b704 --- /dev/null +++ b/public/js/admin.js @@ -0,0 +1,443 @@ +// 订单管理页面脚本 +let currentOrders = []; +let currentOrderId = null; + +// DOM 元素 +const ordersTableBody = document.getElementById('ordersTableBody'); +const searchInput = document.getElementById('searchOrder'); +const statusFilter = document.getElementById('statusFilter'); +const orderDetailModal = document.getElementById('orderDetailModal'); +const shippingModal = document.getElementById('shippingModal'); + +// 页面加载时初始化 +document.addEventListener('DOMContentLoaded', function() { + loadOrders(); +}); + +// 加载订单列表 +async function loadOrders(search = '', status = '') { + try { + showLoading(); + + const params = new URLSearchParams(); + if (search) params.append('search', search); + if (status) params.append('status', status); + + const response = await fetch(`/api/admin/orders?${params}`); + const data = await response.json(); + + if (response.ok) { + currentOrders = data.orders; + updateOrdersTable(data.orders); + updateStats(data.stats); + } else { + throw new Error(data.error || '加载订单失败'); + } + } catch (error) { + console.error('Load orders error:', error); + showError('加载订单失败: ' + error.message); + } +} + +// 更新订单表格 +function updateOrdersTable(orders) { + if (!orders || orders.length === 0) { + ordersTableBody.innerHTML = '暂无订单数据'; + return; + } + + ordersTableBody.innerHTML = orders.map(order => ` + + + ${order.order_id} + + +
+ ${order.customer_name} +
+ ${order.customer_phone || '未提供电话'}
+ ${order.shipping_address ? (order.shipping_address.length > 30 ? order.shipping_address.substring(0, 30) + '...' : order.shipping_address) : '未提供地址'} +
+ + +
+ ${order.product_name}
+ 数量: ${order.quantity} +
+ + + $${order.total_amount.toFixed(2)} + + + + ${getStatusText(order.payment_status, 'payment')} + + + + + ${getStatusText(order.shipping_status, 'shipping')} + + + + ${formatDate(order.created_at)} + + + + + + `).join(''); +} + +// 更新统计数据 +function updateStats(stats) { + if (stats) { + const totalElement = document.getElementById('totalOrders'); + const pendingElement = document.getElementById('pendingOrders'); + const shippedElement = document.getElementById('shippedOrders'); + + if (totalElement) totalElement.textContent = stats.total || 0; + if (pendingElement) pendingElement.textContent = stats.pending_ship || 0; + if (shippedElement) shippedElement.textContent = stats.shipped || 0; + } +} + +// 搜索订单 +function searchOrders() { + const search = searchInput.value.trim(); + const status = statusFilter.value; + loadOrders(search, status); +} + +// 筛选订单 +function filterOrders() { + const search = searchInput.value.trim(); + const status = statusFilter.value; + loadOrders(search, status); +} + +// 显示订单详情 +async function showOrderDetail(orderId) { + try { + const response = await fetch(`/api/orders/${orderId}`); + const order = await response.json(); + + if (response.ok) { + const detailContent = document.getElementById('orderDetailContent'); + detailContent.innerHTML = ` +
+

基本信息

+
+ 订单号: + ${order.order_id} +
+
+ 支付ID: + ${order.payment_id || '未设置'} +
+
+ 创建时间: + ${formatDate(order.created_at)} +
+
+ 更新时间: + ${formatDate(order.updated_at)} +
+
+ +
+

客户信息

+
+ 姓名: + ${order.customer_name} +
+
+ 邮箱: + ${order.customer_email || '未提供'} +
+
+ 电话: + ${order.customer_phone || '未提供'} +
+
+ 收货地址: + + ${order.shipping_address} + + +
+
+ +
+

商品信息

+
+ 产品名称: + ${order.product_name} +
+
+ 单价: + $${order.unit_price.toFixed(2)} +
+
+ 数量: + ${order.quantity} +
+
+ 总金额: + $${order.total_amount.toFixed(2)} +
+
+ +
+

状态信息

+
+ 支付状态: + + + ${getStatusText(order.payment_status, 'payment')} + + +
+
+ 发货状态: + + + ${getStatusText(order.shipping_status, 'shipping')} + + +
+ ${order.tracking_number ? ` +
+ 快递单号: + ${order.tracking_number} +
` : ''} + ${order.shipping_notes ? ` +
+ 发货备注: + ${order.shipping_notes} +
` : ''} +
+ `; + + orderDetailModal.style.display = 'flex'; + } else { + throw new Error(order.error || '获取订单详情失败'); + } + } catch (error) { + console.error('Show order detail error:', error); + alert('获取订单详情失败: ' + error.message); + } +} + +// 复制订单收货信息 +function copyOrderInfo(orderId) { + const order = currentOrders.find(o => o.order_id === orderId); + if (!order) return; + + // 组合收货信息:姓名, 电话, 地址 + let infoText = order.customer_name; + if (order.customer_phone) { + infoText += ', ' + order.customer_phone; + } + infoText += ', ' + order.shipping_address; + + // 复制到剪贴板 + copyText(infoText); +} + +// 显示发货模态框 +function showShippingModal(orderId) { + currentOrderId = orderId; + const order = currentOrders.find(o => o.order_id === orderId); + if (!order) return; + + // 清空表单 + document.getElementById('trackingNumber').value = ''; + document.getElementById('shippingNotes').value = ''; + + // 显示模态框 + shippingModal.style.display = 'flex'; + document.getElementById('trackingNumber').focus(); +} + +// 关闭发货模态框 +function closeShippingModal() { + shippingModal.style.display = 'none'; + currentOrderId = null; +} + +// 确认发货 +async function confirmShipping() { + const trackingNumber = document.getElementById('trackingNumber').value.trim(); + const shippingNotes = document.getElementById('shippingNotes').value.trim(); + + if (!trackingNumber) { + alert('请输入运单号'); + document.getElementById('trackingNumber').focus(); + return; + } + + if (!currentOrderId) { + alert('订单ID错误'); + return; + } + + try { + const response = await fetch(`/api/admin/orders/${currentOrderId}/shipping`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + shipping_status: 'shipped', + tracking_number: trackingNumber, + shipping_notes: shippingNotes + }) + }); + + const result = await response.json(); + + if (response.ok) { + alert('订单已成功发货!'); + closeShippingModal(); + loadOrders(); // 重新加载订单列表 + } else { + throw new Error(result.error || '发货失败'); + } + } catch (error) { + console.error('Shipping error:', error); + alert('发货失败: ' + error.message); + } +} + +// 确认并标记为已发货 +function confirmMarkAsShipped(orderId) { + if (confirm('确认标记此订单为已发货吗?')) { + markAsShipped(orderId); + } +} + +// 一键标记为已发货 +async function markAsShipped(orderId) { + try { + const response = await fetch(`/api/admin/orders/${orderId}/shipping`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + shipping_status: 'shipped' + }) + }); + + const result = await response.json(); + + if (response.ok) { + alert('订单已标记为发货!'); + loadOrders(); // 重新加载订单列表 + } else { + throw new Error(result.error || '标记发货失败'); + } + } catch (error) { + console.error('Mark as shipped error:', error); + alert('标记发货失败: ' + error.message); + } +} + +// 复制文本到剪贴板 +async function copyText(text) { + try { + await navigator.clipboard.writeText(text); + // 显示复制成功提示 + showCopySuccess(); + } catch (err) { + // 降级方案:使用传统方法 + const textArea = document.createElement('textarea'); + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); + showCopySuccess(); + } +} + +// 显示复制成功提示 +function showCopySuccess() { + // 创建提示元素 + const toast = document.createElement('div'); + toast.className = 'copy-toast'; + toast.textContent = '已复制到剪贴板'; + document.body.appendChild(toast); + + // 3秒后移除提示 + setTimeout(() => { + if (toast.parentNode) { + toast.parentNode.removeChild(toast); + } + }, 3000); +} + +// 关闭订单详情模态框 +function closeOrderDetail() { + orderDetailModal.style.display = 'none'; +} + +// 获取状态文本 +function getStatusText(status, type) { + if (type === 'payment') { + switch(status) { + case 'pending': return '未支付'; + case 'finished': return '已支付'; + case 'failed': return '支付失败'; + case 'confirming': return '确认中'; + default: return status || '未知'; + } + } else if (type === 'shipping') { + switch(status) { + case 'pending': return '未发货'; + case 'shipped': return '已发货'; + default: return status || '未知'; + } + } + return status || '未知'; +} + +// 显示加载状态 +function showLoading() { + ordersTableBody.innerHTML = '加载中...'; +} + +// 显示错误信息 +function showError(message) { + ordersTableBody.innerHTML = `错误: ${message}`; +} + +// 格式化日期 +function formatDate(dateString) { + if (!dateString) return '未设置'; + const date = new Date(dateString); + return date.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); +} + +// 键盘事件监听 +document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + closeOrderDetail(); + closeShippingModal(); + } +}); + +// 搜索框回车事件 +searchInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + searchOrders(); + } +}); \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js new file mode 100644 index 0000000..8032ca8 --- /dev/null +++ b/public/js/main.js @@ -0,0 +1,369 @@ +// 全局变量 +let currentProduct = null; +let currentOrder = null; + +// DOM 元素 +const productPrice = document.getElementById('product-price'); +const quantityInput = document.getElementById('quantity'); +const totalPriceElement = document.getElementById('total-price'); +const orderForm = document.getElementById('orderForm'); +const orderModal = document.getElementById('orderModal'); +const paymentStatus = document.getElementById('paymentStatus'); + +// 初始化 +document.addEventListener('DOMContentLoaded', function() { + loadProductData(); + setupEventListeners(); +}); + +// 加载产品数据 +async function loadProductData() { + try { + const response = await fetch('/api/products'); + const products = await response.json(); + + // 使用第一个产品作为展示产品 + const productId = Object.keys(products)[0]; + currentProduct = { id: productId, ...products[productId] }; + + // 更新界面 + document.getElementById('product-name').textContent = currentProduct.name; + document.getElementById('product-description').textContent = currentProduct.description; + document.getElementById('product-price').textContent = currentProduct.price.toFixed(2); + + updateTotalPrice(); + } catch (error) { + console.error('加载产品数据失败:', error); + alert('加载产品信息失败,请刷新页面重试'); + } +} + +// 设置事件监听器 +function setupEventListeners() { + // 数量控制 + document.getElementById('decrease-qty').addEventListener('click', () => { + const current = parseInt(quantityInput.value) || 1; + if (current > 1) { + quantityInput.value = current - 1; + updateTotalPrice(); + } + }); + + document.getElementById('increase-qty').addEventListener('click', () => { + const current = parseInt(quantityInput.value) || 1; + if (current < 999) { + quantityInput.value = current + 1; + updateTotalPrice(); + } + }); + + // 数量输入变化 + quantityInput.addEventListener('input', updateTotalPrice); + quantityInput.addEventListener('change', validateQuantity); + + // 表单提交 + orderForm.addEventListener('submit', handleOrderSubmit); + + // 模态框控制 + document.getElementById('closeModal').addEventListener('click', closeOrderModal); + document.getElementById('pay-now-btn').addEventListener('click', handlePayNow); + + // 支付状态检查 + document.getElementById('check-status-btn').addEventListener('click', checkPaymentStatus); + + // 点击模态框外部关闭 + orderModal.addEventListener('click', (e) => { + if (e.target === orderModal) { + closeOrderModal(); + } + }); +} + +// 更新总价 +function updateTotalPrice() { + if (!currentProduct) return; + + const quantity = parseInt(quantityInput.value) || 1; + const total = (currentProduct.price * quantity).toFixed(2); + totalPriceElement.textContent = `$${total}`; +} + +// 验证数量 +function validateQuantity() { + const value = parseInt(quantityInput.value); + if (isNaN(value) || value < 1) { + quantityInput.value = 1; + } else if (value > 999) { + quantityInput.value = 999; + } + updateTotalPrice(); +} + +// 处理订单提交 +async function handleOrderSubmit(e) { + e.preventDefault(); + + if (!currentProduct) { + alert('产品信息加载中,请稍后重试'); + return; + } + + // 获取表单数据 + const formData = new FormData(orderForm); + const orderData = { + product_id: currentProduct.id, + quantity: parseInt(formData.get('quantity')), + customer_name: formData.get('customer_name').trim(), + customer_email: formData.get('customer_email').trim(), + customer_phone: formData.get('customer_phone').trim(), + shipping_address: formData.get('shipping_address').trim() + }; + + // 验证必填字段 + if (!orderData.customer_name || !orderData.shipping_address) { + alert('请填写姓名和收货地址'); + return; + } + + // 禁用提交按钮 + const submitBtn = document.getElementById('submit-order'); + const btnText = submitBtn.querySelector('.btn-text'); + const btnLoading = submitBtn.querySelector('.btn-loading'); + + submitBtn.disabled = true; + btnText.style.display = 'none'; + btnLoading.style.display = 'inline'; + + try { + // 创建订单 + const response = await fetch('/api/orders', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(orderData) + }); + + const result = await response.json(); + + if (result.success) { + currentOrder = { + ...orderData, + order_id: result.order_id, + total_amount: result.total_amount + }; + + showOrderModal(); + } else { + throw new Error(result.error || '订单创建失败'); + } + } catch (error) { + console.error('创建订单失败:', error); + alert('创建订单失败: ' + error.message); + } finally { + // 恢复提交按钮 + submitBtn.disabled = false; + btnText.style.display = 'inline'; + btnLoading.style.display = 'none'; + } +} + +// 显示订单确认模态框 +function showOrderModal() { + if (!currentOrder) return; + + // 填充订单信息 + document.getElementById('modal-order-id').textContent = currentOrder.order_id; + document.getElementById('modal-product-name').textContent = currentProduct.name; + document.getElementById('modal-quantity').textContent = currentOrder.quantity; + document.getElementById('modal-total-price').textContent = `$${currentOrder.total_amount.toFixed(2)} USDT`; + document.getElementById('modal-address').textContent = currentOrder.shipping_address; + + orderModal.style.display = 'flex'; +} + +// 关闭订单模态框 +function closeOrderModal() { + orderModal.style.display = 'none'; +} + +// 处理支付 +async function handlePayNow() { + if (!currentOrder) return; + + try { + // 创建支付 + const response = await fetch('/api/payment/create', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + order_id: currentOrder.order_id + }) + }); + + const result = await response.json(); + + if (result.success) { + // 检查是否为手动支付模式 + if (result.manual_mode) { + // 关闭模态框,跳转到手动支付页面 + closeOrderModal(); + window.location.href = result.payment_url; + } else { + // 关闭模态框,直接跳转到UPay支付页面 + closeOrderModal(); + + // 显示跳转提示 + const jumpTip = document.createElement('div'); + jumpTip.innerHTML = ` +
+
+

正在跳转到支付页面...

+

请在新页面完成USDT支付

+
+
+
+ +
+
+ `; + document.body.appendChild(jumpTip); + + // 3秒后跳转到UPay支付页面 + setTimeout(() => { + window.location.href = result.payment_url; + }, 3000); + } + } else { + throw new Error(result.error || '创建支付失败'); + } + } catch (error) { + console.error('创建支付失败:', error); + alert('创建支付失败: ' + error.message); + } +} + +// 显示支付状态页面 +function showPaymentStatus() { + if (!currentOrder) return; + + document.getElementById('status-order-id').textContent = currentOrder.order_id; + document.getElementById('status-amount').textContent = currentOrder.total_amount.toFixed(2); + + paymentStatus.style.display = 'flex'; + + // 开始监听支付状态 + startPaymentStatusCheck(); +} + +// 开始支付状态检查 +function startPaymentStatusCheck() { + // 每10秒检查一次支付状态 + const checkInterval = setInterval(async () => { + const status = await checkPaymentStatus(); + if (status === 'finished' || status === 'failed') { + clearInterval(checkInterval); + } + }, 10000); +} + +// 检查支付状态 +async function checkPaymentStatus() { + if (!currentOrder) return; + + try { + const response = await fetch(`/api/orders/${currentOrder.order_id}`); + const order = await response.json(); + + const statusIcon = document.getElementById('status-icon'); + const statusTitle = document.getElementById('status-title'); + const statusMessage = document.getElementById('status-message'); + + switch (order.payment_status) { + case 'finished': + statusIcon.innerHTML = '
'; + statusTitle.textContent = '支付成功!'; + statusMessage.textContent = '您的订单已确认,我们将尽快处理并发货。'; + break; + case 'failed': + case 'expired': + statusIcon.innerHTML = '
'; + statusTitle.textContent = '支付失败'; + statusMessage.textContent = '支付未完成或已过期,请重新下单。'; + break; + case 'confirming': + statusTitle.textContent = '支付确认中...'; + statusMessage.textContent = '我们已收到您的支付,正在等待区块链确认。'; + break; + default: + statusTitle.textContent = '等待支付...'; + statusMessage.textContent = '请完成USDT支付,我们正在等待您的交易。'; + } + + return order.payment_status; + } catch (error) { + console.error('检查支付状态失败:', error); + return null; + } +} + +// 工具函数:验证邮箱 +function validateEmail(email) { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); +} + +// 工具函数:验证电话号码 +function validatePhone(phone) { + const phoneRegex = /^[\d\s\-\+\(\)]+$/; + return phoneRegex.test(phone); +} + +// 管理员登录相关函数 +function showAdminLogin() { + document.getElementById('adminLoginModal').style.display = 'flex'; + document.getElementById('adminPassword').focus(); +} + +function closeAdminLogin() { + document.getElementById('adminLoginModal').style.display = 'none'; + document.getElementById('adminPassword').value = ''; + document.getElementById('loginError').style.display = 'none'; +} + +function verifyAdminPassword() { + const password = document.getElementById('adminPassword').value; + const errorDiv = document.getElementById('loginError'); + + if (password === '223388') { + // 密码正确,跳转到管理页面 + window.location.href = '/admin.html'; + } else { + // 密码错误 + errorDiv.textContent = '密码错误,请重新输入'; + errorDiv.style.display = 'block'; + document.getElementById('adminPassword').value = ''; + document.getElementById('adminPassword').focus(); + } +} + +// 键盘事件监听 +document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') { + closeAdminLogin(); + } +}); + +// 管理员登录密码框回车事件 +document.addEventListener('DOMContentLoaded', function() { + const adminPasswordInput = document.getElementById('adminPassword'); + if (adminPasswordInput) { + adminPasswordInput.addEventListener('keypress', function(e) { + if (e.key === 'Enter') { + verifyAdminPassword(); + } + }); + } +}); \ No newline at end of file diff --git a/public/manual-payment.html b/public/manual-payment.html new file mode 100644 index 0000000..e2cfbd5 --- /dev/null +++ b/public/manual-payment.html @@ -0,0 +1,222 @@ + + + + + + 手动支付确认 - USDT商城 + + + +
+
+

手动支付确认

+

由于UPay API暂时不可用,请手动确认支付状态

+ +
+

支付信息

+
+ 订单号: + - +
+
+ 支付金额: + - USDT +
+
+ 当前状态: + 待支付 +
+
+ +
+

模拟支付操作

+ + + +
+ +
+ +
+
+
+ + + + + + \ No newline at end of file diff --git a/public/success.html b/public/success.html new file mode 100644 index 0000000..fd9f869 --- /dev/null +++ b/public/success.html @@ -0,0 +1,159 @@ + + + + + + 支付成功 - USDT商城 + + + +
+
+
+
+
+

支付成功!

+

感谢您的购买,我们已收到您的USDT支付

+ +
+

订单详情

+
+ 订单号: + - +
+
+ 支付状态: + 已完成 +
+
+ +
+ + +
+
+
+ + + + + + \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..c45105a --- /dev/null +++ b/server.js @@ -0,0 +1,384 @@ +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 = 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_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` + }; + + // 生成签名 + 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}`); +}); \ No newline at end of file