From e4441fa476e72e6a75be7491eaaa87c8c1093435 Mon Sep 17 00:00:00 2001 From: Horacio Daniel Ros Date: Wed, 19 Aug 2026 15:05:45 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20agregar=20env=C3=ADo=20de=20correos=20e?= =?UTF-8?q?lectr=C3=B3nicos=20para=20planes=20aprobados=20y=20mejorar=20el?= =?UTF-8?q?=20formato=20de=20fecha=20y=20monto?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MercadoPago/MercadoPagoWebhook.Service.ts | 72 ++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/server/src/api/MercadoPago/MercadoPagoWebhook.Service.ts b/server/src/api/MercadoPago/MercadoPagoWebhook.Service.ts index 976d6df..a279ba0 100644 --- a/server/src/api/MercadoPago/MercadoPagoWebhook.Service.ts +++ b/server/src/api/MercadoPago/MercadoPagoWebhook.Service.ts @@ -6,6 +6,9 @@ import dayjs from "dayjs"; import PlanPaymentsList from "../../Models/PlanPayments/PlanPayments"; import { IPlanSuscriptionDocument } from "../../Models/PlanSubscriptions/PlanSubscriptions.Adapter.Mongoose"; import { VerifyPendingPlanPaymentResponse } from "../../Models/PlanSubscriptions/PlanSubscriptions.interface"; +import PlansList from "../../Models/Plans/Plans"; +import UserList from "../../Models/Users/Users"; +import { NotificationsManager } from "../../Models/Notifications/Notifications"; type MercadoPagoPaymentData = { id: string | number; @@ -41,6 +44,66 @@ type MercadoPagoPaymentVerification = { const REJECTED_PAYMENT_STATUSES = ["rejected", "cancelled", "failed"]; export class MercadoPagoWebhookService { + private formatTemplateDate(date: Date): string { + return new Intl.DateTimeFormat("es-AR", { + day: "2-digit", + month: "2-digit", + year: "numeric", + }).format(date); + } + + private formatTemplateAmount(amount: number): string { + return new Intl.NumberFormat("es-AR", { + style: "currency", + currency: "ARS", + }).format(amount); + } + + private async sendApprovedPaidPlanEmail( + subscription: IPlanSuscriptionDocument, + paymentData: MercadoPagoPaymentData, + userId: string, + approvedAt: Date, + periodStart: Date + ): Promise { + try { + const plan = await PlansList.findOne({ _id: String(subscription.planId) }); + + if (plan.price <= 0) { + return; + } + + const user = await UserList.users.findOne({ _id: userId }); + + if (!user) { + return; + } + + await NotificationsManager.sendEmail({ + email: user.email, + templateId: "6a85ebb1323720a53d0cfd3f", + context: { + username: user.firstName || user.email, + nombre_plan: plan.name, + importe: this.formatTemplateAmount(paymentData.transaction_amount || 0), + fecha_pago: this.formatTemplateDate(approvedAt), + periodo_desde: this.formatTemplateDate(periodStart), + periodo_hasta: this.formatTemplateDate(subscription.endDate), + }, + }); + } catch (e) { + if (axios.isAxiosError(e)) { + console.error("Error enviando email de pago de plan aprobado:", { + status: e.response?.status, + data: e.response?.data, + }); + return; + } + + console.error("Error enviando email de pago de plan aprobado:", e); + } + } + public async handleWebhook(body: any): Promise { await connect(`${process.env.DATABASE_CONNECTION}`); @@ -300,6 +363,9 @@ export class MercadoPagoWebhookService { const periodStart = isExtensionPayment && dayjs(subscription.endDate).isAfter(approvedAt) ? subscription.endDate : approvedAt; + const transactionId = String(paymentData.id); + const existingPayments = await PlanPaymentsList.find({ transactionId }); + const shouldSendApprovedPaymentEmail = existingPayments.length === 0; if (isUpgradePayment && subscription.pendingPaymentPlanId) { subscription.planId = subscription.pendingPaymentPlanId; @@ -342,8 +408,12 @@ export class MercadoPagoWebhookService { paymentDate: approvedAt, paymentMethod: paymentData.payment_method_id || paymentData.payment_type_id || "mercadopago", status: "completed", - transactionId: String(paymentData.id), + transactionId, }); + + if (shouldSendApprovedPaymentEmail) { + await this.sendApprovedPaidPlanEmail(subscription, paymentData, userId, approvedAt, periodStart); + } } private async applyRejectedPayment(