diff --git a/main.go b/main.go index d51da8d..903ab92 100755 --- a/main.go +++ b/main.go @@ -69,6 +69,7 @@ func main() { migrations.SeedSaas() migrations.SeedServidores() migrations.SeedAdministracion() + migrations.SeedPlantillasBase() // Iniciar cron de vencimientos services.IniciarCron() defer services.DetenerCron() diff --git a/pkg/models/bold_config.go b/pkg/models/bold_config.go index e0f5078..e49ace3 100644 --- a/pkg/models/bold_config.go +++ b/pkg/models/bold_config.go @@ -105,6 +105,18 @@ func GetBoldWebhookLogs(limit int) ([]BoldWebhookLog, error) { return logs, nil } +// GetBoldWebhookLogsByRef devuelve los webhooks recibidos para una referencia dada. +func GetBoldWebhookLogsByRef(referencia string) ([]BoldWebhookLog, error) { + var logs []BoldWebhookLog + if err := app.Http.Database.DB. + Where("referencia = ?", referencia). + Order("id DESC"). + Find(&logs).Error; err != nil { + return nil, err + } + return logs, nil +} + // GetBoldWebhookLogsPaginated devuelve los logs con paginación y filtro por tipo. // El valor especial "APROBADOS" incluye tanto SALE_APPROVED como API_CHECK. func GetBoldWebhookLogsPaginated(page, limit int, tipo string) ([]BoldWebhookLog, int64, error) { @@ -161,8 +173,9 @@ type BoldCallbackLog struct { Params string `json:"params" gorm:"column:params;type:text"` // Estado: pendiente | pagado | fallido | revertido Estado string `json:"estado" gorm:"column:estado;type:varchar(20);default:'pendiente'"` - // Datos del cliente si están disponibles + // Datos del pago (enriquecidos desde el webhook cuando llega) PayerEmail string `json:"payer_email" gorm:"column:payer_email;type:varchar(255)"` + Monto int64 `json:"monto" gorm:"column:monto;default:0"` // Datos de red (para análisis) IP string `json:"ip" gorm:"column:ip;type:varchar(45)"` UserAgent string `json:"user_agent" gorm:"column:user_agent;type:text"` @@ -186,6 +199,27 @@ func UpdateBoldCallbackEstado(referencia, estado string) { Update("estado", estado) } +// EnrichBoldCallbackLog enriquece los intentos de pago con email y monto del webhook. +// Se llama cuando llega SALE_APPROVED que sí tiene esos datos. +func EnrichBoldCallbackLog(referencia, payerEmail string, monto int64) { + if referencia == "" { + return + } + updates := map[string]interface{}{} + if payerEmail != "" { + updates["payer_email"] = payerEmail + } + if monto > 0 { + updates["monto"] = monto + } + if len(updates) == 0 { + return + } + app.Http.Database.DB.Model(&BoldCallbackLog{}). + Where("referencia = ?", referencia). + Updates(updates) +} + // GetBoldCallbackLogByID carga un intento de pago por su PK. func GetBoldCallbackLogByID(id uint, out *BoldCallbackLog) error { return app.Http.Database.DB.First(out, id).Error diff --git a/resources/views/pasarelas_pago.html b/resources/views/pasarelas_pago.html index da72469..8323077 100644 --- a/resources/views/pasarelas_pago.html +++ b/resources/views/pasarelas_pago.html @@ -408,7 +408,7 @@ Estado Referencia - Payment Link + Monto Email IP Fecha intento @@ -429,7 +429,7 @@ x-text="cb.estado || 'pendiente'"> - + diff --git a/rest/controllers/api/bold_controller.go b/rest/controllers/api/bold_controller.go index 5351831..9416ff0 100644 --- a/rest/controllers/api/bold_controller.go +++ b/rest/controllers/api/bold_controller.go @@ -98,6 +98,10 @@ func BoldWebhook(c *fiber.Ctx) error { if paymentID != "" { models.UpdateBoldCallbackEstado(paymentID, estimado) } + // Enriquecer con email y monto del webhook (solo cuando hay datos reales) + if estimado == "pagado" { + models.EnrichBoldCallbackLog(referencia, payerEmail, monto) + } } // Solo continuar lógica de negocio para SALE_APPROVED ───────────────────── diff --git a/rest/controllers/api/pago_controller.go b/rest/controllers/api/pago_controller.go index cad6b55..0becc97 100644 --- a/rest/controllers/api/pago_controller.go +++ b/rest/controllers/api/pago_controller.go @@ -63,15 +63,34 @@ func PagoExitosoPage(c *fiber.Ctx) error { // Bold confirmó el pago directamente en la URL de retorno if boldTxStatus == "approved" { + // IP real (detrás de nginx) + ip := c.Get("X-Real-IP") + if ip == "" { + ip = c.Get("X-Forwarded-For") + } + if ip == "" { + ip = c.IP() + } _ = models.SaveBoldCallbackLog(models.BoldCallbackLog{ Referencia: ref, PaymentLink: paymentLink, Params: paramsJSON, - Estado: "pagado", IP: c.IP(), UserAgent: string(c.Request().Header.UserAgent()), + Estado: "pagado", IP: ip, UserAgent: string(c.Request().Header.UserAgent()), }) // Marcar contrato si aún no está confirmado var contratoID uint if _, err := fmt.Sscanf(ref, "contrato-%d", &contratoID); err == nil && contratoID > 0 { _ = models.MarcarContratoPagado(contratoID) go services.EnviarCorreoConfirmacionPago(contratoID, "bold") + // Intentar enriquecer con datos del webhook log si ya llegó + go func() { + if wLogs, err := models.GetBoldWebhookLogsByRef(ref); err == nil { + for _, wl := range wLogs { + if wl.PayerEmail != "" || wl.Monto > 0 { + models.EnrichBoldCallbackLog(ref, wl.PayerEmail, wl.Monto) + break + } + } + } + }() } estado, fechaPago := verificarPago(ref) return c.Render("pago_exitoso", fiber.Map{ @@ -81,12 +100,19 @@ func PagoExitosoPage(c *fiber.Ctx) error { // Registrar el intento de pago en la tabla de callbacks if ref != "" || paymentLink != "" { + ip := c.Get("X-Real-IP") + if ip == "" { + ip = c.Get("X-Forwarded-For") + } + if ip == "" { + ip = c.IP() + } entry := models.BoldCallbackLog{ Referencia: ref, PaymentLink: paymentLink, Params: paramsJSON, Estado: "pendiente", - IP: c.IP(), + IP: ip, UserAgent: string(c.Request().Header.UserAgent()), } _ = models.SaveBoldCallbackLog(entry)