const Order = require("../models/Order"); const User = require("../models/User"); const AuditLog = require("../models/AuditLog"); const { sendPaymentRejectedEmail } = require("../utils/email"); const { confirmOrderAndGrantAccess } = require("../utils/orderConfirmation"); const logger = require("../utils/logger"); async function listOrders(req, res, next) { try { const { status } = req.query; const filter = {}; if (status) filter.status = status; const orders = await Order.find(filter) .sort({ createdAt: -1 }) .populate("user", "name email") .populate("course", "title"); res.json({ orders: orders.map((o) => ({ ...o.toPublicJSON(), user: o.user ? { id: o.user._id, name: o.user.name, email: o.user.email } : null, })), }); } catch (err) { next(err); } } /** Admin views the decrypted payment reference only when actively reviewing a specific order. */ async function getOrderDetail(req, res, next) { try { const order = await Order.findById(req.params.id) .select("+paymentReferenceEncrypted +payerNoteEncrypted") .populate("user", "name email") .populate("course", "title price"); if (!order) return res.status(404).json({ error: "Order not found." }); await AuditLog.create({ actor: req.user._id, actorEmail: req.user.email, action: "admin.order_viewed", targetType: "Order", targetId: order._id, ip: req.ip, userAgent: req.headers["user-agent"], }); res.json({ order: { ...order.toPublicJSON({ includeSensitive: true }), user: order.user ? { id: order.user._id, name: order.user.name, email: order.user.email } : null, }, }); } catch (err) { next(err); } } /** * Confirms a payment: marks the order confirmed, grants course access to * the user, and emails the customer. Wrapped in a transaction so a * partial failure can't leave the order confirmed without access * granted (or vice versa). */ async function confirmOrder(req, res, next) { try { const existing = await Order.findById(req.params.id); if (!existing) return res.status(404).json({ error: "Order not found." }); if (existing.status === "confirmed") return res.status(409).json({ error: "Order already confirmed." }); const { order } = await confirmOrderAndGrantAccess({ orderId: req.params.id, confirmedBy: req.user._id, ip: req.ip, userAgent: req.headers["user-agent"], }); res.json({ order: order.toPublicJSON() }); } catch (err) { next(err); } } async function rejectOrder(req, res, next) { try { const { reason } = req.body; const order = await Order.findById(req.params.id).populate("user", "name email"); if (!order) return res.status(404).json({ error: "Order not found." }); if (order.status === "confirmed") { return res.status(409).json({ error: "A confirmed order cannot be rejected. Consider revoking access separately." }); } order.status = "rejected"; order.rejectionReason = reason || null; await order.save(); await AuditLog.create({ actor: req.user._id, actorEmail: req.user.email, action: "admin.order_rejected", targetType: "Order", targetId: order._id, ip: req.ip, userAgent: req.headers["user-agent"], metadata: { reason: reason || null }, }); if (order.user) { sendPaymentRejectedEmail({ to: order.user.email, name: order.user.name, order, courseTitle: order.courseTitleSnapshot, reason, }).catch((err) => logger.error("Payment-rejected email failed", { error: err.message })); } res.json({ order: order.toPublicJSON() }); } catch (err) { next(err); } } async function dashboardStats(req, res, next) { try { const [pendingCount, confirmedCount, rejectedCount, totalUsers, revenueAgg] = await Promise.all([ Order.countDocuments({ status: "awaiting_confirmation" }), Order.countDocuments({ status: "confirmed" }), Order.countDocuments({ status: "rejected" }), User.countDocuments({ role: "user" }), Order.aggregate([{ $match: { status: "confirmed" } }, { $group: { _id: "$currency", total: { $sum: "$amount" } } }]), ]); res.json({ pendingOrders: pendingCount, confirmedOrders: confirmedCount, rejectedOrders: rejectedCount, totalUsers, revenueByCurrency: revenueAgg.map((r) => ({ currency: r._id, total: r.total })), }); } catch (err) { next(err); } } async function listUsers(req, res, next) { try { const users = await User.find({}).sort({ createdAt: -1 }).limit(500); res.json({ users: users.map((u) => u.toSafeJSON()) }); } catch (err) { next(err); } } module.exports = { listOrders, getOrderDetail, confirmOrder, rejectOrder, dashboardStats, listUsers };