courseworx/backend/plugins/financial-plugin/routes/cart.js
mmabdalla 5477297914 v2.0.2 - Complete Plugin Architecture System and Multi-Currency Implementation
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.
2025-09-14 04:20:37 +03:00

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;