Spaces:
Running
Running
File size: 5,066 Bytes
6e13570 4c6b1e8 6e13570 4c6b1e8 6e13570 4c6b1e8 6e13570 4c6b1e8 6e13570 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | 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 };
|