|
|
/**
|
|
|
* 支付控制器
|
|
|
*
|
|
|
* 功能:处理支付和发票管理的业务逻辑
|
|
|
*/
|
|
|
|
|
|
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
|
|
|
};
|