254 lines
6.6 KiB
JavaScript
254 lines
6.6 KiB
JavaScript
const { SubscriptionPlan, UserSubscription, UsersAuth } = require('../models');
|
|
const { cacheUtils } = require('../config/redis');
|
|
const { addDatabaseWriteJob } = require('../config/bullmq');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
/**
|
|
* Subscription Controller - Quản lý gói thẻ tháng
|
|
*/
|
|
class SubscriptionController {
|
|
/**
|
|
* GET /api/subscriptions/plans - Danh sách các gói subscription
|
|
*/
|
|
async getPlans(req, res, next) {
|
|
try {
|
|
const { target_role } = req.query;
|
|
const cacheKey = `subscription:plans:${target_role || 'all'}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const where = { is_active: true };
|
|
if (target_role) where.target_role = target_role;
|
|
|
|
const plans = await SubscriptionPlan.findAll({
|
|
where,
|
|
order: [['price', 'ASC']],
|
|
});
|
|
|
|
await cacheUtils.set(cacheKey, plans, 1800); // Cache 30 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: plans,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/subscriptions/purchase - Mua gói subscription
|
|
*/
|
|
async purchaseSubscription(req, res, next) {
|
|
try {
|
|
const { user_id, plan_id, payment_method, transaction_id } = req.body;
|
|
|
|
if (!user_id || !plan_id || !payment_method || !transaction_id) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Missing required fields: user_id, plan_id, payment_method, transaction_id',
|
|
});
|
|
}
|
|
|
|
// Kiểm tra plan có tồn tại không
|
|
const plan = await SubscriptionPlan.findByPk(plan_id);
|
|
if (!plan || !plan.is_active) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Subscription plan not found or inactive',
|
|
});
|
|
}
|
|
|
|
// Kiểm tra user có subscription đang active không
|
|
const existingSubscription = await UserSubscription.findOne({
|
|
where: {
|
|
user_id,
|
|
status: 'active',
|
|
},
|
|
});
|
|
|
|
if (existingSubscription) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'User already has an active subscription',
|
|
});
|
|
}
|
|
|
|
const start_date = new Date();
|
|
const end_date = new Date(start_date);
|
|
end_date.setDate(end_date.getDate() + plan.duration_days);
|
|
|
|
const subscriptionData = {
|
|
id: uuidv4(),
|
|
user_id,
|
|
plan_id,
|
|
start_date,
|
|
end_date,
|
|
status: 'active',
|
|
transaction_id,
|
|
payment_method,
|
|
payment_amount: plan.price,
|
|
auto_renew: req.body.auto_renew || false,
|
|
};
|
|
|
|
// Async write to DB via BullMQ
|
|
await addDatabaseWriteJob('create', 'UserSubscription', subscriptionData);
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`subscription:user:${user_id}`);
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Subscription purchase request queued',
|
|
data: subscriptionData,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/subscriptions/user/:user_id - Kiểm tra subscription status của user
|
|
*/
|
|
async getUserSubscription(req, res, next) {
|
|
try {
|
|
const { user_id } = req.params;
|
|
const cacheKey = `subscription:user:${user_id}`;
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const subscription = await UserSubscription.findOne({
|
|
where: { user_id },
|
|
include: [
|
|
{
|
|
model: SubscriptionPlan,
|
|
as: 'plan',
|
|
attributes: ['name', 'price', 'features', 'duration_days'],
|
|
},
|
|
],
|
|
order: [['created_at', 'DESC']],
|
|
});
|
|
|
|
if (!subscription) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'No subscription found for this user',
|
|
});
|
|
}
|
|
|
|
// Check nếu subscription đã hết hạn
|
|
const now = new Date();
|
|
if (subscription.status === 'active' && new Date(subscription.end_date) < now) {
|
|
subscription.status = 'expired';
|
|
await addDatabaseWriteJob('update', 'UserSubscription', {
|
|
id: subscription.id,
|
|
status: 'expired',
|
|
});
|
|
}
|
|
|
|
await cacheUtils.set(cacheKey, subscription, 900); // Cache 15 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: subscription,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* POST /api/subscriptions/:id/cancel - Hủy subscription
|
|
*/
|
|
async cancelSubscription(req, res, next) {
|
|
try {
|
|
const { id } = req.params;
|
|
const { reason } = req.body;
|
|
|
|
const subscription = await UserSubscription.findByPk(id);
|
|
|
|
if (!subscription) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Subscription not found',
|
|
});
|
|
}
|
|
|
|
if (subscription.status === 'cancelled') {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Subscription already cancelled',
|
|
});
|
|
}
|
|
|
|
// Async update via BullMQ
|
|
await addDatabaseWriteJob('update', 'UserSubscription', {
|
|
id,
|
|
status: 'cancelled',
|
|
auto_renew: false,
|
|
});
|
|
|
|
// Clear cache
|
|
await cacheUtils.del(`subscription:user:${subscription.user_id}`);
|
|
|
|
res.status(202).json({
|
|
success: true,
|
|
message: 'Subscription cancellation request queued',
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* GET /api/subscriptions/stats - Thống kê subscription
|
|
*/
|
|
async getSubscriptionStats(req, res, next) {
|
|
try {
|
|
const cacheKey = 'subscription:stats';
|
|
|
|
const cached = await cacheUtils.get(cacheKey);
|
|
if (cached) {
|
|
return res.json({
|
|
success: true,
|
|
data: cached,
|
|
cached: true,
|
|
});
|
|
}
|
|
|
|
const stats = await UserSubscription.findAll({
|
|
attributes: [
|
|
'status',
|
|
[require('sequelize').fn('COUNT', require('sequelize').col('id')), 'count'],
|
|
],
|
|
group: ['status'],
|
|
});
|
|
|
|
await cacheUtils.set(cacheKey, stats, 300); // Cache 5 min
|
|
|
|
res.json({
|
|
success: true,
|
|
data: stats,
|
|
});
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new SubscriptionController();
|