You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

670 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 支付数据模型
*
* 功能:封装支付表和发票表的数据库操作
*/
const { pool } = require('../config/database');
/**
* 支付模型类
*/
class Payment {
/**
* 根据ID查找支付记录
* @param {number} id - 支付ID
* @returns {Promise<object|null>} 支付对象或null
*/
static async findById(id) {
try {
const [rows] = await pool.query(
`SELECT
p.*,
o.order_no,
o.customer_id,
u.username as customer_name
FROM payments p
LEFT JOIN orders o ON p.order_id = o.id
LEFT JOIN users u ON o.customer_id = u.id
WHERE p.id = ?`,
[id]
);
return rows[0] || null;
} catch (error) {
throw new Error('查询支付记录失败: ' + error.message);
}
}
/**
* 根据订单ID查找支付记录
* @param {number} orderId - 订单ID
* @returns {Promise<object|null>} 支付对象或null
*/
static async findByOrderId(orderId) {
try {
const [rows] = await pool.query(
`SELECT
p.*,
o.order_no,
o.customer_id,
u.username as customer_name
FROM payments p
LEFT JOIN orders o ON p.order_id = o.id
LEFT JOIN users u ON o.customer_id = u.id
WHERE p.order_id = ?`,
[orderId]
);
return rows[0] || null;
} catch (error) {
throw new Error('查询支付记录失败: ' + error.message);
}
}
/**
* 根据交易号查找支付记录
* @param {string} transactionNo - 交易号
* @returns {Promise<object|null>} 支付对象或null
*/
static async findByTransactionNo(transactionNo) {
try {
const [rows] = await pool.query(
`SELECT
p.*,
o.order_no,
o.customer_id,
u.username as customer_name
FROM payments p
LEFT JOIN orders o ON p.order_id = o.id
LEFT JOIN users u ON o.customer_id = u.id
WHERE p.transaction_no = ?`,
[transactionNo]
);
return rows[0] || null;
} catch (error) {
throw new Error('查询支付记录失败: ' + error.message);
}
}
/**
* 获取支付记录列表(分页、筛选)
* @param {object} options - 查询选项
* @returns {Promise<object>} 支付记录列表和总数
*/
static async findAll(options = {}) {
try {
const {
page = 1,
pageSize = 10,
payment_method,
status,
start_date,
end_date
} = options;
const offset = (page - 1) * pageSize;
// 构建WHERE条件
const conditions = [];
const params = [];
if (payment_method) {
conditions.push('p.payment_method = ?');
params.push(payment_method);
}
if (status) {
conditions.push('p.status = ?');
params.push(status);
}
if (start_date && end_date) {
conditions.push('DATE(p.created_at) BETWEEN ? AND ?');
params.push(start_date, end_date);
}
const whereClause = conditions.length > 0
? 'WHERE ' + conditions.join(' AND ')
: '';
// 查询总数
const [countResult] = await pool.query(
`SELECT COUNT(*) as total
FROM payments p
LEFT JOIN orders o ON p.order_id = o.id
${whereClause}`,
params
);
const total = countResult[0].total;
// 查询列表
const [rows] = await pool.query(
`SELECT
p.*,
o.order_no,
o.customer_id,
u.username as customer_name
FROM payments p
LEFT JOIN orders o ON p.order_id = o.id
LEFT JOIN users u ON o.customer_id = u.id
${whereClause}
ORDER BY p.created_at DESC
LIMIT ? OFFSET ?`,
[...params, pageSize, offset]
);
return {
list: rows,
total,
page: parseInt(page),
pageSize: parseInt(pageSize)
};
} catch (error) {
throw new Error('查询支付记录列表失败: ' + error.message);
}
}
/**
* 生成交易号
* @returns {string} 交易号
*/
static generateTransactionNo() {
const timestamp = Date.now();
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
return `PAY${timestamp}${random}`;
}
/**
* 创建支付记录
* @param {object} paymentData - 支付数据
* @returns {Promise<object>} 创建的支付记录
*/
static async create(paymentData) {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const {
order_id,
payment_method,
amount
} = paymentData;
const transaction_no = this.generateTransactionNo();
// 创建支付记录
const [result] = await connection.query(
`INSERT INTO payments
(order_id, transaction_no, payment_method, amount, status)
VALUES (?, ?, ?, ?, 'pending')`,
[order_id, transaction_no, payment_method, amount]
);
await connection.commit();
return await this.findById(result.insertId);
} catch (error) {
await connection.rollback();
throw new Error('创建支付记录失败: ' + error.message);
} finally {
connection.release();
}
}
/**
* 更新支付状态
* @param {number} id - 支付ID
* @param {string} status - 新状态
* @param {string} thirdPartyNo - 第三方交易号(可选)
* @returns {Promise<object>} 更新后的支付记录
*/
static async updateStatus(id, status, thirdPartyNo = null) {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
// 更新支付状态
if (status === 'paid') {
await connection.query(
`UPDATE payments
SET status = ?, paid_at = NOW(), third_party_transaction_no = ?
WHERE id = ?`,
[status, thirdPartyNo, id]
);
} else {
await connection.query(
'UPDATE payments SET status = ? WHERE id = ?',
[status, id]
);
}
// 如果支付成功,更新订单状态
if (status === 'paid') {
const payment = await this.findById(id);
if (payment && payment.order_id) {
await connection.query(
'UPDATE orders SET status = ? WHERE id = ?',
['paid', payment.order_id]
);
}
}
await connection.commit();
return await this.findById(id);
} catch (error) {
await connection.rollback();
throw new Error('更新支付状态失败: ' + error.message);
} finally {
connection.release();
}
}
/**
* 支付成功处理(包含积分奖励)
* @param {number} id - 支付ID
* @param {string} thirdPartyNo - 第三方交易号
* @returns {Promise<object>} 更新后的支付记录
*/
static async handlePaymentSuccess(id, thirdPartyNo = null) {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const payment = await this.findById(id);
if (!payment) {
throw new Error('支付记录不存在');
}
// 更新支付状态
await connection.query(
`UPDATE payments
SET status = 'paid', paid_at = NOW(), third_party_transaction_no = ?
WHERE id = ?`,
[thirdPartyNo, id]
);
// 更新订单状态为已支付
await connection.query(
'UPDATE orders SET status = ? WHERE id = ?',
['paid', payment.order_id]
);
// 更新会员消费金额
await connection.query(
`UPDATE members
SET total_spent = total_spent + ?
WHERE user_id = ?`,
[payment.amount, payment.customer_id]
);
// 计算并增加积分消费1元 = 1积分
const points = Math.floor(payment.amount);
if (points > 0) {
await connection.query(
`UPDATE members
SET points = points + ?,
total_points = total_points + ?
WHERE user_id = ?`,
[points, points, payment.customer_id]
);
// 检查是否需要升级会员等级
const [members] = await connection.query(
'SELECT * FROM members WHERE user_id = ?',
[payment.customer_id]
);
if (members.length > 0) {
const member = members[0];
let newLevel = 'bronze';
if (member.total_points >= 10000) {
newLevel = 'diamond';
} else if (member.total_points >= 5000) {
newLevel = 'gold';
} else if (member.total_points >= 1000) {
newLevel = 'silver';
}
if (newLevel !== member.level) {
await connection.query(
'UPDATE members SET level = ? WHERE user_id = ?',
[newLevel, payment.customer_id]
);
}
}
}
await connection.commit();
return await this.findById(id);
} catch (error) {
await connection.rollback();
throw new Error('处理支付成功失败: ' + error.message);
} finally {
connection.release();
}
}
/**
* 获取支付统计信息
* @param {string} startDate - 开始日期(可选)
* @param {string} endDate - 结束日期(可选)
* @returns {Promise<object>} 统计信息
*/
static async getStatistics(startDate = null, endDate = null) {
try {
let whereClause = "WHERE status = 'paid'";
const params = [];
if (startDate && endDate) {
whereClause += ' AND DATE(paid_at) BETWEEN ? AND ?';
params.push(startDate, endDate);
}
// 总支付金额和笔数
const [totalStats] = await pool.query(
`SELECT
COUNT(*) as total_transactions,
SUM(amount) as total_amount,
AVG(amount) as avg_amount
FROM payments ${whereClause}`,
params
);
// 按支付方式统计
const [methodStats] = await pool.query(
`SELECT
payment_method,
COUNT(*) as count,
SUM(amount) as total_amount
FROM payments ${whereClause}
GROUP BY payment_method`,
params
);
return {
...totalStats[0],
by_method: methodStats
};
} catch (error) {
throw new Error('查询支付统计失败: ' + error.message);
}
}
/**
* 申请退款
* @param {number} id - 支付ID
* @param {string} reason - 退款原因
* @returns {Promise<object>} 更新后的支付记录
*/
static async requestRefund(id, reason) {
try {
await pool.query(
`UPDATE payments
SET status = 'refunding', refund_reason = ?
WHERE id = ? AND status = 'paid'`,
[reason, id]
);
return await this.findById(id);
} catch (error) {
throw new Error('申请退款失败: ' + error.message);
}
}
/**
* 完成退款
* @param {number} id - 支付ID
* @returns {Promise<object>} 更新后的支付记录
*/
static async completeRefund(id) {
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
const payment = await this.findById(id);
if (!payment) {
throw new Error('支付记录不存在');
}
// 更新支付状态
await connection.query(
`UPDATE payments
SET status = 'refunded', refunded_at = NOW()
WHERE id = ?`,
[id]
);
// 更新订单状态
await connection.query(
'UPDATE orders SET status = ? WHERE id = ?',
['refunded', payment.order_id]
);
// 扣减会员消费金额和积分
const points = Math.floor(payment.amount);
await connection.query(
`UPDATE members
SET total_spent = GREATEST(total_spent - ?, 0),
points = GREATEST(points - ?, 0)
WHERE user_id = ?`,
[payment.amount, points, payment.customer_id]
);
await connection.commit();
return await this.findById(id);
} catch (error) {
await connection.rollback();
throw new Error('完成退款失败: ' + error.message);
} finally {
connection.release();
}
}
}
/**
* 发票模型类
*/
class Invoice {
/**
* 根据ID查找发票
* @param {number} id - 发票ID
* @returns {Promise<object|null>} 发票对象或null
*/
static async findById(id) {
try {
const [rows] = await pool.query(
`SELECT
i.*,
p.transaction_no,
p.amount,
o.order_no
FROM invoices i
LEFT JOIN payments p ON i.payment_id = p.id
LEFT JOIN orders o ON p.order_id = o.id
WHERE i.id = ?`,
[id]
);
return rows[0] || null;
} catch (error) {
throw new Error('查询发票失败: ' + error.message);
}
}
/**
* 根据支付ID查找发票
* @param {number} paymentId - 支付ID
* @returns {Promise<object|null>} 发票对象或null
*/
static async findByPaymentId(paymentId) {
try {
const [rows] = await pool.query(
`SELECT
i.*,
p.transaction_no,
p.amount,
o.order_no
FROM invoices i
LEFT JOIN payments p ON i.payment_id = p.id
LEFT JOIN orders o ON p.order_id = o.id
WHERE i.payment_id = ?`,
[paymentId]
);
return rows[0] || null;
} catch (error) {
throw new Error('查询发票失败: ' + error.message);
}
}
/**
* 生成发票号
* @returns {string} 发票号
*/
static generateInvoiceNo() {
const timestamp = Date.now();
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
return `INV${timestamp}${random}`;
}
/**
* 创建发票
* @param {object} invoiceData - 发票数据
* @returns {Promise<object>} 创建的发票
*/
static async create(invoiceData) {
try {
const {
payment_id,
invoice_type,
invoice_title,
tax_no,
email
} = invoiceData;
const invoice_no = this.generateInvoiceNo();
const [result] = await pool.query(
`INSERT INTO invoices
(payment_id, invoice_no, invoice_type, invoice_title, tax_no, email, status)
VALUES (?, ?, ?, ?, ?, ?, 'pending')`,
[payment_id, invoice_no, invoice_type, invoice_title, tax_no || null, email]
);
return await this.findById(result.insertId);
} catch (error) {
throw new Error('创建发票失败: ' + error.message);
}
}
/**
* 更新发票状态
* @param {number} id - 发票ID
* @param {string} status - 新状态
* @returns {Promise<object>} 更新后的发票
*/
static async updateStatus(id, status) {
try {
const updateField = status === 'issued' ? ', issued_at = NOW()' : '';
await pool.query(
`UPDATE invoices SET status = ? ${updateField} WHERE id = ?`,
[status, id]
);
return await this.findById(id);
} catch (error) {
throw new Error('更新发票状态失败: ' + error.message);
}
}
/**
* 获取用户的发票列表
* @param {number} userId - 用户ID
* @returns {Promise<array>} 发票列表
*/
static async getUserInvoices(userId) {
try {
const [rows] = await pool.query(
`SELECT
i.*,
p.transaction_no,
p.amount,
o.order_no
FROM invoices i
JOIN payments p ON i.payment_id = p.id
JOIN orders o ON p.order_id = o.id
WHERE o.customer_id = ?
ORDER BY i.created_at DESC`,
[userId]
);
return rows;
} catch (error) {
throw new Error('查询用户发票列表失败: ' + error.message);
}
}
/**
* 获取所有发票列表(分页)
* @param {object} options - 查询选项
* @returns {Promise<object>} 发票列表
*/
static async findAll(options = {}) {
try {
const {
page = 1,
pageSize = 10,
status
} = options;
const offset = (page - 1) * pageSize;
const whereClause = status ? 'WHERE i.status = ?' : '';
const params = status ? [status] : [];
// 查询总数
const [countResult] = await pool.query(
`SELECT COUNT(*) as total
FROM invoices i ${whereClause}`,
params
);
const total = countResult[0].total;
// 查询列表
const [rows] = await pool.query(
`SELECT
i.*,
p.transaction_no,
p.amount,
o.order_no,
o.customer_id,
u.username as customer_name
FROM invoices i
LEFT JOIN payments p ON i.payment_id = p.id
LEFT JOIN orders o ON p.order_id = o.id
LEFT JOIN users u ON o.customer_id = u.id
${whereClause}
ORDER BY i.created_at DESC
LIMIT ? OFFSET ?`,
[...params, pageSize, offset]
);
return {
list: rows,
total,
page: parseInt(page),
pageSize: parseInt(pageSize)
};
} catch (error) {
throw new Error('查询发票列表失败: ' + error.message);
}
}
}
module.exports = { Payment, Invoice };