require('dotenv').config(); const { Preference, Payment } = require('mercadopago'); const crypto = require('crypto'); const db = require('../models'); const clientConfig = require('../config/mercadopago.config'); console.log('MP_ACCESS_TOKEN usado:', clientConfig.accessToken); const isSandbox = clientConfig.accessToken.startsWith('TEST-'); const buildCheckoutUrl = prefId => isSandbox ? `https://sandbox.mercadopago.com/checkout/v1/redirect?pref_id=${prefId}` : `https://www.mercadopago.com/checkout/v1/redirect?pref_id=${prefId}`; // === 1) Crear preferencia y guardar transacción "pendiente" === exports.createPayment = async (req, res) => { try { console.log('BACKEND RECIBE BODY:', req.body); const { amount, email, sellerId, raffleId, ticketQuantity = 1, nombre, rut, telefono, direccion, acepto_condiciones } = req.body; // Validaciones obligatorias if (![amount, email, sellerId, raffleId, nombre, rut, direccion].every(Boolean) || acepto_condiciones !== true) { console.log('❌ Faltan datos:', { amount, email, sellerId, raffleId, nombre, rut, direccion, acepto_condiciones }); return res.status(400).json({ status: 'error', code: 'MISSING_FIELDS', message: 'Faltan datos obligatorios o no aceptó condiciones' }); } if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { return res.status(400).json({ status: 'error', code: 'INVALID_EMAIL', message: 'Email inválido' }); } // Guardar comprador invitado (Buyer) const buyer = await db.Buyer.create({ nombre, rut, correo: email, telefono, direccion, acepto_condiciones }); // Construir preferencia MercadoPago const num = parseFloat(amount), qty = parseInt(ticketQuantity, 10); const preferenceData = { site_id: 'MLC', items: [{ title: `Rifa #${raffleId}`, unit_price: num, quantity: qty, currency_id: 'CLP', description: `Compra de ${qty} ticket(s)` }], payer: { email: email.trim() }, payment_methods: { excluded_payment_types: [{ id: 'atm' }], installments: 1 }, back_urls: { success: process.env.FRONTEND_SUCCESS_URL, failure: process.env.FRONTEND_FAILURE_URL, pending: process.env.FRONTEND_PENDING_URL }, auto_return: 'approved', external_reference: `RIFA-${raffleId}-${Date.now()}`, notification_url: process.env.MP_WEBHOOK_URL, binary_mode: true, metadata: { raffleId, ticketQuantity: qty, sellerId, buyer_id: buyer.id } }; // Instancia para crear preferencia (SDK 2.7) const prefApi = new Preference(clientConfig); // Crear preferencia const result = await prefApi.create({ body: preferenceData }); // El resultado puede venir como .body, .response o directo const pref = result.body || result.response || result; if (!pref.id) throw new Error('No se obtuvo ID de preferencia'); // Guarda la transacción (estado "pendiente") await db.Transaction.create({ buyer_id: buyer.id, usuario_id: null, ticket_ids: [], monto: num * qty, metodo_pago: 'mercadopago', estado_transaccion: 'pendiente', fecha: new Date(), mp_payment_id: pref.id, // Guarda el preference_id! mp_status: 'pending', mp_detail: null }); // Devuelve al frontend la URL para redirigir al usuario a MercadoPago return res.json({ status: 'success', data: { payment_url: buildCheckoutUrl(pref.id), payment_id: pref.id, expires_at: new Date(Date.now() + 3600000).toISOString() } }); } catch (error) { console.error('🔥 createPayment Error:', error); return res.status(500).json({ status: 'error', code: 'MP_API_ERROR', message: error.message || 'Error procesando pago' }); } }; // === 2) Webhook MercadoPago: actualiza estado de la transacción === exports.webhook = async (req, res) => { try { console.log('🔔 Webhook recibido:', req.body); const { action, data, type } = req.body; if (type === 'payment' && data && data.id) { const mpPaymentId = data.id; // Obtener detalles del pago desde MercadoPago API const paymentApi = new Payment(clientConfig); const mpPayment = await paymentApi.get({ id: mpPaymentId }); const mpData = mpPayment.body || mpPayment.response || mpPayment; // Busca la transacción por mp_payment_id (el preference_id que guardaste) const transaction = await db.Transaction.findOne({ where: { mp_payment_id: mpData.preference_id } }); if (!transaction) { console.error('Transacción no encontrada para payment_id:', mpData.preference_id); return res.status(404).send(); } // Actualizar estado de la transacción y guarda TODO el json de MercadoPago await transaction.update({ mp_status: mpData.status, estado_transaccion: mpData.status === 'approved' ? 'exitoso' : mpData.status === 'rejected' ? 'rechazado' : 'pendiente', mp_detail: mpData }); return res.status(200).send(); } res.status(200).send(); // Si no es pago, responde OK igual } catch (err) { console.error('🔥 Webhook MP error:', err); res.status(500).send(); } }; // === 3) Obtener el estado de un pago por ID de preferencia === exports.getPaymentStatus = async (req, res) => { try { const { id } = req.params; const tx = await db.Transaction.findOne({ where: { mp_payment_id: id } }); if (!tx) { return res.status(404).json({ status: 'error', message: 'Transacción no encontrada' }); } return res.json({ status: 'success', data: tx }); } catch (err) { return res.status(500).json({ status: 'error', message: err.message }); } }; // === 4) Listar todas las transacciones (admin) === exports.listPayments = async (req, res) => { try { const txs = await db.Transaction.findAll({ order: [['fecha', 'DESC']] }); return res.json({ status: 'success', data: txs }); } catch (err) { return res.status(500).json({ status: 'error', message: err.message }); } };