rentease-backend-new/services/billingService.js

331 lines
9.1 KiB
JavaScript
Raw Permalink 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 { Tenant, SubscriptionPlan, Order, PricingConfig } = require('../models');
const { Op } = require('sequelize');
const { logOperation } = require('../utils/logger');
/**
* 检查并更新租户到期状态
* 每天执行一次
*/
const checkTenantExpiration = async () => {
try {
const now = new Date();
console.log(`[${now.toISOString()}] 开始检查租户到期状态...`);
// 1. 检查试用期到期的租户
const trialExpiredTenants = await Tenant.findAll({
where: {
billingStatus: 'trial_active',
trialEndDate: {
[Op.lt]: now
}
}
});
for (const tenant of trialExpiredTenants) {
await tenant.update({ billingStatus: 'trial_expired' });
console.log(`租户 ${tenant.name} (ID: ${tenant.id}) 试用期已过期`);
// 记录日志
await logOperation({
tenantId: tenant.id,
module: '计费管理',
action: '试用期到期',
description: `租户 ${tenant.name} 试用期已过期`,
status: 'success'
});
}
// 2. 检查付费期到期的租户
const paidExpiredTenants = await Tenant.findAll({
where: {
billingStatus: 'paid_active',
paidEndDate: {
[Op.lt]: now
}
}
});
for (const tenant of paidExpiredTenants) {
await tenant.update({ billingStatus: 'paid_expired' });
console.log(`租户 ${tenant.name} (ID: ${tenant.id}) 付费期已过期`);
// 记录日志
await logOperation({
tenantId: tenant.id,
module: '计费管理',
action: '付费期到期',
description: `租户 ${tenant.name} 付费期已过期`,
status: 'success'
});
}
// 3. 检查数据保留期超过90天的租户可选发送提醒或清理
const retentionLimit = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
const longExpiredTenants = await Tenant.findAll({
where: {
billingStatus: {
[Op.in]: ['trial_expired', 'paid_expired']
},
updateTime: {
[Op.lt]: retentionLimit
}
}
});
if (longExpiredTenants.length > 0) {
console.log(`发现 ${longExpiredTenants.length} 个租户超过90天数据保留期`);
// 这里可以添加数据清理逻辑或发送提醒
}
console.log(`[${new Date().toISOString()}] 租户到期检查完成`);
console.log(`- 试用期过期: ${trialExpiredTenants.length}`);
console.log(`- 付费期过期: ${paidExpiredTenants.length}`);
return {
trialExpired: trialExpiredTenants.length,
paidExpired: paidExpiredTenants.length,
longExpired: longExpiredTenants.length
};
} catch (error) {
console.error('检查租户到期状态失败:', error);
throw error;
}
};
/**
* 获取即将到期的租户(用于提醒)
* @param {number} days - 提前多少天提醒
*/
const getUpcomingExpiredTenants = async (days = 7) => {
try {
const reminderDate = new Date();
reminderDate.setDate(reminderDate.getDate() + days);
const upcomingTenants = await Tenant.findAll({
where: {
[Op.or]: [
{
billingStatus: 'trial_active',
trialEndDate: {
[Op.lte]: reminderDate,
[Op.gte]: new Date()
}
},
{
billingStatus: 'paid_active',
paidEndDate: {
[Op.lte]: reminderDate,
[Op.gte]: new Date()
}
}
]
},
include: [{
model: SubscriptionPlan,
as: 'subscriptionPlan'
}]
});
return upcomingTenants;
} catch (error) {
console.error('获取即将到期租户失败:', error);
throw error;
}
};
/**
* 计算租户的超额使用量
* @param {number} tenantId - 租户ID
*/
const calculateOverage = async (tenantId) => {
try {
const tenant = await Tenant.findByPk(tenantId, {
include: [{
model: SubscriptionPlan,
as: 'subscriptionPlan'
}]
});
if (!tenant || !tenant.subscriptionPlan) {
throw new Error('租户或套餐不存在');
}
const plan = tenant.subscriptionPlan;
// 获取当前使用量
const { Apartment, Room, User } = require('../models');
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 } })
]);
// 计算超额
const overageApartments = Math.max(0, apartmentCount - plan.maxApartments);
const overageRooms = Math.max(0, roomCount - plan.maxRooms);
const overageUsers = Math.max(0, userCount - plan.maxUsers);
return {
usage: {
apartments: apartmentCount,
rooms: roomCount,
users: userCount
},
limits: {
apartments: plan.maxApartments,
rooms: plan.maxRooms,
users: plan.maxUsers
},
overage: {
apartments: overageApartments,
rooms: overageRooms,
users: overageUsers
}
};
} catch (error) {
console.error('计算超额使用量失败:', error);
throw error;
}
};
/**
* 计算续费订单金额
* @param {number} tenantId - 租户ID
* @param {number} planId - 套餐ID
* @param {number} months - 购买月数
*/
const calculateRenewalAmount = async (tenantId, planId, months) => {
try {
// 获取套餐信息
const plan = await SubscriptionPlan.findByPk(planId);
if (!plan) {
throw new Error('套餐不存在');
}
// 获取当前价格配置
const pricingConfig = await PricingConfig.findOne({
where: { isActive: true },
order: [['effectiveDate', 'DESC']]
});
if (!pricingConfig) {
throw new Error('价格配置不存在');
}
// 计算超额使用费
const overage = await calculateOverage(tenantId);
// 基础费用 = 套餐月费 × 月数
const baseAmount = plan.monthlyPrice * months;
// 超额费用 = (超额公寓×单价 + 超额房间×单价 + 超额用户×单价) × 月数
const overageAmount = (
overage.overage.apartments * pricingConfig.overageApartmentPrice +
overage.overage.rooms * pricingConfig.overageRoomPrice +
overage.overage.users * pricingConfig.overageUserPrice
) * months;
const totalAmount = baseAmount + overageAmount;
return {
baseAmount: parseFloat(baseAmount.toFixed(2)),
overageAmount: parseFloat(overageAmount.toFixed(2)),
totalAmount: parseFloat(totalAmount.toFixed(2)),
details: {
planName: plan.name,
monthlyPrice: plan.monthlyPrice,
months,
overage
}
};
} catch (error) {
console.error('计算续费金额失败:', error);
throw error;
}
};
/**
* 处理支付成功后的租户状态更新
* @param {number} tenantId - 租户ID
* @param {number} months - 续费月数
* @param {number} planId - 套餐ID可选用于更新资源配置
*/
const processPaymentSuccess = async (tenantId, months, planId = null) => {
try {
const tenant = await Tenant.findByPk(tenantId);
if (!tenant) {
throw new Error('租户不存在');
}
const now = new Date();
let paidStartDate, paidEndDate, billingStatus;
// 如果当前是付费期,则延长
if (tenant.billingStatus === 'paid_active' && tenant.paidEndDate && tenant.paidEndDate > now) {
paidStartDate = tenant.paidStartDate;
paidEndDate = new Date(tenant.paidEndDate.getTime() + months * 30 * 24 * 60 * 60 * 1000);
billingStatus = 'paid_active';
} else {
// 新付费期或从过期状态恢复
paidStartDate = now;
paidEndDate = new Date(now.getTime() + months * 30 * 24 * 60 * 60 * 1000);
billingStatus = 'paid_active';
}
// 构建更新数据
const updateData = {
billingStatus,
paidStartDate,
paidEndDate,
currentPeriodStart: paidStartDate,
currentPeriodEnd: paidEndDate
};
// 如果有套餐ID更新租户套餐和资源配置
if (planId) {
const plan = await SubscriptionPlan.findByPk(planId);
if (plan) {
updateData.planId = planId;
updateData.maxApartments = plan.maxApartments;
updateData.maxRooms = plan.maxRooms;
updateData.maxUsers = plan.maxUsers;
}
}
await tenant.update(updateData);
console.log(`租户 ${tenant.name} (ID: ${tenantId}) 续费成功,有效期至 ${paidEndDate.toISOString()}`);
// 记录日志
await logOperation({
tenantId: tenant.id,
module: '计费管理',
action: '续费成功',
description: `租户续费 ${months} 个月,有效期至 ${paidEndDate.toLocaleDateString()}`,
status: 'success'
});
return {
billingStatus,
paidStartDate,
paidEndDate
};
} catch (error) {
console.error('处理支付成功失败:', error);
throw error;
}
};
module.exports = {
checkTenantExpiration,
getUpcomingExpiredTenants,
calculateOverage,
calculateRenewalAmount,
processPaymentSuccess
};