REDROOM
PHP 8.2.30
Path:
Logout
Edit File
Size: 12.78 KB
Close
/home/byroehnu/easepay.easetack.com/controllers/subscriptionController.js
Text
Base64
// controllers/subscriptionController.js - Enhanced Subscription Management with Flutterwave const { prisma } = require('../config/prisma'); const flutterwaveService = require('../services/flutterwaveService'); /** * Enhanced Subscription Controller * Handles Flutterwave integration and subscription lifecycle */ /** * Get all available plans */ const getPlans = async (req, res) => { try { const plans = await prisma.plan.findMany({ where: { isActive: true }, orderBy: { sortOrder: 'asc' } }); res.json({ success: true, plans: plans.map(plan => ({ id: plan.id, name: plan.name, description: plan.description, priceMonthly: plan.priceMonthly, priceYearly: plan.priceYearly, currency: plan.currency, features: { maxInvoices: plan.maxInvoices === -1 ? 'Unlimited' : plan.maxInvoices, maxTeamMembers: plan.maxTeamMembers === -1 ? 'Unlimited' : plan.maxTeamMembers, maxReportExports: plan.maxReportExports === -1 ? 'Unlimited' : plan.maxReportExports, hasAdvancedReports: plan.hasAdvancedReports, hasAPIAccess: plan.hasAPIAccess, hasPrioritySupport: plan.hasPrioritySupport } })) }); } catch (error) { console.error('❌ Get plans error:', error); res.status(500).json({ success: false, message: 'Failed to fetch plans', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; /** * Get current user's subscription details */ const getMySubscription = async (req, res) => { try { const userId = req.user.id; const user = await prisma.user.findUnique({ where: { id: userId }, include: { subscription: true, usage: true, limits: true } }); if (!user) { return res.status(404).json({ success: false, message: 'User not found' }); } // Get current plan details let currentPlan = null; if (user.subscription?.planName && user.subscription.planName !== 'Free') { currentPlan = await prisma.plan.findUnique({ where: { name: user.subscription.planName } }); } res.json({ success: true, subscription: { status: user.subscription?.status || 'INACTIVE', planName: user.subscription?.planName || 'Free', nextBillingDate: user.subscription?.nextBillingDate, gracePeriodEnd: user.subscription?.gracePeriodEnd, amount: user.subscription?.amount, currency: user.subscription?.currency || 'NGN', interval: user.subscription?.interval }, usage: { invoiceCount: user.usage?.invoiceCount || 0, teamMemberCount: user.usage?.teamMemberCount || 1, reportExports: user.usage?.reportExports || 0 }, limits: { maxInvoices: user.limits?.maxInvoices || 5, maxTeamMembers: user.limits?.maxTeamMembers || 1, maxReportExports: user.limits?.maxReportExports || 2, hasAdvancedReports: user.limits?.hasAdvancedReports || false, hasAPIAccess: user.limits?.hasAPIAccess || false }, currentPlan }); } catch (error) { console.error('❌ Get subscription error:', error); res.status(500).json({ success: false, message: 'Failed to fetch subscription', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; /** * Initiate subscription process with Flutterwave */ const subscribe = async (req, res) => { try { const userId = req.user.id; const { planId, interval = 'monthly', callbackUrl, redirectUrl } = req.body; // Validate input if (!planId || !['monthly', 'yearly'].includes(interval)) { return res.status(400).json({ success: false, message: 'Invalid plan or interval specified' }); } // Get plan details const plan = await prisma.plan.findUnique({ where: { id: planId } }); if (!plan || !plan.isActive) { return res.status(404).json({ success: false, message: 'Plan not found or inactive' }); } // Get user details const user = await prisma.user.findUnique({ where: { id: userId } }); if (!user) { return res.status(404).json({ success: false, message: 'User not found' }); } // Don't allow subscription to Free plan if (plan.name === 'Free') { return res.status(400).json({ success: false, message: 'Cannot subscribe to free plan' }); } // Get the appropriate Flutterwave plan ID const flwPlanId = interval === 'monthly' ? plan.flwPlanIdMonthly : plan.flwPlanIdYearly; const amount = interval === 'monthly' ? plan.priceMonthly : plan.priceYearly; if (!flwPlanId) { return res.status(400).json({ success: false, message: `${interval} subscription not available for this plan` }); } // Create or get Flutterwave customer let flwCustomerId = user.subscription?.flwCustomerId; if (!flwCustomerId) { try { const customer = await flutterwaveService.createCustomer({ email: user.email, phone: user.phone, name: `${user.firstName} ${user.lastName}` }); flwCustomerId = customer.id; } catch (error) { console.log('⚠️ Customer creation failed, proceeding without customer ID'); } } // Generate unique transaction reference const tx_ref = `easepay_sub_${userId}_${Date.now()}`; // Generate payment link const paymentData = await flutterwaveService.generatePaymentLink({ planId: flwPlanId, tx_ref, customer: { email: user.email, phone: user.phone, name: `${user.firstName} ${user.lastName}`, amount: amount, planName: plan.name }, redirect_url: redirectUrl || `${process.env.FRONTEND_URL}/subscription/success`, callback_url: callbackUrl || `${process.env.BASE_URL}/api/webhook/flutterwave` }); // Store subscription initiation data await prisma.userSubscription.upsert({ where: { userId }, update: { flwCustomerId, status: 'INACTIVE' // Will be updated by webhook }, create: { userId, planName: plan.name, flwCustomerId, status: 'INACTIVE', amount, currency: plan.currency, interval } }); res.json({ success: true, message: 'Payment link generated successfully', paymentLink: paymentData.link, tx_ref, planName: plan.name, amount, interval }); } catch (error) { console.error('❌ Subscribe error:', error); res.status(500).json({ success: false, message: 'Failed to initiate subscription', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; /** * Cancel current subscription */ const cancelSubscription = async (req, res) => { try { const userId = req.user.id; const user = await prisma.user.findUnique({ where: { id: userId }, include: { subscription: true } }); if (!user?.subscription) { return res.status(404).json({ success: false, message: 'No active subscription found' }); } const subscription = user.subscription; if (subscription.status === 'CANCELLED' || subscription.status === 'INACTIVE') { return res.status(400).json({ success: false, message: 'Subscription is already cancelled or inactive' }); } // Cancel in Flutterwave if subscription ID exists if (subscription.flwSubscriptionId) { try { await flutterwaveService.cancelSubscription(subscription.flwSubscriptionId); console.log('✅ Flutterwave subscription cancelled'); } catch (error) { console.error('⚠️ Failed to cancel Flutterwave subscription:', error); // Continue with local cancellation even if Flutterwave fails } } // Update subscription status await prisma.userSubscription.update({ where: { userId }, data: { status: 'CANCELLED', planId: null } }); // Set user limits to Free tier await prisma.userLimits.upsert({ where: { userId }, update: { maxInvoices: 5, maxTeamMembers: 1, maxReportExports: 2, hasAdvancedReports: false, hasAPIAccess: false }, create: { userId, maxInvoices: 5, maxTeamMembers: 1, maxReportExports: 2, hasAdvancedReports: false, hasAPIAccess: false } }); res.json({ success: true, message: `Subscription cancelled. You have access until ${subscription.nextBillingDate?.toDateString() || 'now'}`, accessUntil: subscription.nextBillingDate }); } catch (error) { console.error('❌ Cancel subscription error:', error); res.status(500).json({ success: false, message: 'Failed to cancel subscription', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; /** * Get admin financial summary */ const getAdminStats = async (req, res) => { try { // Check if user is admin if (req.user.role !== 'ADMIN') { return res.status(403).json({ success: false, message: 'Access denied. Admin role required.' }); } // Get subscription statistics const stats = await prisma.userSubscription.groupBy({ by: ['planName', 'status'], _count: { id: true }, _sum: { amount: true } }); // Calculate MRR and other metrics let totalMRR = 0; let totalActiveUsers = 0; let pastDueUsers = 0; const planBreakdown = {}; for (const stat of stats) { if (stat.status === 'ACTIVE') { totalActiveUsers += stat._count.id; const monthlyAmount = stat._sum.amount || 0; totalMRR += parseFloat(monthlyAmount); planBreakdown[stat.planName] = { users: stat._count.id, revenue: parseFloat(monthlyAmount) }; } else if (stat.status === 'PAST_DUE') { pastDueUsers += stat._count.id; } } // Get total users const totalUsers = await prisma.user.count(); // Get recent subscriptions (last 30 days) const recentSubscriptions = await prisma.userSubscription.count({ where: { createdAt: { gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) }, status: 'ACTIVE' } }); res.json({ success: true, stats: { totalUsers, totalActiveUsers, pastDueUsers, freeUsers: totalUsers - totalActiveUsers - pastDueUsers, totalMRR: totalMRR.toFixed(2), currency: 'NGN', recentSubscriptions, planBreakdown, conversionRate: totalUsers > 0 ? ((totalActiveUsers / totalUsers) * 100).toFixed(2) : 0, churnRisk: pastDueUsers } }); } catch (error) { console.error('❌ Admin stats error:', error); res.status(500).json({ success: false, message: 'Failed to fetch admin statistics', error: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }; /** * Update user's plan limits (Internal use - called by webhook) */ const updateUserLimits = async (userId, planName) => { try { const plan = await prisma.plan.findUnique({ where: { name: planName } }); if (!plan) { console.error(`❌ Plan not found: ${planName}`); return; } await prisma.userLimits.upsert({ where: { userId }, update: { maxInvoices: plan.maxInvoices, maxTeamMembers: plan.maxTeamMembers, maxReportExports: plan.maxReportExports, hasAdvancedReports: plan.hasAdvancedReports, hasAPIAccess: plan.hasAPIAccess }, create: { userId, maxInvoices: plan.maxInvoices, maxTeamMembers: plan.maxTeamMembers, maxReportExports: plan.maxReportExports, hasAdvancedReports: plan.hasAdvancedReports, hasAPIAccess: plan.hasAPIAccess } }); console.log(`✅ User limits updated for ${userId} - Plan: ${planName}`); } catch (error) { console.error('❌ Update user limits error:', error); } }; module.exports = { getPlans, getMySubscription, subscribe, cancelSubscription, getAdminStats, updateUserLimits };
Save
Close
Exit & Reset
Text mode: syntax highlighting auto-detects file type.
Directory Contents
Dirs: 1 × Files: 17
Delete Selected
Select All
Select None
Sort:
Name
Size
Modified
Enable drag-to-move
Name
Size
Perms
Modified
Actions
controllers
DIR
-
drwxr-xr-x
2026-03-20 23:25:48
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
.htaccess
127 B
lr--r--r--
2026-03-14 01:49:22
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
advancedAuthController.js
15.34 KB
lrw-r--r--
2026-02-19 22:06:47
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
authController.js
30.77 KB
lrw-r--r--
2026-03-04 06:33:18
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
authController_copy.js
62.98 KB
lrw-r--r--
2026-03-01 23:46:52
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
businessController.js
10.79 KB
lrw-r--r--
2026-02-07 16:28:16
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
businessProfileController.js
14.17 KB
lrw-r--r--
2026-02-27 04:02:38
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
creditScoreController.js
6.32 KB
lrw-r--r--
2026-02-06 17:10:19
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
dashboardController.js
15.13 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
expenseController.js
8.50 KB
lrw-r--r--
2026-03-01 05:05:52
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
inventoryController.js
12.15 KB
lrw-r--r--
2026-02-27 04:02:38
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
invoiceController.js
25.39 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
reportController.js
17.61 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
saleController.js
6.45 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
settingsController.js
2.33 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
subscriptionController.js
12.78 KB
lrw-r--r--
2026-02-21 08:13:30
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
taxController.js
9.96 KB
lrw-r--r--
2026-03-01 05:05:51
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
webhookController.js
8.51 KB
lrw-r--r--
2026-02-20 19:31:28
Edit
Download
Rename
Chmod
Change Date
Delete
OK
Cancel
recursive
OK
Cancel
recursive
OK
Cancel
Zip Selected
If ZipArchive is unavailable, a
.tar
will be created (no compression).