rentease-backend-new/controllers/billingController.js

913 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const { SubscriptionPlan, PricingConfig, Order, Payment, Tenant, User, Apartment, Room, PaymentSetting } = require('../models');
const { Op } = require('sequelize');
const billingService = require('../services/billingService');
const response = require('../utils/response');
// 获取所有套餐列表
const getAllPlans = async (req, res) => {
try {
const plans = await SubscriptionPlan.findAll({
where: { isDeleted: 0 },
order: [['sort', 'ASC']]
});
response.success(res, '获取套餐列表成功', plans);
} catch (error) {
response.serverError(res, '获取套餐列表失败', error);
}
};
// 创建套餐
const createPlan = async (req, res) => {
try {
const { name, description, maxApartments, maxRooms, maxUsers, monthlyPrice } = req.body;
// 验证必填字段
if (!name) {
return response.badRequest(res, '套餐名称不能为空');
}
// 检查是否是第一个套餐
const existingPlans = await SubscriptionPlan.count({ where: { isDeleted: 0 } });
const isDefault = existingPlans === 0;
// 创建套餐
const plan = await SubscriptionPlan.create({
name,
description,
maxApartments: maxApartments || 10,
maxRooms: maxRooms || 50,
maxUsers: maxUsers || 5,
monthlyPrice: monthlyPrice || 0,
isDefault
});
response.created(res, '套餐创建成功', plan);
} catch (error) {
response.serverError(res, '创建套餐失败', error);
}
};
// 更新套餐
const updatePlan = async (req, res) => {
try {
const { id } = req.params;
const { name, description, maxApartments, maxRooms, maxUsers, monthlyPrice, status, sort } = req.body;
// 查找套餐
const plan = await SubscriptionPlan.findOne({
where: { id, isDeleted: 0 }
});
if (!plan) {
return response.badRequest(res, '套餐不存在');
}
// 更新套餐信息
await plan.update({
name: name !== undefined ? name : plan.name,
description: description !== undefined ? description : plan.description,
maxApartments: maxApartments !== undefined ? maxApartments : plan.maxApartments,
maxRooms: maxRooms !== undefined ? maxRooms : plan.maxRooms,
maxUsers: maxUsers !== undefined ? maxUsers : plan.maxUsers,
monthlyPrice: monthlyPrice !== undefined ? monthlyPrice : plan.monthlyPrice,
status: status !== undefined ? status : plan.status,
sort: sort !== undefined ? sort : plan.sort
});
response.success(res, '套餐更新成功', plan);
} catch (error) {
response.serverError(res, '更新套餐失败', error);
}
};
// 删除套餐(软删除)
const deletePlan = async (req, res) => {
try {
const { id } = req.params;
// 查找套餐
const plan = await SubscriptionPlan.findOne({
where: { id, isDeleted: 0 }
});
if (!plan) {
return response.badRequest(res, '套餐不存在');
}
// 不能删除默认套餐
if (plan.isDefault) {
return response.badRequest(res, '不能删除默认套餐');
}
// 软删除
await plan.update({ isDeleted: 1 });
response.success(res, '套餐删除成功');
} catch (error) {
response.serverError(res, '删除套餐失败', error);
}
};
// 设置默认套餐
const setDefaultPlan = async (req, res) => {
try {
const { id } = req.params;
// 查找套餐
const plan = await SubscriptionPlan.findOne({
where: { id, isDeleted: 0 }
});
if (!plan) {
return response.badRequest(res, '套餐不存在');
}
// 将其他套餐 isDefault 设为 false
await SubscriptionPlan.update(
{ isDefault: false },
{ where: { isDeleted: 0 } }
);
// 将指定套餐 isDefault 设为 true
await plan.update({ isDefault: true });
response.success(res, '设置默认套餐成功', plan);
} catch (error) {
response.serverError(res, '设置默认套餐失败', error);
}
};
// 获取当前价格配置
const getPricingConfig = async (req, res) => {
try {
// 查询 isActive=true 且最新的记录
const config = await PricingConfig.findOne({
where: { isActive: true, isDeleted: 0 },
order: [['createTime', 'DESC']]
});
// 如果没有则返回默认配置
if (!config) {
return response.success(res, '获取价格配置成功', {
overageApartmentPrice: 10.00,
overageRoomPrice: 2.00,
overageUserPrice: 5.00,
currency: 'CNY'
});
}
response.success(res, '获取价格配置成功', config);
} catch (error) {
response.serverError(res, '获取价格配置失败', error);
}
};
// 更新价格配置
const updatePricingConfig = async (req, res) => {
try {
const { overageApartmentPrice, overageRoomPrice, overageUserPrice } = req.body;
// 将旧配置 isActive 设为 false
await PricingConfig.update(
{ isActive: false },
{ where: { isActive: true } }
);
// 创建新配置记录
const config = await PricingConfig.create({
overageApartmentPrice: overageApartmentPrice || 10.00,
overageRoomPrice: overageRoomPrice || 2.00,
overageUserPrice: overageUserPrice || 5.00,
isActive: true
});
response.success(res, '价格配置更新成功', config);
} catch (error) {
response.serverError(res, '更新价格配置失败', error);
}
};
// 初始化默认套餐数据
const initDefaultPlans = async (req, res) => {
try {
// 检查是否已有套餐
const existingCount = await SubscriptionPlan.count({ where: { isDeleted: 0 } });
if (existingCount > 0) {
return response.badRequest(res, '套餐数据已存在,无需初始化');
}
// 默认套餐数据
const defaultPlans = [
{
name: '免费版',
description: '适合个人用户试用',
maxApartments: 2,
maxRooms: 10,
maxUsers: 2,
monthlyPrice: 0,
isDefault: true,
sort: 1
},
{
name: '基础版',
description: '适合小型公寓管理',
maxApartments: 5,
maxRooms: 50,
maxUsers: 5,
monthlyPrice: 99,
isDefault: false,
sort: 2
},
{
name: '专业版',
description: '适合中型公寓管理',
maxApartments: 20,
maxRooms: 200,
maxUsers: 20,
monthlyPrice: 299,
isDefault: false,
sort: 3
},
{
name: '旗舰版',
description: '适合大型公寓管理',
maxApartments: 100,
maxRooms: 1000,
maxUsers: 100,
monthlyPrice: 999,
isDefault: false,
sort: 4
}
];
// 批量创建套餐
const plans = await SubscriptionPlan.bulkCreate(defaultPlans);
response.success(res, '默认套餐初始化成功', plans);
} catch (error) {
response.serverError(res, '初始化默认套餐失败', error);
}
};
// 初始化默认价格配置
const initDefaultPricing = async (req, res) => {
try {
// 检查是否已有活跃配置
const existingConfig = await PricingConfig.findOne({
where: { isActive: true, isDeleted: 0 }
});
if (existingConfig) {
return response.badRequest(res, '价格配置已存在,无需初始化');
}
// 创建默认价格配置
const config = await PricingConfig.create({
overageApartmentPrice: 10.00,
overageRoomPrice: 2.00,
overageUserPrice: 5.00,
isActive: true
});
response.success(res, '默认价格配置初始化成功', config);
} catch (error) {
response.serverError(res, '初始化默认价格配置失败', error);
}
};
// 获取订单列表
const getOrders = async (req, res) => {
try {
const { page = 1, pageSize = 10, tenantId, status } = req.query;
const offset = (page - 1) * pageSize;
const isSystemAdmin = req.user.userType === 'super_admin';
const currentTenantId = req.tenantId;
const where = { isDeleted: 0 };
// 权限检查:普通租户只能查看自己的订单
if (!isSystemAdmin) {
where.tenantId = currentTenantId;
} else if (tenantId) {
// 系统管理员可以查看指定租户的订单
where.tenantId = tenantId;
}
if (status) {
where.status = status;
}
const { count, rows } = await Order.findAndCountAll({
where,
limit: parseInt(pageSize),
offset: parseInt(offset),
order: [['createTime', 'DESC']],
include: [
{
model: Tenant,
as: 'tenant',
attributes: ['id', 'code', 'contactName']
},
{
model: SubscriptionPlan,
as: 'subscriptionPlan',
attributes: ['id', 'name', 'monthlyPrice']
}
]
});
response.success(res, '获取订单列表成功', {
list: rows,
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
});
} catch (error) {
console.error('获取订单列表失败:', error);
response.serverError(res, '获取订单列表失败', error);
}
};
// 计算续费价格(含超额费用)
const calculatePrice = async (req, res) => {
try {
const { planId, months } = req.body;
const tenantId = req.tenantId;
// 验证必填字段
if (!planId || !months) {
return response.badRequest(res, '套餐ID和购买月数不能为空');
}
if (months < 1 || months > 36) {
return response.badRequest(res, '购买月数必须在1-36之间');
}
// 获取套餐信息
const plan = await SubscriptionPlan.findOne({
where: { id: planId, isDeleted: 0 }
});
if (!plan) {
return response.badRequest(res, '套餐不存在');
}
// 计算订单金额(含超额费用)
const amountInfo = await billingService.calculateRenewalAmount(tenantId, planId, months);
response.success(res, '计算成功', amountInfo);
} catch (error) {
console.error('计算价格失败:', error);
response.serverError(res, '计算价格失败', error);
}
};
// 创建续费订单
const createOrder = async (req, res) => {
try {
const { planId, months } = req.body;
const tenantId = req.tenantId;
// 验证必填字段
if (!planId || !months) {
return response.badRequest(res, '套餐ID和购买月数不能为空');
}
if (months < 1 || months > 36) {
return response.badRequest(res, '购买月数必须在1-36之间');
}
// 获取套餐信息
const plan = await SubscriptionPlan.findOne({
where: { id: planId, isDeleted: 0 }
});
if (!plan) {
return response.badRequest(res, '套餐不存在');
}
// 计算订单金额
const amountInfo = await billingService.calculateRenewalAmount(tenantId, planId, months);
// 生成订单编号
const orderNo = `ORD${Date.now()}${Math.floor(Math.random() * 1000)}`;
// 计算过期时间30分钟后过期
const expireTime = new Date();
expireTime.setMinutes(expireTime.getMinutes() + 30);
// 创建订单
const order = await Order.create({
orderNo,
tenantId,
planId,
planName: plan.name,
months,
amount: amountInfo.totalAmount,
discountAmount: 0,
actualAmount: amountInfo.totalAmount,
status: 'pending',
expireTime,
createBy: req.user.id,
updateBy: req.user.id
});
// 重新查询订单以确保数据完整,包含关联数据
const createdOrder = await Order.findByPk(order.id, {
include: [
{
model: Tenant,
as: 'tenant',
attributes: ['id', 'code', 'contactName']
},
{
model: SubscriptionPlan,
as: 'subscriptionPlan',
attributes: ['id', 'name', 'monthlyPrice']
}
]
});
response.created(res, '订单创建成功', {
order: createdOrder.toJSON(),
details: amountInfo.details
});
} catch (error) {
console.error('创建订单失败:', error);
response.serverError(res, '创建订单失败', error);
}
};
// 获取订单详情
const getOrderDetail = async (req, res) => {
try {
const { id } = req.params;
const isSystemAdmin = req.user.userType === 'super_admin';
const currentTenantId = req.tenantId;
const order = await Order.findOne({
where: { id, isDeleted: 0 },
include: [
{
model: Tenant,
as: 'tenant',
attributes: ['id', 'code', 'contactName']
},
{
model: SubscriptionPlan,
as: 'subscriptionPlan',
attributes: ['id', 'name', 'description', 'monthlyPrice', 'maxApartments', 'maxRooms', 'maxUsers']
},
{
model: Payment,
as: 'payments',
attributes: ['id', 'amount', 'paymentMethod', 'status', 'transactionId', 'paidAt', 'createTime']
}
]
});
if (!order) {
return response.notFound(res, '订单不存在');
}
// 权限检查:普通租户只能查看自己的订单
if (!isSystemAdmin && order.tenantId !== currentTenantId) {
return response.forbidden(res, '无权查看此订单');
}
response.success(res, '获取订单详情成功', order);
} catch (error) {
console.error('获取订单详情失败:', error);
response.serverError(res, '获取订单详情失败', error);
}
};
// 取消订单
const cancelOrder = async (req, res) => {
try {
const { id } = req.params;
const isSystemAdmin = req.user.userType === 'super_admin';
const currentTenantId = req.tenantId;
const order = await Order.findOne({
where: { id, isDeleted: 0 }
});
if (!order) {
return response.notFound(res, '订单不存在');
}
// 权限检查:普通租户只能取消自己的订单
if (!isSystemAdmin && order.tenantId !== currentTenantId) {
return response.forbidden(res, '无权取消此订单');
}
// 只能取消待支付的订单
if (order.status !== 'pending') {
return response.badRequest(res, '只能取消待支付的订单');
}
await order.update({ status: 'cancelled' });
response.success(res, '订单取消成功');
} catch (error) {
console.error('取消订单失败:', error);
response.serverError(res, '取消订单失败', error);
}
};
// 支付订单(管理员确认收款)
const payOrder = async (req, res) => {
try {
const { id: orderId } = req.params;
const { paymentMethod = 'other', amount, transactionId: customTransactionId, remark } = req.body || {};
const isSystemAdmin = req.user.userType === 'super_admin';
if (!orderId) {
return response.badRequest(res, '订单ID不能为空');
}
// 只有管理员可以确认支付
if (!isSystemAdmin) {
return response.forbidden(res, '无权操作,请联系管理员');
}
// 查询订单
const order = await Order.findOne({
where: { id: orderId, isDeleted: 0 }
});
if (!order) {
return response.notFound(res, '订单不存在');
}
// 验证订单状态
if (order.status !== 'pending') {
return response.badRequest(res, '订单状态异常,无法支付');
}
// 验证金额
const paymentAmount = parseFloat(amount) || order.actualAmount;
if (paymentAmount <= 0) {
return response.badRequest(res, '支付金额必须大于0');
}
// 生成或使用自定义交易流水号
const transactionId = customTransactionId || `PAY${Date.now()}${Math.floor(Math.random() * 1000)}`;
// 创建支付记录
const payment = await Payment.create({
orderId,
tenantId: order.tenantId,
amount: paymentAmount,
paymentMethod: ['alipay', 'wechat', 'bank', 'other'].includes(paymentMethod) ? paymentMethod : 'other',
status: 'success',
transactionId,
paidAt: new Date(),
remark: remark || '管理员确认收款'
});
// 更新订单状态
const paidTime = new Date();
await order.update({
status: 'paid',
paidTime: paidTime
});
// 处理支付成功后的租户状态更新
// 使用订单的months字段和planId更新资源配置
const paymentResult = await billingService.processPaymentSuccess(order.tenantId, order.months, order.planId);
response.success(res, '支付成功', {
payment,
order: {
...order.toJSON(),
status: 'paid',
paidTime: paidTime
},
tenant: paymentResult
});
} catch (error) {
console.error('支付订单失败:', error);
response.serverError(res, '支付订单失败', error);
}
};
// 获取支付记录
const getPayments = async (req, res) => {
try {
const { page = 1, pageSize = 10, tenantId } = req.query;
const offset = (page - 1) * pageSize;
const isSystemAdmin = req.user.userType === 'super_admin';
const currentTenantId = req.tenantId;
const where = { isDeleted: 0 };
// 权限检查:普通租户只能查看自己的支付记录
if (!isSystemAdmin) {
where.tenantId = currentTenantId;
} else if (tenantId) {
// 系统管理员可以查看指定租户的支付记录
where.tenantId = tenantId;
}
const { count, rows } = await Payment.findAndCountAll({
where,
limit: parseInt(pageSize),
offset: parseInt(offset),
order: [['createTime', 'DESC']],
include: [
{
model: Tenant,
as: 'tenant',
attributes: ['id', 'code', 'contactName']
},
{
model: Order,
as: 'order',
attributes: ['id', 'orderNo', 'planId', 'planName', 'months', 'amount', 'discountAmount', 'actualAmount']
}
]
});
response.success(res, '获取支付记录成功', {
list: rows,
total: count,
page: parseInt(page),
pageSize: parseInt(pageSize)
});
} catch (error) {
console.error('获取支付记录失败:', error);
response.serverError(res, '获取支付记录失败', error);
}
};
// 获取当前租户计费信息
const getBillingInfo = async (req, res) => {
try {
const tenantId = req.tenantId;
const tenant = await Tenant.findOne({
where: { id: tenantId, isDeleted: 0 },
include: [
{
model: SubscriptionPlan,
as: 'subscriptionPlan',
attributes: ['id', 'name', 'description', 'monthlyPrice', 'maxApartments', 'maxRooms', 'maxUsers']
}
]
});
if (!tenant) {
return response.notFound(res, '租户不存在');
}
// 获取当前使用量
const [apartmentCount, roomCount, userCount] = await Promise.all([
Apartment.count({ where: { tenantId, isDeleted: 0 } }),
Room.count({ where: { tenantId, isDeleted: 0 } }),
User.count({ where: { tenantId, isDeleted: 0 } })
]);
response.success(res, '获取计费信息成功', {
tenant: {
id: tenant.id,
name: tenant.name,
code: tenant.code,
billingStatus: tenant.billingStatus,
trialStartDate: tenant.trialStartDate,
trialEndDate: tenant.trialEndDate,
paidStartDate: tenant.paidStartDate,
paidEndDate: tenant.paidEndDate,
currentPeriodStart: tenant.currentPeriodStart,
currentPeriodEnd: tenant.currentPeriodEnd
},
plan: tenant.subscriptionPlan,
usage: {
apartments: apartmentCount,
rooms: roomCount,
users: userCount
}
});
} catch (error) {
console.error('获取计费信息失败:', error);
response.serverError(res, '获取计费信息失败', error);
}
};
// 获取资源使用情况
const getUsageStats = async (req, res) => {
try {
const tenantId = req.tenantId;
const usageStats = await billingService.calculateOverage(tenantId);
response.success(res, '获取资源使用情况成功', usageStats);
} catch (error) {
console.error('获取资源使用情况失败:', error);
response.serverError(res, '获取资源使用情况失败', error);
}
};
// 获取计费统计(仅系统管理员)
const getBillingStats = async (req, res) => {
try {
const isSystemAdmin = req.user.userType === 'super_admin';
if (!isSystemAdmin) {
return response.forbidden(res, '无权访问此接口');
}
// 各计费状态租户数量
const billingStatusStats = await Tenant.findAll({
where: { isDeleted: 0 },
attributes: ['billingStatus', [require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count']],
group: ['billingStatus']
});
// 总收入
const totalRevenue = await Payment.sum('amount', {
where: { status: 'success', isDeleted: 0 }
});
// 本月收入
const now = new Date();
const monthStart = new Date(now.getFullYear(), now.getMonth(), 1);
const monthRevenue = await Payment.sum('amount', {
where: {
status: 'success',
isDeleted: 0,
paidAt: {
[Op.gte]: monthStart
}
}
});
// 即将到期租户数7天内
const reminderDate = new Date();
reminderDate.setDate(reminderDate.getDate() + 7);
const upcomingExpiredCount = await Tenant.count({
where: {
isDeleted: 0,
[Op.or]: [
{
billingStatus: 'trial_active',
trialEndDate: {
[Op.lte]: reminderDate,
[Op.gte]: now
}
},
{
billingStatus: 'paid_active',
paidEndDate: {
[Op.lte]: reminderDate,
[Op.gte]: now
}
}
]
}
});
// 已过期租户数
const expiredCount = await Tenant.count({
where: {
isDeleted: 0,
billingStatus: {
[Op.in]: ['trial_expired', 'paid_expired']
}
}
});
// 待处理订单数
const pendingOrdersCount = await Order.count({
where: {
status: 'pending',
isDeleted: 0
}
});
response.success(res, '获取计费统计成功', {
billingStatusStats: billingStatusStats.reduce((acc, item) => {
acc[item.billingStatus] = parseInt(item.get('count'));
return acc;
}, {}),
totalRevenue: totalRevenue || 0,
monthRevenue: monthRevenue || 0,
upcomingExpiredCount,
expiredCount,
pendingOrdersCount
});
} catch (error) {
console.error('获取计费统计失败:', error);
response.serverError(res, '获取计费统计失败', error);
}
};
// 获取支付设置
const getPaymentSettings = async (req, res) => {
try {
// 查询第一条记录(系统中只有一条支付设置记录)
let settings = await PaymentSetting.findOne({
where: { isDeleted: 0 }
});
// 如果没有记录,返回空对象
if (!settings) {
return response.success(res, '获取支付设置成功', {});
}
response.success(res, '获取支付设置成功', settings);
} catch (error) {
console.error('获取支付设置失败:', error);
response.serverError(res, '获取支付设置失败', error);
}
};
// 更新支付设置
const updatePaymentSettings = async (req, res) => {
try {
const isSystemAdmin = req.user.userType === 'super_admin';
// 只有超级管理员可以修改支付设置
if (!isSystemAdmin) {
return response.forbidden(res, '无权操作,请联系管理员');
}
const {
alipayAccount,
alipayName,
wechatId,
wechatText,
bankName,
bankAccount,
bankHolder,
servicePhone,
serviceTime
} = req.body;
// 查询是否存在记录
let settings = await PaymentSetting.findOne({
where: { isDeleted: 0 }
});
if (settings) {
// 更新现有记录
await settings.update({
alipayAccount: alipayAccount !== undefined ? alipayAccount : settings.alipayAccount,
alipayName: alipayName !== undefined ? alipayName : settings.alipayName,
wechatId: wechatId !== undefined ? wechatId : settings.wechatId,
wechatText: wechatText !== undefined ? wechatText : settings.wechatText,
bankName: bankName !== undefined ? bankName : settings.bankName,
bankAccount: bankAccount !== undefined ? bankAccount : settings.bankAccount,
bankHolder: bankHolder !== undefined ? bankHolder : settings.bankHolder,
servicePhone: servicePhone !== undefined ? servicePhone : settings.servicePhone,
serviceTime: serviceTime !== undefined ? serviceTime : settings.serviceTime
});
} else {
// 创建新记录
settings = await PaymentSetting.create({
alipayAccount,
alipayName,
wechatId,
wechatText,
bankName,
bankAccount,
bankHolder,
servicePhone,
serviceTime
});
}
response.success(res, '支付设置更新成功', settings);
} catch (error) {
console.error('更新支付设置失败:', error);
response.serverError(res, '更新支付设置失败', error);
}
};
module.exports = {
getAllPlans,
createPlan,
updatePlan,
deletePlan,
setDefaultPlan,
getPricingConfig,
updatePricingConfig,
initDefaultPlans,
initDefaultPricing,
getOrders,
createOrder,
calculatePrice,
getOrderDetail,
cancelOrder,
payOrder,
getPayments,
getBillingInfo,
getUsageStats,
getBillingStats,
getPaymentSettings,
updatePaymentSettings
};