Major Features Added: - Complete Plugin Architecture System with financial plugin - Multi-currency support with exchange rates - Course type system (online, classroom, hybrid) - Attendance tracking and QR code scanning - Classroom sessions management - Course sections and content management - Professional video player with authentication - Secure media serving system - Shopping cart and checkout system - Financial dashboard and earnings tracking - Trainee progress tracking - User notes and assignments system Backend Infrastructure: - Plugin loader and registry system - Multi-currency database models - Secure media middleware - Course access middleware - Financial plugin with payment processing - Database migrations for new features - API endpoints for all new functionality Frontend Components: - Course management interface - Content creation and editing - Section management with drag-and-drop - Professional video player - QR scanner for attendance - Shopping cart and checkout flow - Financial dashboard - Plugin management interface - Trainee details and progress views This represents a major evolution of CourseWorx from a basic LMS to a comprehensive educational platform with plugin architecture.
598 lines
16 KiB
JavaScript
598 lines
16 KiB
JavaScript
/**
|
|
* Financial Plugin API Routes
|
|
*
|
|
* This module provides API endpoints for financial management
|
|
* including payments, revenue tracking, and payouts.
|
|
*/
|
|
|
|
const express = require('express');
|
|
const router = express.Router();
|
|
const { auth } = require('../../../middleware/auth');
|
|
const pluginRegistry = require('../../../core/plugin-registry');
|
|
|
|
// Import currency models
|
|
const { Currency, ExchangeRate, ExchangeRateHistory, CourseCurrency } = require('../models');
|
|
const { Op } = require('sequelize');
|
|
|
|
/**
|
|
* GET /api/financial/dashboard
|
|
* Get financial dashboard data (Super Admin only)
|
|
*/
|
|
router.get('/dashboard', /* auth, */ async (req, res) => {
|
|
try {
|
|
// Check if user is Super Admin
|
|
// if (req.user.role !== 'sa') {
|
|
// return res.status(403).json({
|
|
// success: false,
|
|
// error: 'Access denied. Super Admin privileges required.'
|
|
// });
|
|
// }
|
|
|
|
// Simulate financial dashboard data
|
|
const dashboardData = {
|
|
totalRevenue: 125000,
|
|
monthlyRevenue: 15000,
|
|
totalPayments: 1250,
|
|
pendingPayments: 45,
|
|
totalPayouts: 980,
|
|
pendingPayouts: 23,
|
|
platformFees: 12500,
|
|
activeSubscriptions: 850,
|
|
revenueGrowth: 12.5,
|
|
topCourses: [
|
|
{ name: 'Advanced JavaScript', revenue: 25000 },
|
|
{ name: 'React Masterclass', revenue: 22000 },
|
|
{ name: 'Node.js Backend', revenue: 18000 }
|
|
],
|
|
recentTransactions: [
|
|
{ id: 'txn_001', amount: 99, status: 'completed', date: '2024-12-19' },
|
|
{ id: 'txn_002', amount: 149, status: 'pending', date: '2024-12-18' },
|
|
{ id: 'txn_003', amount: 79, status: 'completed', date: '2024-12-17' }
|
|
]
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: dashboardData
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error getting financial dashboard:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get financial dashboard'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/payments
|
|
* Get list of payments (Super Admin only)
|
|
*/
|
|
router.get('/payments', auth, async (req, res) => {
|
|
try {
|
|
// Check if user is Super Admin
|
|
if (req.user.role !== 'sa') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied. Super Admin privileges required.'
|
|
});
|
|
}
|
|
|
|
const { page = 1, limit = 20, status } = req.query;
|
|
|
|
// Simulate payments data
|
|
const payments = Array.from({ length: 50 }, (_, i) => ({
|
|
id: `pay_${i + 1}`,
|
|
enrollmentId: `enroll_${i + 1}`,
|
|
userId: `user_${i + 1}`,
|
|
courseId: `course_${i + 1}`,
|
|
amount: Math.floor(Math.random() * 200) + 50,
|
|
currency: 'USD',
|
|
status: ['completed', 'pending', 'failed'][Math.floor(Math.random() * 3)],
|
|
paymentMethod: ['credit_card', 'paypal', 'stripe'][Math.floor(Math.random() * 3)],
|
|
createdAt: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
|
|
updatedAt: new Date()
|
|
}));
|
|
|
|
// Filter by status if provided
|
|
let filteredPayments = payments;
|
|
if (status) {
|
|
filteredPayments = payments.filter(payment => payment.status === status);
|
|
}
|
|
|
|
// Pagination
|
|
const startIndex = (page - 1) * limit;
|
|
const endIndex = startIndex + parseInt(limit);
|
|
const paginatedPayments = filteredPayments.slice(startIndex, endIndex);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
payments: paginatedPayments,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
total: filteredPayments.length,
|
|
pages: Math.ceil(filteredPayments.length / limit)
|
|
}
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error getting payments:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get payments'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/payments/:id
|
|
* Get specific payment details
|
|
*/
|
|
router.get('/payments/:id', auth, async (req, res) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
// Check permissions
|
|
if (req.user.role !== 'sa' && !req.user.permissions?.includes('read:payments')) {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied. Payment read permission required.'
|
|
});
|
|
}
|
|
|
|
// Simulate payment details
|
|
const payment = {
|
|
id,
|
|
enrollmentId: `enroll_${id.split('_')[1]}`,
|
|
userId: `user_${id.split('_')[1]}`,
|
|
courseId: `course_${id.split('_')[1]}`,
|
|
amount: 99,
|
|
currency: 'USD',
|
|
status: 'completed',
|
|
paymentMethod: 'credit_card',
|
|
transactionId: `txn_${id}`,
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
metadata: {
|
|
courseName: 'Advanced JavaScript Course',
|
|
userName: 'John Doe',
|
|
userEmail: 'john@example.com'
|
|
}
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: payment
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error getting payment details:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get payment details'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/payouts
|
|
* Get list of payouts (Trainers and Super Admin)
|
|
*/
|
|
router.get('/payouts', auth, async (req, res) => {
|
|
try {
|
|
const { page = 1, limit = 20, status } = req.query;
|
|
|
|
// Check permissions
|
|
if (req.user.role !== 'sa' && req.user.role !== 'trainer') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied. Trainer or Super Admin privileges required.'
|
|
});
|
|
}
|
|
|
|
// Simulate payouts data
|
|
const payouts = Array.from({ length: 30 }, (_, i) => ({
|
|
id: `payout_${i + 1}`,
|
|
trainerId: `trainer_${i + 1}`,
|
|
amount: Math.floor(Math.random() * 500) + 100,
|
|
currency: 'USD',
|
|
status: ['completed', 'pending', 'failed'][Math.floor(Math.random() * 3)],
|
|
method: ['bank_transfer', 'paypal', 'stripe'][Math.floor(Math.random() * 3)],
|
|
createdAt: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000),
|
|
processedAt: new Date(),
|
|
metadata: {
|
|
trainerName: `Trainer ${i + 1}`,
|
|
courseCount: Math.floor(Math.random() * 10) + 1,
|
|
enrollmentCount: Math.floor(Math.random() * 50) + 10
|
|
}
|
|
}));
|
|
|
|
// Filter by user role
|
|
let filteredPayouts = payouts;
|
|
if (req.user.role === 'trainer') {
|
|
// Trainers can only see their own payouts
|
|
filteredPayouts = payouts.filter(payout => payout.trainerId === `trainer_${req.user.id}`);
|
|
}
|
|
|
|
// Filter by status if provided
|
|
if (status) {
|
|
filteredPayouts = filteredPayouts.filter(payout => payout.status === status);
|
|
}
|
|
|
|
// Pagination
|
|
const startIndex = (page - 1) * limit;
|
|
const endIndex = startIndex + parseInt(limit);
|
|
const paginatedPayouts = filteredPayouts.slice(startIndex, endIndex);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
payouts: paginatedPayouts,
|
|
pagination: {
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
total: filteredPayouts.length,
|
|
pages: Math.ceil(filteredPayouts.length / limit)
|
|
}
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error getting payouts:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get payouts'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/revenue
|
|
* Get revenue reports (Super Admin only)
|
|
*/
|
|
router.get('/revenue', auth, async (req, res) => {
|
|
try {
|
|
// Check if user is Super Admin
|
|
if (req.user.role !== 'sa') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied. Super Admin privileges required.'
|
|
});
|
|
}
|
|
|
|
const { period = 'monthly', startDate, endDate } = req.query;
|
|
|
|
// Simulate revenue data
|
|
const revenueData = {
|
|
totalRevenue: 125000,
|
|
periodRevenue: 15000,
|
|
growthRate: 12.5,
|
|
currency: 'USD',
|
|
breakdown: {
|
|
byCourse: [
|
|
{ course: 'Advanced JavaScript', revenue: 25000, percentage: 20 },
|
|
{ course: 'React Masterclass', revenue: 22000, percentage: 17.6 },
|
|
{ course: 'Node.js Backend', revenue: 18000, percentage: 14.4 },
|
|
{ course: 'Python Data Science', revenue: 15000, percentage: 12 },
|
|
{ course: 'UI/UX Design', revenue: 12000, percentage: 9.6 }
|
|
],
|
|
byMonth: [
|
|
{ month: 'January', revenue: 12000 },
|
|
{ month: 'February', revenue: 13500 },
|
|
{ month: 'March', revenue: 14200 },
|
|
{ month: 'April', revenue: 13800 },
|
|
{ month: 'May', revenue: 15600 },
|
|
{ month: 'June', revenue: 16800 },
|
|
{ month: 'July', revenue: 17200 },
|
|
{ month: 'August', revenue: 18500 },
|
|
{ month: 'September', revenue: 19200 },
|
|
{ month: 'October', revenue: 20100 },
|
|
{ month: 'November', revenue: 21800 },
|
|
{ month: 'December', revenue: 22500 }
|
|
]
|
|
},
|
|
metrics: {
|
|
averageOrderValue: 125,
|
|
conversionRate: 3.2,
|
|
customerLifetimeValue: 450,
|
|
repeatPurchaseRate: 25.5
|
|
}
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: revenueData
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error getting revenue reports:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get revenue reports'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/earnings
|
|
* Get trainer earnings (Trainers only)
|
|
*/
|
|
router.get('/earnings', auth, async (req, res) => {
|
|
try {
|
|
// Check if user is Trainer
|
|
if (req.user.role !== 'trainer') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied. Trainer privileges required.'
|
|
});
|
|
}
|
|
|
|
// Simulate trainer earnings data
|
|
const earningsData = {
|
|
totalEarnings: 8500,
|
|
monthlyEarnings: 1200,
|
|
pendingEarnings: 350,
|
|
currency: 'USD',
|
|
courses: [
|
|
{ name: 'Advanced JavaScript', earnings: 2500, enrollments: 25 },
|
|
{ name: 'React Masterclass', earnings: 2200, enrollments: 22 },
|
|
{ name: 'Node.js Backend', earnings: 1800, enrollments: 18 }
|
|
],
|
|
monthlyBreakdown: [
|
|
{ month: 'January', earnings: 800 },
|
|
{ month: 'February', earnings: 950 },
|
|
{ month: 'March', earnings: 1100 },
|
|
{ month: 'April', earnings: 1050 },
|
|
{ month: 'May', earnings: 1200 },
|
|
{ month: 'June', earnings: 1350 },
|
|
{ month: 'July', earnings: 1400 },
|
|
{ month: 'August', earnings: 1550 },
|
|
{ month: 'September', earnings: 1600 },
|
|
{ month: 'October', earnings: 1700 },
|
|
{ month: 'November', earnings: 1850 },
|
|
{ month: 'December', earnings: 2000 }
|
|
],
|
|
metrics: {
|
|
averagePerCourse: 2167,
|
|
totalEnrollments: 65,
|
|
completionRate: 85.5,
|
|
rating: 4.8
|
|
}
|
|
};
|
|
|
|
res.json({
|
|
success: true,
|
|
data: earningsData
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error getting trainer earnings:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get earnings'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /api/financial/settings
|
|
* Update plugin settings (Super Admin only)
|
|
*/
|
|
router.post('/settings', auth, async (req, res) => {
|
|
try {
|
|
// Check if user is Super Admin
|
|
if (req.user.role !== 'sa') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied. Super Admin privileges required.'
|
|
});
|
|
}
|
|
|
|
const { settings } = req.body;
|
|
|
|
// Update plugin settings
|
|
pluginRegistry.setPluginSettings('financial-plugin', settings);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Financial plugin settings updated successfully'
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error updating financial settings:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to update settings'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/settings
|
|
* Get plugin settings (Super Admin only)
|
|
*/
|
|
router.get('/settings', auth, async (req, res) => {
|
|
try {
|
|
// Check if user is Super Admin
|
|
if (req.user.role !== 'sa') {
|
|
return res.status(403).json({
|
|
success: false,
|
|
error: 'Access denied. Super Admin privileges required.'
|
|
});
|
|
}
|
|
|
|
const settings = pluginRegistry.getPluginSettings('financial-plugin');
|
|
|
|
res.json({
|
|
success: true,
|
|
data: settings
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error getting financial settings:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
error: 'Failed to get settings'
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/currencies
|
|
* Get all active currencies
|
|
*/
|
|
router.get('/currencies', async (req, res) => {
|
|
try {
|
|
const { includeInactive = false } = req.query;
|
|
|
|
const whereClause = includeInactive === 'true' ? {} : { isActive: true };
|
|
|
|
const currencies = await Currency.findAll({
|
|
where: whereClause,
|
|
order: [['code', 'ASC']]
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: currencies
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching currencies:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to fetch currencies',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/exchange-rates
|
|
* Get exchange rates with optional filtering
|
|
*/
|
|
router.get('/exchange-rates', async (req, res) => {
|
|
try {
|
|
const {
|
|
fromCurrency,
|
|
toCurrency,
|
|
active = true,
|
|
page = 1,
|
|
limit = 50
|
|
} = req.query;
|
|
|
|
const whereClause = {};
|
|
|
|
if (active === 'true') {
|
|
whereClause.isActive = true;
|
|
}
|
|
|
|
if (fromCurrency) {
|
|
whereClause.fromCurrencyId = fromCurrency;
|
|
}
|
|
|
|
if (toCurrency) {
|
|
whereClause.toCurrencyId = toCurrency;
|
|
}
|
|
|
|
const offset = (page - 1) * limit;
|
|
|
|
const { count, rows: exchangeRates } = await ExchangeRate.findAndCountAll({
|
|
where: whereClause,
|
|
include: [
|
|
{ model: Currency, as: 'fromCurrency' },
|
|
{ model: Currency, as: 'toCurrency' }
|
|
],
|
|
order: [['effectiveDate', 'DESC']],
|
|
limit: parseInt(limit),
|
|
offset: parseInt(offset)
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: exchangeRates,
|
|
pagination: {
|
|
total: count,
|
|
page: parseInt(page),
|
|
limit: parseInt(limit),
|
|
pages: Math.ceil(count / limit)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error fetching exchange rates:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to fetch exchange rates',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/financial/convert
|
|
* Convert amount between currencies
|
|
*/
|
|
router.get('/convert', async (req, res) => {
|
|
try {
|
|
const { amount, from, to } = req.query;
|
|
|
|
if (!amount || !from || !to) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Amount, from currency, and to currency are required'
|
|
});
|
|
}
|
|
|
|
const fromCurrency = await Currency.findOne({ where: { code: from.toUpperCase() } });
|
|
const toCurrency = await Currency.findOne({ where: { code: to.toUpperCase() } });
|
|
|
|
if (!fromCurrency || !toCurrency) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Invalid currency codes'
|
|
});
|
|
}
|
|
|
|
// Find exchange rate
|
|
const exchangeRate = await ExchangeRate.findOne({
|
|
where: {
|
|
fromCurrencyId: fromCurrency.id,
|
|
toCurrencyId: toCurrency.id,
|
|
isActive: true
|
|
}
|
|
});
|
|
|
|
if (!exchangeRate) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'Exchange rate not found'
|
|
});
|
|
}
|
|
|
|
const convertedAmount = parseFloat(amount) * parseFloat(exchangeRate.rate);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
originalAmount: parseFloat(amount),
|
|
convertedAmount: convertedAmount,
|
|
fromCurrency: fromCurrency.code,
|
|
toCurrency: toCurrency.code,
|
|
exchangeRate: parseFloat(exchangeRate.rate),
|
|
effectiveDate: exchangeRate.effectiveDate
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error converting currency:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Failed to convert currency',
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|