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.

484 lines
12 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 { Payment, Invoice } = require('../models/Payment');
const Order = require('../models/Order');
const { validateRequired, validateEmail, validateBatch } = require('../utils/validator');
const { success } = require('../utils/response');
const { BusinessError, ValidationError, AuthorizationError } = require('../middleware/errorHandler');
// ========================================
// 支付相关控制器
// ========================================
/**
* 获取支付记录列表
* GET /api/payments
* 需要管理员权限
*/
const getPayments = async (req, res, next) => {
try {
const {
page = 1,
pageSize = 10,
payment_method,
status,
start_date,
end_date
} = req.query;
const result = await Payment.findAll({
page: parseInt(page),
pageSize: parseInt(pageSize),
payment_method,
status,
start_date,
end_date
});
res.json(success({
list: result.list,
pagination: {
total: result.total,
page: result.page,
pageSize: result.pageSize,
totalPages: Math.ceil(result.total / result.pageSize)
}
}, '获取支付记录成功'));
} catch (err) {
next(err);
}
};
/**
* 获取支付详情
* GET /api/payments/:id
*/
const getPaymentById = async (req, res, next) => {
try {
const { id } = req.params;
const payment = await Payment.findById(id);
if (!payment) {
throw new BusinessError('支付记录不存在', 404);
}
// 权限控制:顾客只能查看自己的支付记录
if (req.user.role === 'customer' && payment.customer_id !== req.user.id) {
throw new AuthorizationError('您没有权限查看此支付记录');
}
res.json(success(payment, '获取支付详情成功'));
} catch (err) {
next(err);
}
};
/**
* 创建支付(发起支付)
* POST /api/payments
*/
const createPayment = async (req, res, next) => {
try {
const { order_id, payment_method } = req.body;
// 数据验证
const validation = validateBatch([
validateRequired(order_id, '订单ID'),
validateRequired(payment_method, '支付方式')
]);
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// 验证支付方式
const validMethods = ['cash', 'card', 'wechat', 'alipay'];
if (!validMethods.includes(payment_method)) {
throw new ValidationError(`支付方式必须是以下之一: ${validMethods.join(', ')}`);
}
// 检查订单是否存在
const order = await Order.findById(order_id);
if (!order) {
throw new BusinessError('订单不存在', 404);
}
// 权限控制:只能支付自己的订单
if (req.user.role === 'customer' && order.customer_id !== req.user.id) {
throw new AuthorizationError('您没有权限支付此订单');
}
// 检查订单状态
if (['paid', 'cancelled', 'refunded'].includes(order.status)) {
throw new BusinessError('该订单无法支付');
}
// 检查是否已有支付记录
const existingPayment = await Payment.findByOrderId(order_id);
if (existingPayment && existingPayment.status === 'paid') {
throw new BusinessError('该订单已支付');
}
// 创建支付记录
const payment = await Payment.create({
order_id,
payment_method,
amount: order.final_amount
});
res.status(201).json(success(payment, '创建支付成功'));
} catch (err) {
next(err);
}
};
/**
* 确认支付(模拟支付成功)
* POST /api/payments/:id/confirm
*/
const confirmPayment = async (req, res, next) => {
try {
const { id } = req.params;
const { third_party_transaction_no } = req.body;
// 检查支付记录是否存在
const payment = await Payment.findById(id);
if (!payment) {
throw new BusinessError('支付记录不存在', 404);
}
// 检查支付状态
if (payment.status === 'paid') {
throw new BusinessError('该支付已完成');
}
if (payment.status === 'refunded') {
throw new BusinessError('该支付已退款');
}
// 处理支付成功(包含积分奖励)
const updatedPayment = await Payment.handlePaymentSuccess(
id,
third_party_transaction_no || null
);
res.json(success(updatedPayment, '支付成功'));
} catch (err) {
next(err);
}
};
/**
* 申请退款
* POST /api/payments/:id/refund
*/
const requestRefund = async (req, res, next) => {
try {
const { id } = req.params;
const { reason } = req.body;
// 数据验证
const validation = validateRequired(reason, '退款原因');
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// 检查支付记录是否存在
const payment = await Payment.findById(id);
if (!payment) {
throw new BusinessError('支付记录不存在', 404);
}
// 权限控制:只能退款自己的订单
if (req.user.role === 'customer' && payment.customer_id !== req.user.id) {
throw new AuthorizationError('您没有权限申请退款');
}
// 检查支付状态
if (payment.status !== 'paid') {
throw new BusinessError('只有已支付的订单才能申请退款');
}
const updatedPayment = await Payment.requestRefund(id, reason);
res.json(success(updatedPayment, '退款申请已提交'));
} catch (err) {
next(err);
}
};
/**
* 完成退款
* POST /api/payments/:id/complete-refund
* 需要管理员权限
*/
const completeRefund = async (req, res, next) => {
try {
const { id } = req.params;
// 检查支付记录是否存在
const payment = await Payment.findById(id);
if (!payment) {
throw new BusinessError('支付记录不存在', 404);
}
// 检查支付状态
if (payment.status !== 'refunding') {
throw new BusinessError('该支付未申请退款');
}
const updatedPayment = await Payment.completeRefund(id);
res.json(success(updatedPayment, '退款完成'));
} catch (err) {
next(err);
}
};
/**
* 获取支付统计
* GET /api/payments/statistics
* 需要管理员权限
*/
const getPaymentStatistics = async (req, res, next) => {
try {
const { start_date, end_date } = req.query;
const stats = await Payment.getStatistics(
start_date || null,
end_date || null
);
res.json(success(stats, '获取支付统计成功'));
} catch (err) {
next(err);
}
};
// ========================================
// 发票相关控制器
// ========================================
/**
* 获取发票列表
* GET /api/payments/invoices
*/
const getInvoices = async (req, res, next) => {
try {
const {
page = 1,
pageSize = 10,
status
} = req.query;
// 权限控制:顾客只能查看自己的发票
if (req.user.role === 'customer') {
const invoices = await Invoice.getUserInvoices(req.user.id);
return res.json(success({
count: invoices.length,
invoices
}, '获取发票列表成功'));
}
// 管理员可以查看所有发票
const result = await Invoice.findAll({
page: parseInt(page),
pageSize: parseInt(pageSize),
status
});
res.json(success({
list: result.list,
pagination: {
total: result.total,
page: result.page,
pageSize: result.pageSize,
totalPages: Math.ceil(result.total / result.pageSize)
}
}, '获取发票列表成功'));
} catch (err) {
next(err);
}
};
/**
* 获取发票详情
* GET /api/payments/invoices/:id
*/
const getInvoiceById = async (req, res, next) => {
try {
const { id } = req.params;
const invoice = await Invoice.findById(id);
if (!invoice) {
throw new BusinessError('发票不存在', 404);
}
// 权限控制需要通过payment查找customer_id
const payment = await Payment.findById(invoice.payment_id);
if (req.user.role === 'customer' && payment.customer_id !== req.user.id) {
throw new AuthorizationError('您没有权限查看此发票');
}
res.json(success(invoice, '获取发票详情成功'));
} catch (err) {
next(err);
}
};
/**
* 申请开票
* POST /api/payments/invoices
*/
const createInvoice = async (req, res, next) => {
try {
const {
payment_id,
invoice_type,
invoice_title,
tax_no,
email
} = req.body;
// 数据验证
const validation = validateBatch([
validateRequired(payment_id, '支付ID'),
validateRequired(invoice_type, '发票类型'),
validateRequired(invoice_title, '发票抬头'),
validateRequired(email, '邮箱'),
validateEmail(email, '邮箱')
]);
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// 验证发票类型
const validTypes = ['personal', 'company'];
if (!validTypes.includes(invoice_type)) {
throw new ValidationError('发票类型必须是 personal 或 company');
}
// 企业发票需要税号
if (invoice_type === 'company' && !tax_no) {
throw new ValidationError('企业发票需要提供税号');
}
// 检查支付记录是否存在
const payment = await Payment.findById(payment_id);
if (!payment) {
throw new BusinessError('支付记录不存在', 404);
}
// 权限控制:只能为自己的支付开票
if (req.user.role === 'customer' && payment.customer_id !== req.user.id) {
throw new AuthorizationError('您没有权限为此支付开票');
}
// 检查支付状态
if (payment.status !== 'paid') {
throw new BusinessError('只有已支付的订单才能开具发票');
}
// 检查是否已开票
const existingInvoice = await Invoice.findByPaymentId(payment_id);
if (existingInvoice) {
throw new BusinessError('该支付已申请开票');
}
const invoice = await Invoice.create({
payment_id,
invoice_type,
invoice_title,
tax_no: tax_no || null,
email
});
res.status(201).json(success(invoice, '申请开票成功'));
} catch (err) {
next(err);
}
};
/**
* 更新发票状态(开具发票)
* PUT /api/payments/invoices/:id/status
* 需要管理员权限
*/
const updateInvoiceStatus = async (req, res, next) => {
try {
const { id } = req.params;
const { status } = req.body;
// 数据验证
const validation = validateRequired(status, '状态');
if (!validation.valid) {
throw new ValidationError('数据验证失败', validation.errors);
}
// 验证状态值
const validStatuses = ['pending', 'issued', 'cancelled'];
if (!validStatuses.includes(status)) {
throw new ValidationError(`状态必须是以下之一: ${validStatuses.join(', ')}`);
}
// 检查发票是否存在
const invoice = await Invoice.findById(id);
if (!invoice) {
throw new BusinessError('发票不存在', 404);
}
const updatedInvoice = await Invoice.updateStatus(id, status);
const messages = {
pending: '发票已标记为待处理',
issued: '发票已开具',
cancelled: '发票已取消'
};
res.json(success(updatedInvoice, messages[status]));
} catch (err) {
next(err);
}
};
/**
* 获取我的发票列表
* GET /api/payments/my/invoices
*/
const getMyInvoices = async (req, res, next) => {
try {
const invoices = await Invoice.getUserInvoices(req.user.id);
res.json(success({
count: invoices.length,
invoices
}, '获取我的发票成功'));
} catch (err) {
next(err);
}
};
// 导出所有控制器函数
module.exports = {
// 支付相关
getPayments,
getPaymentById,
createPayment,
confirmPayment,
requestRefund,
completeRefund,
getPaymentStatistics,
// 发票相关
getInvoices,
getInvoiceById,
createInvoice,
updateInvoiceStatus,
getMyInvoices
};