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.
352 lines
9.6 KiB
JavaScript
352 lines
9.6 KiB
JavaScript
/**
|
|
* Shopping Cart Routes for Financial Plugin
|
|
*
|
|
* This file handles all shopping cart operations including
|
|
* adding/removing items, calculating totals, and applying coupons.
|
|
*/
|
|
|
|
const express = require('express');
|
|
const { body, validationResult } = require('express-validator');
|
|
const { auth } = require('../../../middleware/auth');
|
|
const { Cart, Coupon } = require('../models');
|
|
const { v4: uuidv4 } = require('uuid');
|
|
|
|
const router = express.Router();
|
|
|
|
// Middleware to get or create cart
|
|
const getOrCreateCart = async (req, res, next) => {
|
|
try {
|
|
const { userId } = req.user || {};
|
|
const sessionId = req.headers['x-session-id'] || req.sessionID;
|
|
|
|
let cart = await Cart.findByUserOrSession(userId, sessionId);
|
|
|
|
if (!cart) {
|
|
cart = await Cart.create({
|
|
userId: userId || null,
|
|
sessionId: userId ? null : sessionId,
|
|
items: [],
|
|
totalAmount: 0,
|
|
discountAmount: 0,
|
|
taxAmount: 0,
|
|
finalAmount: 0
|
|
});
|
|
}
|
|
|
|
req.cart = cart;
|
|
next();
|
|
} catch (error) {
|
|
console.error('Cart middleware error:', error);
|
|
res.status(500).json({ error: 'Failed to initialize cart' });
|
|
}
|
|
};
|
|
|
|
// @route GET /api/financial/cart
|
|
// @desc Get current cart contents
|
|
// @access Private
|
|
router.get('/', auth, getOrCreateCart, async (req, res) => {
|
|
try {
|
|
const cart = req.cart;
|
|
|
|
// Check if cart is expired
|
|
if (cart.isExpired()) {
|
|
await cart.destroy();
|
|
return res.json({
|
|
cart: {
|
|
id: null,
|
|
items: [],
|
|
totalAmount: 0,
|
|
discountAmount: 0,
|
|
taxAmount: 0,
|
|
finalAmount: 0,
|
|
couponCode: null,
|
|
expiresAt: null
|
|
}
|
|
});
|
|
}
|
|
|
|
res.json({
|
|
cart: {
|
|
id: cart.id,
|
|
items: cart.items,
|
|
totalAmount: parseFloat(cart.totalAmount),
|
|
discountAmount: parseFloat(cart.discountAmount),
|
|
taxAmount: parseFloat(cart.taxAmount),
|
|
finalAmount: parseFloat(cart.finalAmount),
|
|
couponCode: cart.couponCode,
|
|
expiresAt: cart.expiresAt
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Get cart error:', error);
|
|
res.status(500).json({ error: 'Failed to get cart' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/financial/cart/add
|
|
// @desc Add course to cart
|
|
// @access Private
|
|
router.post('/add', [
|
|
auth,
|
|
getOrCreateCart,
|
|
body('courseId').isUUID().withMessage('Valid course ID is required'),
|
|
body('price').isFloat({ min: 0 }).withMessage('Valid price is required'),
|
|
body('type').isIn(['online', 'classroom', 'hybrid']).withMessage('Valid course type is required'),
|
|
body('quantity').optional().isInt({ min: 1 }).withMessage('Quantity must be a positive integer')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { courseId, price, type, quantity = 1 } = req.body;
|
|
const cart = req.cart;
|
|
|
|
// Check cart item limit
|
|
const maxItems = 10; // This should come from plugin settings
|
|
if (cart.items.length >= maxItems) {
|
|
return res.status(400).json({
|
|
error: 'Cart is full. Maximum 10 items allowed.'
|
|
});
|
|
}
|
|
|
|
// Check if course is already in cart
|
|
const existingItem = cart.items.find(item => item.courseId === courseId);
|
|
if (existingItem) {
|
|
return res.status(400).json({
|
|
error: 'Course is already in cart'
|
|
});
|
|
}
|
|
|
|
// Add item to cart
|
|
cart.addItem(courseId, price, type, quantity);
|
|
await cart.save();
|
|
|
|
res.json({
|
|
message: 'Course added to cart successfully',
|
|
cart: {
|
|
id: cart.id,
|
|
items: cart.items,
|
|
totalAmount: parseFloat(cart.totalAmount),
|
|
discountAmount: parseFloat(cart.discountAmount),
|
|
taxAmount: parseFloat(cart.taxAmount),
|
|
finalAmount: parseFloat(cart.finalAmount),
|
|
couponCode: cart.couponCode
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Add to cart error:', error);
|
|
res.status(500).json({ error: 'Failed to add course to cart' });
|
|
}
|
|
});
|
|
|
|
// @route DELETE /api/financial/cart/remove/:courseId
|
|
// @desc Remove course from cart
|
|
// @access Private
|
|
router.delete('/remove/:courseId', [
|
|
auth,
|
|
getOrCreateCart,
|
|
body('courseId').isUUID().withMessage('Valid course ID is required')
|
|
], async (req, res) => {
|
|
try {
|
|
const { courseId } = req.params;
|
|
const cart = req.cart;
|
|
|
|
// Remove item from cart
|
|
cart.removeItem(courseId);
|
|
await cart.save();
|
|
|
|
res.json({
|
|
message: 'Course removed from cart successfully',
|
|
cart: {
|
|
id: cart.id,
|
|
items: cart.items,
|
|
totalAmount: parseFloat(cart.totalAmount),
|
|
discountAmount: parseFloat(cart.discountAmount),
|
|
taxAmount: parseFloat(cart.taxAmount),
|
|
finalAmount: parseFloat(cart.finalAmount),
|
|
couponCode: cart.couponCode
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Remove from cart error:', error);
|
|
res.status(500).json({ error: 'Failed to remove course from cart' });
|
|
}
|
|
});
|
|
|
|
// @route PUT /api/financial/cart/update
|
|
// @desc Update cart item quantity
|
|
// @access Private
|
|
router.put('/update', [
|
|
auth,
|
|
getOrCreateCart,
|
|
body('courseId').isUUID().withMessage('Valid course ID is required'),
|
|
body('quantity').isInt({ min: 1 }).withMessage('Quantity must be a positive integer')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { courseId, quantity } = req.body;
|
|
const cart = req.cart;
|
|
|
|
// Update item quantity
|
|
cart.updateItemQuantity(courseId, quantity);
|
|
await cart.save();
|
|
|
|
res.json({
|
|
message: 'Cart updated successfully',
|
|
cart: {
|
|
id: cart.id,
|
|
items: cart.items,
|
|
totalAmount: parseFloat(cart.totalAmount),
|
|
discountAmount: parseFloat(cart.discountAmount),
|
|
taxAmount: parseFloat(cart.taxAmount),
|
|
finalAmount: parseFloat(cart.finalAmount),
|
|
couponCode: cart.couponCode
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Update cart error:', error);
|
|
res.status(500).json({ error: 'Failed to update cart' });
|
|
}
|
|
});
|
|
|
|
// @route POST /api/financial/cart/apply-coupon
|
|
// @desc Apply coupon to cart
|
|
// @access Private
|
|
router.post('/apply-coupon', [
|
|
auth,
|
|
getOrCreateCart,
|
|
body('couponCode').isLength({ min: 3, max: 50 }).withMessage('Valid coupon code is required')
|
|
], async (req, res) => {
|
|
try {
|
|
const errors = validationResult(req);
|
|
if (!errors.isEmpty()) {
|
|
return res.status(400).json({ errors: errors.array() });
|
|
}
|
|
|
|
const { couponCode } = req.body;
|
|
const cart = req.cart;
|
|
|
|
// Find coupon
|
|
const coupon = await Coupon.findByCode(couponCode);
|
|
if (!coupon) {
|
|
return res.status(400).json({
|
|
error: 'Invalid coupon code'
|
|
});
|
|
}
|
|
|
|
// Validate coupon
|
|
const validation = coupon.isValid();
|
|
if (!validation.valid) {
|
|
return res.status(400).json({
|
|
error: validation.reason
|
|
});
|
|
}
|
|
|
|
// Check minimum order amount
|
|
if (!coupon.canApplyToOrder(cart.totalAmount)) {
|
|
return res.status(400).json({
|
|
error: `Minimum order amount of $${coupon.minOrderAmount} required`
|
|
});
|
|
}
|
|
|
|
// Apply coupon to cart
|
|
cart.applyCoupon(coupon);
|
|
await cart.save();
|
|
|
|
res.json({
|
|
message: 'Coupon applied successfully',
|
|
cart: {
|
|
id: cart.id,
|
|
items: cart.items,
|
|
totalAmount: parseFloat(cart.totalAmount),
|
|
discountAmount: parseFloat(cart.discountAmount),
|
|
taxAmount: parseFloat(cart.taxAmount),
|
|
finalAmount: parseFloat(cart.finalAmount),
|
|
couponCode: cart.couponCode
|
|
},
|
|
coupon: {
|
|
code: coupon.code,
|
|
name: coupon.name,
|
|
type: coupon.type,
|
|
value: parseFloat(coupon.value)
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Apply coupon error:', error);
|
|
res.status(500).json({ error: 'Failed to apply coupon' });
|
|
}
|
|
});
|
|
|
|
// @route DELETE /api/financial/cart/remove-coupon
|
|
// @desc Remove coupon from cart
|
|
// @access Private
|
|
router.delete('/remove-coupon', [
|
|
auth,
|
|
getOrCreateCart
|
|
], async (req, res) => {
|
|
try {
|
|
const cart = req.cart;
|
|
|
|
// Remove coupon from cart
|
|
cart.couponCode = null;
|
|
cart.discountAmount = 0;
|
|
cart.calculateTotals();
|
|
await cart.save();
|
|
|
|
res.json({
|
|
message: 'Coupon removed successfully',
|
|
cart: {
|
|
id: cart.id,
|
|
items: cart.items,
|
|
totalAmount: parseFloat(cart.totalAmount),
|
|
discountAmount: parseFloat(cart.discountAmount),
|
|
taxAmount: parseFloat(cart.taxAmount),
|
|
finalAmount: parseFloat(cart.finalAmount),
|
|
couponCode: cart.couponCode
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Remove coupon error:', error);
|
|
res.status(500).json({ error: 'Failed to remove coupon' });
|
|
}
|
|
});
|
|
|
|
// @route DELETE /api/financial/cart/clear
|
|
// @desc Clear entire cart
|
|
// @access Private
|
|
router.delete('/clear', [
|
|
auth,
|
|
getOrCreateCart
|
|
], async (req, res) => {
|
|
try {
|
|
const cart = req.cart;
|
|
|
|
// Clear cart
|
|
cart.clear();
|
|
await cart.save();
|
|
|
|
res.json({
|
|
message: 'Cart cleared successfully',
|
|
cart: {
|
|
id: cart.id,
|
|
items: cart.items,
|
|
totalAmount: parseFloat(cart.totalAmount),
|
|
discountAmount: parseFloat(cart.discountAmount),
|
|
taxAmount: parseFloat(cart.taxAmount),
|
|
finalAmount: parseFloat(cart.finalAmount),
|
|
couponCode: cart.couponCode
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Clear cart error:', error);
|
|
res.status(500).json({ error: 'Failed to clear cart' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|