完整示例
端到端的电商平台支付集成完整示例
18 分钟阅读
更新于 2025/8/13
完整示例电商集成
本示例展示如何在一个电商平台中完整集成 Payve 支付系统,包括前端、后端、数据库和 Webhook 处理的完整实现。
项目结构
ecommerce-payve-integration/
├── backend/
│ ├── src/
│ │ ├── controllers/
│ │ │ ├── order.controller.js
│ │ │ ├── payment.controller.js
│ │ │ └── webhook.controller.js
│ │ ├── services/
│ │ │ ├── payve.service.js
│ │ │ ├── order.service.js
│ │ │ └── notification.service.js
│ │ ├── models/
│ │ │ ├── order.model.js
│ │ │ ├── payment.model.js
│ │ │ └── user.model.js
│ │ ├── middleware/
│ │ │ ├── auth.middleware.js
│ │ │ └── webhook.middleware.js
│ │ ├── config/
│ │ │ └── database.js
│ │ └── app.js
│ ├── package.json
│ └── .env
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── Checkout.jsx
│ │ │ ├── PaymentModal.jsx
│ │ │ └── OrderStatus.jsx
│ │ ├── hooks/
│ │ │ └── usePayment.js
│ │ ├── services/
│ │ │ └── api.js
│ │ └── App.jsx
│ └── package.json
└── docker-compose.yml
PLAIN TEXT
代码已折叠
共 37 行
后端实现
1. 环境配置 (.env)
# 服务器配置
PORT=3000
NODE_ENV=production
# 数据库配置
DATABASE_URL=postgresql://user:password@localhost:5432/ecommerce
# Payve 配置
PAYVE_API_KEY=your-api-key
PAYVE_API_SECRET=your-api-secret
PAYVE_WEBHOOK_SECRET=your-webhook-secret
PAYVE_MERCHANT_ID=your-merchant-id
PAYVE_API_URL=https://api.payve.io
# Redis 配置(用于缓存和会话)
REDIS_URL=redis://localhost:6379ENV
代码已折叠
共 16 行
2. 数据库模型
// models/order.model.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const Order = sequelize.define('Order', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
userId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
orderNumber: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
status: {
type: DataTypes.ENUM(
'pending',
'payment_pending',
'paid',
'processing',
'shipped',
'delivered',
'cancelled',
'refunded'
),
defaultValue: 'pending'
},
items: {
type: DataTypes.JSON,
allowNull: false
},
subtotal: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
tax: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0
},
shipping: {
type: DataTypes.DECIMAL(10, 2),
defaultValue: 0
},
total: {
type: DataTypes.DECIMAL(10, 2),
allowNull: false
},
currency: {
type: DataTypes.STRING(3),
defaultValue: 'USD'
},
shippingAddress: {
type: DataTypes.JSON,
allowNull: false
},
billingAddress: {
type: DataTypes.JSON,
allowNull: false
},
paymentMethod: {
type: DataTypes.STRING,
allowNull: true
},
notes: {
type: DataTypes.TEXT,
allowNull: true
}
}, {
timestamps: true,
indexes: [
{ fields: ['userId'] },
{ fields: ['status'] },
{ fields: ['orderNumber'] }
]
});
module.exports = Order;JAVASCRIPT
代码已折叠
共 86 行
// models/payment.model.js
const { DataTypes } = require('sequelize');
const sequelize = require('../config/database');
const Payment = sequelize.define('Payment', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
orderId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'Orders',
key: 'id'
}
},
payveOrderId: {
type: DataTypes.STRING,
unique: true,
allowNull: false
},
status: {
type: DataTypes.ENUM(
'created',
'pending',
'confirming',
'completed',
'failed',
'expired',
'refunded'
),
defaultValue: 'created'
},
amount: {
type: DataTypes.DECIMAL(18, 8),
allowNull: false
},
currency: {
type: DataTypes.STRING(10),
allowNull: false
},
network: {
type: DataTypes.STRING(20),
allowNull: true
},
address: {
type: DataTypes.STRING,
allowNull: true
},
txHash: {
type: DataTypes.STRING,
allowNull: true
},
confirmations: {
type: DataTypes.INTEGER,
defaultValue: 0
},
requiredConfirmations: {
type: DataTypes.INTEGER,
defaultValue: 6
},
paidAmount: {
type: DataTypes.DECIMAL(18, 8),
allowNull: true
},
paidAt: {
type: DataTypes.DATE,
allowNull: true
},
expiresAt: {
type: DataTypes.DATE,
allowNull: true
},
webhookEvents: {
type: DataTypes.JSON,
defaultValue: []
},
metadata: {
type: DataTypes.JSON,
defaultValue: {}
}
}, {
timestamps: true,
indexes: [
{ fields: ['orderId'] },
{ fields: ['payveOrderId'] },
{ fields: ['status'] },
{ fields: ['txHash'] }
]
});
module.exports = Payment;JAVASCRIPT
代码已折叠
共 94 行
3. Payve 服务封装
// services/payve.service.js
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const axios = require('axios');
class PayveService {
constructor() {
this.apiKey = process.env.PAYVE_API_KEY;
this.apiSecret = process.env.PAYVE_API_SECRET;
this.merchantId = process.env.PAYVE_MERCHANT_ID;
this.apiUrl = process.env.PAYVE_API_URL;
this.webhookSecret = process.env.PAYVE_WEBHOOK_SECRET;
}
// 生成 JWT Token
async generateToken() {
const payload = {
merchantId: this.merchantId,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600 // 1小时过期
};
return jwt.sign(payload, this.apiSecret, { algorithm: 'HS256' });
}
// 创建支付订单
async createPayment(options) {
const {
amount,
currency = 'USDT',
orderId,
description,
callbackUrl,
returnUrl,
metadata = {}
} = options;
const token = await this.generateToken();
try {
const response = await axios.post(
`${this.apiUrl}/api/v1/payments`,
{
amount,
currency,
description,
callbackUrl: callbackUrl || `${process.env.APP_URL}/webhook/payve`,
returnUrl: returnUrl || `${process.env.APP_URL}/order/success`,
metadata: {
...metadata,
orderId,
merchantOrderId: orderId
}
},
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
}
);
return response.data;
} catch (error) {
console.error('Payve API Error:', error.response?.data || error.message);
throw new Error('Failed to create payment');
}
}
// 查询支付状态
async getPaymentStatus(payveOrderId) {
const token = await this.generateToken();
try {
const response = await axios.get(
`${this.apiUrl}/api/v1/payments/${payveOrderId}`,
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
return response.data;
} catch (error) {
console.error('Payve API Error:', error.response?.data || error.message);
throw new Error('Failed to get payment status');
}
}
// 验证 Webhook 签名
verifyWebhookSignature(payload, signature, timestamp) {
// 检查时间戳防止重放攻击
const currentTime = Math.floor(Date.now() / 1000);
if (Math.abs(currentTime - parseInt(timestamp)) > 300) {
return false;
}
// 计算签名
const signaturePayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', this.webhookSecret)
.update(signaturePayload)
.digest('hex');
// 恒定时间比较
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// 批量查询支付状态
async batchGetPaymentStatus(payveOrderIds) {
const token = await this.generateToken();
try {
const promises = payveOrderIds.map(id =>
this.getPaymentStatus(id).catch(err => ({
orderId: id,
error: err.message
}))
);
return await Promise.all(promises);
} catch (error) {
console.error('Batch status check failed:', error);
throw error;
}
}
// 取消支付订单
async cancelPayment(payveOrderId) {
const token = await this.generateToken();
try {
const response = await axios.post(
`${this.apiUrl}/api/v1/payments/${payveOrderId}/cancel`,
{},
{
headers: {
'Authorization': `Bearer ${token}`
}
}
);
return response.data;
} catch (error) {
console.error('Cancel payment error:', error.response?.data || error.message);
throw new Error('Failed to cancel payment');
}
}
}
module.exports = new PayveService();JAVASCRIPT
代码已折叠
共 155 行
4. 支付控制器
// controllers/payment.controller.js
const { Order, Payment } = require('../models');
const payveService = require('../services/payve.service');
const { sequelize } = require('../config/database');
class PaymentController {
// 创建支付
async createPayment(req, res) {
const transaction = await sequelize.transaction();
try {
const { orderId } = req.params;
const { currency = 'USDT' } = req.body;
// 获取订单信息
const order = await Order.findByPk(orderId, { transaction });
if (!order) {
await transaction.rollback();
return res.status(404).json({ error: 'Order not found' });
}
if (order.status !== 'pending') {
await transaction.rollback();
return res.status(400).json({ error: 'Order is not payable' });
}
// 检查是否已有未完成的支付
const existingPayment = await Payment.findOne({
where: {
orderId: order.id,
status: ['created', 'pending', 'confirming']
},
transaction
});
if (existingPayment) {
// 检查是否过期
if (existingPayment.expiresAt && existingPayment.expiresAt < new Date()) {
existingPayment.status = 'expired';
await existingPayment.save({ transaction });
} else {
await transaction.rollback();
return res.json({
payment: existingPayment,
message: 'Payment already exists'
});
}
}
// 创建 Payve 支付订单
const payvePayment = await payveService.createPayment({
amount: order.total,
currency,
orderId: order.id,
description: `Order #${order.orderNumber}`,
metadata: {
userId: order.userId,
orderNumber: order.orderNumber,
items: order.items
}
});
// 保存支付记录
const payment = await Payment.create({
orderId: order.id,
payveOrderId: payvePayment.orderId,
status: 'pending',
amount: payvePayment.amount,
currency: payvePayment.currency,
network: payvePayment.network,
address: payvePayment.address,
expiresAt: payvePayment.expiresAt,
metadata: payvePayment.metadata
}, { transaction });
// 更新订单状态
order.status = 'payment_pending';
order.paymentMethod = 'crypto';
await order.save({ transaction });
await transaction.commit();
res.json({
success: true,
payment: {
id: payment.id,
orderId: payment.orderId,
payveOrderId: payment.payveOrderId,
amount: payment.amount,
currency: payment.currency,
network: payment.network,
address: payment.address,
qrCode: payvePayment.qrCode,
expiresAt: payment.expiresAt,
status: payment.status
}
});
} catch (error) {
await transaction.rollback();
console.error('Create payment error:', error);
res.status(500).json({ error: 'Failed to create payment' });
}
}
// 查询支付状态
async getPaymentStatus(req, res) {
try {
const { paymentId } = req.params;
const payment = await Payment.findOne({
where: { id: paymentId },
include: [{
model: Order,
attributes: ['id', 'orderNumber', 'status', 'total']
}]
});
if (!payment) {
return res.status(404).json({ error: 'Payment not found' });
}
// 如果支付还在进行中,查询最新状态
if (['pending', 'confirming'].includes(payment.status)) {
try {
const latestStatus = await payveService.getPaymentStatus(payment.payveOrderId);
// 更新本地状态
if (latestStatus.status !== payment.status) {
payment.status = latestStatus.status;
payment.confirmations = latestStatus.confirmations || 0;
payment.txHash = latestStatus.txHash || payment.txHash;
if (latestStatus.status === 'COMPLETED') {
payment.paidAmount = latestStatus.paidAmount;
payment.paidAt = new Date();
}
await payment.save();
}
} catch (error) {
console.error('Failed to fetch latest status:', error);
}
}
res.json({
payment: {
id: payment.id,
status: payment.status,
amount: payment.amount,
currency: payment.currency,
confirmations: payment.confirmations,
requiredConfirmations: payment.requiredConfirmations,
txHash: payment.txHash,
paidAt: payment.paidAt,
expiresAt: payment.expiresAt,
order: payment.Order
}
});
} catch (error) {
console.error('Get payment status error:', error);
res.status(500).json({ error: 'Failed to get payment status' });
}
}
// 取消支付
async cancelPayment(req, res) {
const transaction = await sequelize.transaction();
try {
const { paymentId } = req.params;
const payment = await Payment.findByPk(paymentId, { transaction });
if (!payment) {
await transaction.rollback();
return res.status(404).json({ error: 'Payment not found' });
}
if (!['created', 'pending'].includes(payment.status)) {
await transaction.rollback();
return res.status(400).json({ error: 'Payment cannot be cancelled' });
}
// 调用 Payve API 取消支付
await payveService.cancelPayment(payment.payveOrderId);
// 更新状态
payment.status = 'cancelled';
await payment.save({ transaction });
// 更新订单状态
const order = await Order.findByPk(payment.orderId, { transaction });
if (order && order.status === 'payment_pending') {
order.status = 'pending';
await order.save({ transaction });
}
await transaction.commit();
res.json({
success: true,
message: 'Payment cancelled successfully'
});
} catch (error) {
await transaction.rollback();
console.error('Cancel payment error:', error);
res.status(500).json({ error: 'Failed to cancel payment' });
}
}
}
module.exports = new PaymentController();JAVASCRIPT
代码已折叠
共 216 行
5. Webhook 处理
// controllers/webhook.controller.js
const { Order, Payment } = require('../models');
const payveService = require('../services/payve.service');
const notificationService = require('../services/notification.service');
const { sequelize } = require('../config/database');
class WebhookController {
async handlePayveWebhook(req, res) {
const transaction = await sequelize.transaction();
try {
// 验证签名
const signature = req.headers['x-payve-signature'];
const timestamp = req.headers['x-payve-timestamp'];
const eventId = req.headers['x-payve-event-id'];
if (!payveService.verifyWebhookSignature(
req.rawBody,
signature,
timestamp
)) {
await transaction.rollback();
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.rawBody.toString());
// 检查是否已处理(幂等性)
const payment = await Payment.findOne({
where: { payveOrderId: event.data.orderId },
transaction
});
if (!payment) {
await transaction.rollback();
return res.status(404).json({ error: 'Payment not found' });
}
// 检查是否已处理此事件
const processedEvents = payment.webhookEvents || [];
if (processedEvents.includes(eventId)) {
await transaction.rollback();
return res.json({ message: 'Event already processed' });
}
// 根据事件类型处理
switch (event.type) {
case 'payment.succeeded':
await this.handlePaymentSuccess(event.data, payment, transaction);
break;
case 'payment.confirming':
await this.handlePaymentConfirming(event.data, payment, transaction);
break;
case 'payment.failed':
await this.handlePaymentFailed(event.data, payment, transaction);
break;
case 'payment.expired':
await this.handlePaymentExpired(event.data, payment, transaction);
break;
default:
console.log('Unknown webhook event type:', event.type);
}
// 记录已处理的事件
payment.webhookEvents = [...processedEvents, eventId];
await payment.save({ transaction });
await transaction.commit();
res.json({ success: true });
} catch (error) {
await transaction.rollback();
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Processing failed' });
}
}
async handlePaymentSuccess(data, payment, transaction) {
// 更新支付记录
payment.status = 'completed';
payment.txHash = data.txHash;
payment.confirmations = data.confirmations;
payment.paidAmount = data.paidAmount;
payment.paidAt = new Date(data.paidAt);
await payment.save({ transaction });
// 更新订单状态
const order = await Order.findByPk(payment.orderId, { transaction });
if (order) {
order.status = 'paid';
await order.save({ transaction });
// 发送通知
await notificationService.sendOrderPaidNotification(order, payment);
// 触发订单履行流程
await this.fulfillOrder(order, transaction);
}
console.log(`Payment ${payment.payveOrderId} succeeded`);
}
async handlePaymentConfirming(data, payment, transaction) {
payment.status = 'confirming';
payment.txHash = data.txHash;
payment.confirmations = data.confirmations;
await payment.save({ transaction });
console.log(`Payment ${payment.payveOrderId} confirming: ${data.confirmations}/${data.requiredConfirmations}`);
}
async handlePaymentFailed(data, payment, transaction) {
payment.status = 'failed';
payment.failureReason = data.failureReason;
await payment.save({ transaction });
// 更新订单状态
const order = await Order.findByPk(payment.orderId, { transaction });
if (order && order.status === 'payment_pending') {
order.status = 'pending';
await order.save({ transaction });
// 发送失败通知
await notificationService.sendPaymentFailedNotification(order, payment);
}
console.log(`Payment ${payment.payveOrderId} failed: ${data.failureReason}`);
}
async handlePaymentExpired(data, payment, transaction) {
payment.status = 'expired';
await payment.save({ transaction });
// 更新订单状态
const order = await Order.findByPk(payment.orderId, { transaction });
if (order && order.status === 'payment_pending') {
order.status = 'pending';
await order.save({ transaction });
}
console.log(`Payment ${payment.payveOrderId} expired`);
}
async fulfillOrder(order, transaction) {
// 实现订单履行逻辑
// 例如:减少库存、生成发货单、通知仓库等
console.log(`Fulfilling order ${order.orderNumber}`);
// 更新订单状态为处理中
order.status = 'processing';
await order.save({ transaction });
// 这里可以触发其他业务流程
// await inventoryService.reduceStock(order.items);
// await shippingService.createShipment(order);
}
}
module.exports = new WebhookController();JAVASCRIPT
代码已折叠
共 164 行
前端实现
1. 支付 Hook
// hooks/usePayment.js
import { useState, useCallback, useEffect } from 'react';
import { usePayve } from '@payve/react';
import api from '../services/api';
export function usePayment(orderId) {
const { subscribeToPayment, unsubscribe } = usePayve();
const [payment, setPayment] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [status, setStatus] = useState('idle');
// 创建支付
const createPayment = useCallback(async (currency = 'USDT') => {
setLoading(true);
setError(null);
try {
const response = await api.post(`/orders/${orderId}/payment`, {
currency
});
setPayment(response.data.payment);
setStatus('pending');
// 订阅支付状态更新
const subscription = subscribeToPayment(
response.data.payment.payveOrderId,
(event) => {
handlePaymentUpdate(event);
}
);
return response.data.payment;
} catch (err) {
setError(err.message);
setStatus('error');
throw err;
} finally {
setLoading(false);
}
}, [orderId]);
// 处理支付更新
const handlePaymentUpdate = useCallback((event) => {
console.log('Payment update:', event);
switch (event.type) {
case 'payment.confirming':
setStatus('confirming');
setPayment(prev => ({
...prev,
confirmations: event.data.confirmations,
txHash: event.data.txHash
}));
break;
case 'payment.succeeded':
setStatus('completed');
setPayment(prev => ({
...prev,
status: 'completed',
paidAt: event.data.paidAt,
txHash: event.data.txHash
}));
break;
case 'payment.failed':
setStatus('failed');
setError(event.data.failureReason);
break;
case 'payment.expired':
setStatus('expired');
break;
default:
console.log('Unknown event:', event.type);
}
}, []);
// 查询支付状态
const checkStatus = useCallback(async () => {
if (!payment?.id) return;
try {
const response = await api.get(`/payments/${payment.id}/status`);
setPayment(response.data.payment);
if (response.data.payment.status === 'completed') {
setStatus('completed');
} else if (response.data.payment.status === 'failed') {
setStatus('failed');
}
return response.data.payment;
} catch (err) {
console.error('Failed to check status:', err);
}
}, [payment?.id]);
// 取消支付
const cancelPayment = useCallback(async () => {
if (!payment?.id) return;
try {
await api.post(`/payments/${payment.id}/cancel`);
setStatus('cancelled');
setPayment(null);
} catch (err) {
setError(err.message);
throw err;
}
}, [payment?.id]);
// 清理
useEffect(() => {
return () => {
if (payment?.payveOrderId) {
unsubscribe(`payment.${payment.payveOrderId}`);
}
};
}, [payment?.payveOrderId]);
return {
payment,
loading,
error,
status,
createPayment,
checkStatus,
cancelPayment
};
}JSX
代码已折叠
共 134 行
2. 结账组件
// components/Checkout.jsx
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import PaymentModal from './PaymentModal';
import { useCart } from '../hooks/useCart';
import { useAuth } from '../hooks/useAuth';
import api from '../services/api';
function Checkout() {
const navigate = useNavigate();
const { cart, clearCart } = useCart();
const { user } = useAuth();
const [order, setOrder] = useState(null);
const [loading, setLoading] = useState(false);
const [showPayment, setShowPayment] = useState(false);
const [shippingInfo, setShippingInfo] = useState({
fullName: '',
email: '',
phone: '',
address: '',
city: '',
state: '',
zipCode: '',
country: 'US'
});
// 计算订单总额
const calculateTotal = () => {
const subtotal = cart.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const tax = subtotal * 0.1; // 10% 税
const shipping = subtotal > 100 ? 0 : 10; // 满100免运费
return {
subtotal,
tax,
shipping,
total: subtotal + tax + shipping
};
};
// 创建订单
const createOrder = async () => {
setLoading(true);
try {
const { subtotal, tax, shipping, total } = calculateTotal();
const response = await api.post('/orders', {
items: cart.map(item => ({
productId: item.id,
name: item.name,
price: item.price,
quantity: item.quantity
})),
subtotal,
tax,
shipping,
total,
shippingAddress: shippingInfo,
billingAddress: shippingInfo
});
setOrder(response.data.order);
setShowPayment(true);
} catch (error) {
console.error('Failed to create order:', error);
alert('创建订单失败,请重试');
} finally {
setLoading(false);
}
};
// 处理支付成功
const handlePaymentSuccess = async (payment) => {
console.log('Payment successful:', payment);
// 清空购物车
clearCart();
// 跳转到订单成功页面
navigate(`/order/success/${order.id}`);
};
// 处理支付失败
const handlePaymentFailed = () => {
setShowPayment(false);
alert('支付失败,请重试');
};
const { subtotal, tax, shipping, total } = calculateTotal();
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">结账</h1>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
{/* 左侧:配送信息 */}
<div>
<h2 className="text-xl font-semibold mb-4">配送信息</h2>
<form className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">
姓名 *
</label>
<input
type="text"
value={shippingInfo.fullName}
onChange={(e) => setShippingInfo({
...shippingInfo,
fullName: e.target.value
})}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">
邮箱 *
</label>
<input
type="email"
value={shippingInfo.email}
onChange={(e) => setShippingInfo({
...shippingInfo,
email: e.target.value
})}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">
电话 *
</label>
<input
type="tel"
value={shippingInfo.phone}
onChange={(e) => setShippingInfo({
...shippingInfo,
phone: e.target.value
})}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">
地址 *
</label>
<input
type="text"
value={shippingInfo.address}
onChange={(e) => setShippingInfo({
...shippingInfo,
address: e.target.value
})}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-1">
城市 *
</label>
<input
type="text"
value={shippingInfo.city}
onChange={(e) => setShippingInfo({
...shippingInfo,
city: e.target.value
})}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">
邮编 *
</label>
<input
type="text"
value={shippingInfo.zipCode}
onChange={(e) => setShippingInfo({
...shippingInfo,
zipCode: e.target.value
})}
className="w-full px-3 py-2 border rounded-lg"
required
/>
</div>
</div>
</form>
</div>
{/* 右侧:订单摘要 */}
<div>
<h2 className="text-xl font-semibold mb-4">订单摘要</h2>
<div className="bg-gray-50 rounded-lg p-6">
{/* 商品列表 */}
<div className="space-y-3 mb-4">
{cart.map(item => (
<div key={item.id} className="flex justify-between">
<span className="text-gray-600">
{item.name} x {item.quantity}
</span>
<span>${(item.price * item.quantity).toFixed(2)}</span>
</div>
))}
</div>
<div className="border-t pt-4 space-y-2">
<div className="flex justify-between">
<span className="text-gray-600">小计</span>
<span>${subtotal.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">税费</span>
<span>${tax.toFixed(2)}</span>
</div>
<div className="flex justify-between">
<span className="text-gray-600">运费</span>
<span>${shipping.toFixed(2)}</span>
</div>
</div>
<div className="border-t pt-4 mt-4">
<div className="flex justify-between text-lg font-semibold">
<span>总计</span>
<span>${total.toFixed(2)}</span>
</div>
</div>
<button
onClick={createOrder}
disabled={loading || cart.length === 0}
className="w-full mt-6 bg-blue-600 text-white py-3 rounded-lg hover:bg-blue-700 disabled:opacity-50"
>
{loading ? '处理中...' : '立即支付'}
</button>
</div>
{/* 支付方式说明 */}
<div className="mt-6 p-4 bg-blue-50 rounded-lg">
<h3 className="font-semibold mb-2">支持的支付方式</h3>
<div className="grid grid-cols-3 gap-2 text-sm">
<span className="bg-white px-2 py-1 rounded">USDT</span>
<span className="bg-white px-2 py-1 rounded">USDC</span>
<span className="bg-white px-2 py-1 rounded">BTC</span>
<span className="bg-white px-2 py-1 rounded">ETH</span>
<span className="bg-white px-2 py-1 rounded">BNB</span>
<span className="bg-white px-2 py-1 rounded">SOL</span>
</div>
</div>
</div>
</div>
{/* 支付弹窗 */}
{showPayment && order && (
<PaymentModal
isOpen={showPayment}
onClose={() => setShowPayment(false)}
orderId={order.id}
amount={order.total}
onSuccess={handlePaymentSuccess}
onFailed={handlePaymentFailed}
/>
)}
</div>
);
}
export default Checkout;JSX
代码已折叠
共 280 行
3. 订单状态组件
// components/OrderStatus.jsx
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import { CheckCircle, Clock, Package, Truck, Home } from 'lucide-react';
import api from '../services/api';
function OrderStatus() {
const { orderId } = useParams();
const [order, setOrder] = useState(null);
const [payment, setPayment] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchOrderDetails();
}, [orderId]);
const fetchOrderDetails = async () => {
try {
const response = await api.get(`/orders/${orderId}`);
setOrder(response.data.order);
setPayment(response.data.payment);
} catch (error) {
console.error('Failed to fetch order:', error);
} finally {
setLoading(false);
}
};
const getStatusSteps = () => {
const steps = [
{
id: 'pending',
name: '订单创建',
icon: Clock,
completed: true
},
{
id: 'paid',
name: '支付成功',
icon: CheckCircle,
completed: ['paid', 'processing', 'shipped', 'delivered'].includes(order?.status)
},
{
id: 'processing',
name: '处理中',
icon: Package,
completed: ['processing', 'shipped', 'delivered'].includes(order?.status)
},
{
id: 'shipped',
name: '已发货',
icon: Truck,
completed: ['shipped', 'delivered'].includes(order?.status)
},
{
id: 'delivered',
name: '已送达',
icon: Home,
completed: order?.status === 'delivered'
}
];
return steps;
};
if (loading) {
return (
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
if (!order) {
return (
<div className="text-center py-12">
<p className="text-gray-500">订单不存在</p>
</div>
);
}
const steps = getStatusSteps();
return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-3xl mx-auto">
<h1 className="text-3xl font-bold mb-8">订单状态</h1>
{/* 订单信息 */}
<div className="bg-white rounded-lg shadow p-6 mb-8">
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<p className="text-gray-600 text-sm">订单号</p>
<p className="font-semibold">{order.orderNumber}</p>
</div>
<div>
<p className="text-gray-600 text-sm">下单时间</p>
<p className="font-semibold">
{new Date(order.createdAt).toLocaleString()}
</p>
</div>
<div>
<p className="text-gray-600 text-sm">订单金额</p>
<p className="font-semibold">${order.total}</p>
</div>
<div>
<p className="text-gray-600 text-sm">支付方式</p>
<p className="font-semibold">
{payment ? `${payment.currency} (${payment.network})` : '未支付'}
</p>
</div>
</div>
{/* 支付信息 */}
{payment && payment.status === 'completed' && (
<div className="border-t pt-4 mt-4">
<p className="text-gray-600 text-sm mb-2">交易信息</p>
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="text-gray-500">交易哈希:</span>
<a
href={`https://etherscan.io/tx/${payment.txHash}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
{payment.txHash.slice(0, 10)}...{payment.txHash.slice(-8)}
</a>
</div>
<div>
<span className="text-gray-500">支付时间:</span>
<span>{new Date(payment.paidAt).toLocaleString()}</span>
</div>
</div>
</div>
)}
</div>
{/* 状态步骤 */}
<div className="bg-white rounded-lg shadow p-6 mb-8">
<h2 className="text-xl font-semibold mb-6">订单进度</h2>
<div className="relative">
{/* 进度线 */}
<div className="absolute left-8 top-8 bottom-8 w-0.5 bg-gray-200"></div>
{/* 步骤 */}
<div className="space-y-8">
{steps.map((step, index) => {
const Icon = step.icon;
return (
<div key={step.id} className="flex items-start">
<div className={`
relative z-10 w-16 h-16 rounded-full flex items-center justify-center
${step.completed
? 'bg-green-100 text-green-600'
: 'bg-gray-100 text-gray-400'}
`}>
<Icon size={24} />
</div>
<div className="ml-6 flex-1">
<h3 className={`font-semibold ${
step.completed ? 'text-gray-900' : 'text-gray-400'
}`}>
{step.name}
</h3>
{step.completed && (
<p className="text-sm text-gray-600 mt-1">
{step.id === order.status && '当前状态'}
</p>
)}
</div>
</div>
);
})}
</div>
</div>
</div>
{/* 商品列表 */}
<div className="bg-white rounded-lg shadow p-6">
<h2 className="text-xl font-semibold mb-4">商品清单</h2>
<div className="space-y-4">
{order.items.map((item, index) => (
<div key={index} className="flex items-center justify-between py-3 border-b last:border-0">
<div className="flex items-center">
<img
src={item.image || '/placeholder.png'}
alt={item.name}
className="w-16 h-16 object-cover rounded"
/>
<div className="ml-4">
<p className="font-medium">{item.name}</p>
<p className="text-sm text-gray-600">
数量: {item.quantity}
</p>
</div>
</div>
<p className="font-semibold">
${(item.price * item.quantity).toFixed(2)}
</p>
</div>
))}
</div>
</div>
</div>
</div>
);
}
export default OrderStatus;JSX
代码已折叠
共 212 行
Docker 部署
# docker-compose.yml
version: '3.8'
services:
# PostgreSQL 数据库
postgres:
image: postgres:15
environment:
POSTGRES_DB: ecommerce
POSTGRES_USER: payve
POSTGRES_PASSWORD: secure_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- payve_network
# Redis 缓存
redis:
image: redis:7-alpine
command: redis-server --requirepass redis_password
ports:
- "6379:6379"
networks:
- payve_network
# 后端服务
backend:
build: ./backend
environment:
NODE_ENV: production
DATABASE_URL: postgresql://payve:secure_password@postgres:5432/ecommerce
REDIS_URL: redis://:redis_password@redis:6379
PAYVE_API_KEY: ${PAYVE_API_KEY}
PAYVE_API_SECRET: ${PAYVE_API_SECRET}
PAYVE_WEBHOOK_SECRET: ${PAYVE_WEBHOOK_SECRET}
PAYVE_MERCHANT_ID: ${PAYVE_MERCHANT_ID}
ports:
- "3000:3000"
depends_on:
- postgres
- redis
networks:
- payve_network
volumes:
- ./backend/logs:/app/logs
# 前端服务
frontend:
build: ./frontend
environment:
REACT_APP_API_URL: http://backend:3000
REACT_APP_PAYVE_API_KEY: ${PAYVE_API_KEY}
ports:
- "3001:80"
depends_on:
- backend
networks:
- payve_network
# Nginx 反向代理
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
ports:
- "80:80"
- "443:443"
depends_on:
- frontend
- backend
networks:
- payve_network
networks:
payve_network:
driver: bridge
volumes:
postgres_data:YAML
代码已折叠
共 82 行
生产环境部署清单
1. 安全配置
- 使用 HTTPS/SSL 证书
- 配置 CORS 策略
- 实施速率限制
- 启用 helmet.js 安全头
- 环境变量加密
2. 监控和日志
- 配置错误追踪(Sentry)
- 设置性能监控
- 配置日志聚合
- 设置告警规则
3. 备份和灾备
- 数据库自动备份
- 配置主从复制
- 制定灾难恢复计划
4. 性能优化
- 启用 Redis 缓存
- 配置 CDN
- 实施数据库索引
- 启用 Gzip 压缩
5. 测试
- 单元测试覆盖率 > 80%
- 集成测试
- 负载测试
- 安全渗透测试
总结
这个完整示例展示了如何在电商平台中集成 Payve 支付系统,包括:
- 后端架构:使用 Node.js + Express + Sequelize 构建 RESTful API
- 前端实现:使用 React + Payve SDK 实现支付界面
- 数据库设计:完整的订单和支付数据模型
- Webhook 处理:安全可靠的异步通知处理
- 错误处理:完善的错误处理和重试机制
- 部署方案:使用 Docker Compose 的容器化部署
关键特性:
- 支持多种加密货币支付
- 实时支付状态更新
- 完整的订单生命周期管理
- 安全的 Webhook 验证
- 幂等性保证
- 生产级的错误处理和日志记录
