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 };