diff --git a/pkg/models/contrato.go b/pkg/models/contrato.go index 5ddba46..492840a 100644 --- a/pkg/models/contrato.go +++ b/pkg/models/contrato.go @@ -187,15 +187,74 @@ func LimpiarEnlacePago(contratoID uint) error { }).Error } -// MarcarContratoPagado marca el contrato como pagado al recibir confirmación de la pasarela. +// periodicidadAMeses convierte el string de periodicidad en número de meses. +func periodicidadAMeses(p string) int { + switch p { + case "mensual": + return 1 + case "trimestral": + return 3 + case "semestral": + return 6 + case "anual": + return 12 + default: + return 12 + } +} + +// MarcarContratoPagado marca el contrato como pagado y renueva la fecha de vencimiento +// según la periodicidad del servicio renovable asociado. func MarcarContratoPagado(contratoID uint) error { now := time.Now() - return app.Http.Database.DB.Model(&Contrato{}).Where("id = ?", contratoID).Updates(map[string]interface{}{ + + // Cargar contrato con servicios para calcular nueva fecha de vencimiento + var c Contrato + if err := app.Http.Database.DB.Preload("Servicios").First(&c, contratoID).Error; err != nil { + log.Printf("[PAGO] No se pudo cargar contrato %d para renovar: %v", contratoID, err) + // Fallback: solo marcar pago sin renovar fecha + return app.Http.Database.DB.Model(&Contrato{}).Where("id = ?", contratoID).Updates(map[string]interface{}{ + "pago_confirmado": true, + "fecha_pago": now, + "enlace_pago": "", + "enlace_pago_link_id": "", + }).Error + } + + updates := map[string]interface{}{ "pago_confirmado": true, "fecha_pago": now, "enlace_pago": "", "enlace_pago_link_id": "", - }).Error + "estado": "activo", + } + + // Buscar el servicio renovable con mayor período para calcular nueva fecha + meses := 0 + for _, s := range c.Servicios { + if s.Tipo == "renovable" { + if m := periodicidadAMeses(s.Periodicidad); m > meses { + meses = m + } + } + } + if meses > 0 { + // Extender desde la fecha de vencimiento actual; si ya venció, extender desde hoy + base := c.FechaVencimiento + if base.Before(now) { + base = now + } + nuevaFecha := base.AddDate(0, meses, 0) + updates["fecha_vencimiento"] = nuevaFecha + log.Printf("[PAGO] Contrato %d renovado: %s → %s (%d meses)", + contratoID, + c.FechaVencimiento.Format("02/01/2006"), + nuevaFecha.Format("02/01/2006"), + meses, + ) + } + + return app.Http.Database.DB.Model(&Contrato{}).Where("id = ?", contratoID).Updates(updates).Error } // GetEstadoPago devuelve si el contrato tiene pago confirmado (para polling desde el frontend). diff --git a/rest/controllers/api/pago_controller.go b/rest/controllers/api/pago_controller.go index 8ee40fc..0f22528 100644 --- a/rest/controllers/api/pago_controller.go +++ b/rest/controllers/api/pago_controller.go @@ -61,6 +61,24 @@ func PagoExitosoPage(c *fiber.Ctx) error { }, "layouts/landing") } + // Bold confirmó el pago directamente en la URL de retorno + if boldTxStatus == "approved" { + _ = models.SaveBoldCallbackLog(models.BoldCallbackLog{ + Referencia: ref, PaymentLink: paymentLink, Params: paramsJSON, + Estado: "pagado", IP: c.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) + } + estado, fechaPago := verificarPago(ref) + return c.Render("pago_exitoso", fiber.Map{ + "Ref": ref, "Estado": estado, "FechaPago": fechaPago, + }, "layouts/landing") + } + // Registrar el intento de pago en la tabla de callbacks if ref != "" || paymentLink != "" { entry := models.BoldCallbackLog{