'use strict'; const mdl_CoursePurchase = require('../../models/courses/course_purchases.mdl'); const mdl_Product = require('../../models/courses/products.mdl'); const paymentSvc = require('../../services/payment.service'); const R = require('../../utils/response.util'); // ─── CREATE ORDER ───────────────────────────────────────────────────────────── exports.createCourseOrder = async (req, res) => { try { const { product_id } = req.body; if (!product_id) return R.error(res, 'product_id is required.', 400); const product = await mdl_Product.findOne({ where: { id: product_id, is_active: true } }); if (!product) return R.error(res, 'Product not found or inactive.', 404); const existing = await mdl_CoursePurchase.findOne({ where: { user_id: req.user.user_id, product_id, status: 'completed' }, }); if (existing) { const stillActive = !existing.expires_at || new Date(existing.expires_at) > new Date(); if (stillActive) return R.error(res, 'You already have active access to this course.', 409); } const ppOrder = await paymentSvc.createOrder('paypal', { amount: Number(product.price).toFixed(2), currency: product.currency, referenceId: `user_${req.user.user_id}_product_${product_id}`, returnUrl: `${process.env.FRONTEND_URL}/course/${product.course_id}/checkout`, cancelUrl: `${process.env.FRONTEND_URL}/course/${product.course_id}/checkout?cancelled=true`, }); const approvalUrl = ppOrder.links?.find((l) => l.rel === 'approve')?.href ?? null; const expiresAt = product.access_days ? new Date(Date.now() + product.access_days * 86400000) : null; const purchase = await mdl_CoursePurchase.create({ user_id: req.user.user_id, product_id, amount: product.price, currency: product.currency, status: 'pending', provider: 'paypal', expires_at: expiresAt, provider_payload: { order_id: ppOrder.id, approval_url: approvalUrl }, }); return R.success(res, 'Order created.', { purchase_id: purchase.id, order_id: ppOrder.id, approval_url: approvalUrl, amount: product.price, currency: product.currency, }, 201); } catch (err) { console.error('[CLIENT][COURSE PURCHASE][CREATE ORDER]', err); return R.error(res, 'Could not create order.', 500); } }; // ─── CAPTURE ORDER ──────────────────────────────────────────────────────────── exports.captureCourseOrder = async (req, res) => { try { const { order_id } = req.body; if (!order_id) return R.error(res, 'order_id is required.', 400); const purchase = await mdl_CoursePurchase.findOne({ where: { user_id: req.user.user_id, status: 'pending' }, include: [{ model: mdl_Product, as: 'product' }], order: [['createdAt', 'DESC']], }); if (!purchase || purchase.provider_payload?.order_id !== order_id) return R.error(res, 'Pending purchase not found.', 404); let captureData; try { captureData = await paymentSvc.captureOrder(purchase.provider, order_id); } catch (ppErr) { await purchase.update({ status: 'failed', provider_payload: { ...purchase.provider_payload, error: ppErr?.response?.data ?? {} }, }); return R.error(res, 'Payment capture failed.', 402); } const capture = captureData.purchase_units?.[0]?.payments?.captures?.[0]; await purchase.update({ status: 'completed', paid_at: new Date(), provider_payload: { ...purchase.provider_payload, capture_id: capture?.id, payer_id: captureData.payer?.payer_id, capture: captureData, }, }); return R.success(res, 'Payment successful. Course access granted.', { purchase_id: purchase.id, expires_at: purchase.expires_at, course_id: purchase.product.course_id, }); } catch (err) { console.error('[CLIENT][COURSE PURCHASE][CAPTURE]', err); return R.error(res, 'Could not capture order.', 500); } }; // ─── CANCEL ORDER ───────────────────────────────────────────────────────────── exports.cancelCourseOrder = async (req, res) => { try { const { order_id } = req.body; if (!order_id) return R.error(res, 'order_id is required.', 400); const purchase = await mdl_CoursePurchase.findOne({ where: { user_id: req.user.user_id, status: 'pending' }, order: [['createdAt', 'DESC']], }); if (!purchase || purchase.provider_payload?.order_id !== order_id) return R.error(res, 'Pending purchase not found.', 404); await purchase.update({ status: 'cancelled', provider_payload: { ...purchase.provider_payload, cancelled_at: new Date().toISOString() }, }); return R.success(res, 'Purchase cancelled.'); } catch (err) { console.error('[CLIENT][COURSE PURCHASE][CANCEL]', err); return R.error(res, 'Could not cancel purchase.', 500); } }; // ─── MY PURCHASES ───────────────────────────────────────────────────────────── exports.getMyPurchases = async (req, res) => { try { const purchases = await mdl_CoursePurchase.findAll({ where: { user_id: req.user.user_id }, include: [{ model: mdl_Product, as: 'product', attributes: ['id', 'name', 'course_id', 'access_days'] }], attributes: { exclude: ['provider_payload'] }, order: [['createdAt', 'DESC']], }); return R.success(res, 'Purchases retrieved.', purchases); } catch (err) { console.error('[CLIENT][COURSE PURCHASE][GET MINE]', err); return R.error(res, 'Could not retrieve purchases.', 500); } };