diff --git a/pkg/models/notificacion_regla.go b/pkg/models/notificacion_regla.go index 1d650e6..c6b5e71 100644 --- a/pkg/models/notificacion_regla.go +++ b/pkg/models/notificacion_regla.go @@ -7,12 +7,13 @@ import ( type NotificacionRegla struct { gorm.Model - Nombre string `json:"nombre" gorm:"column:nombre"` - DiasAntes int `json:"dias_antes" gorm:"column:dias_antes"` - PlantillaID uint `json:"plantilla_id" gorm:"column:plantilla_id"` - Plantilla PlantillaCorreo `json:"plantilla" gorm:"foreignKey:PlantillaID"` - Activo bool `json:"activo" gorm:"column:activo;default:true"` - AplicaA string `json:"aplica_a" gorm:"column:aplica_a;default:'todos'"` // todos | renovable | unico + Nombre string `json:"nombre" gorm:"column:nombre"` + DiasAntes int `json:"dias_antes" gorm:"column:dias_antes"` + PlantillaID uint `json:"plantilla_id" gorm:"column:plantilla_id"` + Plantilla PlantillaCorreo `json:"plantilla" gorm:"foreignKey:PlantillaID"` + Activo bool `json:"activo" gorm:"column:activo;default:true"` + AplicaA string `json:"aplica_a" gorm:"column:aplica_a;default:'todos'"` // todos | renovable | unico + PasarelaEnlace string `json:"pasarela_enlace" gorm:"column:pasarela_enlace;default:'bold'"` // bold | dlocal | ninguna } func (NotificacionRegla) TableName() string { return "notificacion_reglas" } @@ -48,11 +49,12 @@ func CreateRegla(r NotificacionRegla) error { func UpdateRegla(r NotificacionRegla) error { return app.Http.Database.DB.Model(&r).Updates(map[string]interface{}{ - "nombre": r.Nombre, - "dias_antes": r.DiasAntes, - "plantilla_id": r.PlantillaID, - "activo": r.Activo, - "aplica_a": r.AplicaA, + "nombre": r.Nombre, + "dias_antes": r.DiasAntes, + "plantilla_id": r.PlantillaID, + "activo": r.Activo, + "aplica_a": r.AplicaA, + "pasarela_enlace": r.PasarelaEnlace, }).Error } diff --git a/pkg/services/renovacion_service.go b/pkg/services/renovacion_service.go index f461cf3..aed1681 100644 --- a/pkg/services/renovacion_service.go +++ b/pkg/services/renovacion_service.go @@ -142,7 +142,7 @@ func EnviarNotificacionGrupo(regla *models.NotificacionRegla, cliente *models.Cl // Generar o reutilizar el enlace de pago para el primer contrato del grupo enlacePago := "" if len(contratos) > 0 { - enlacePago = ObtenerOCrearEnlacePago(&contratos[0]) + enlacePago = ObtenerOCrearEnlacePago(&contratos[0], regla.PasarelaEnlace) } datos := DatosPlantilla{ @@ -221,24 +221,40 @@ func ReenviarLog(logEntry *models.NotificacionLog) error { return sendErr } -// ObtenerOCrearEnlacePago devuelve el enlace Bold vigente del contrato. -// Si ya existe uno (no ha sido pagado aún), lo reutiliza. -// Si no existe, crea uno nuevo en Bold y lo guarda en el contrato. -// Cuando Bold notifica SALE_APPROVED, llamar models.LimpiarEnlacePago(contratoID) -// para que la próxima notificación genere un link nuevo. -func ObtenerOCrearEnlacePago(contrato *models.Contrato) string { +// ObtenerOCrearEnlacePago devuelve el enlace de pago vigente del contrato. +// gateway puede ser "bold", "dlocal" o "ninguna"/"". +// Si ya existe un enlace guardado, lo reutiliza (mismo ciclo). +// Cuando el pago se aprueba, llamar models.LimpiarEnlacePago para el siguiente ciclo. +func ObtenerOCrearEnlacePago(contrato *models.Contrato, gateway string) string { + // Normalizar gateway vacío → bold por defecto + if gateway == "" { + gateway = "bold" + } + if gateway == "ninguna" { + return "" + } + // Reutilizar si ya hay uno vigente if contrato.EnlacePago != "" { return contrato.EnlacePago } + switch gateway { + case "dlocal": + return obtenerEnlaceDlocal(contrato) + default: // "bold" + return obtenerEnlaceBold(contrato) + } +} + +// obtenerEnlaceBold crea un payment link en Bold y lo persiste en el contrato. +func obtenerEnlaceBold(contrato *models.Contrato) string { cfg, err := models.GetBoldConfig() if err != nil { - log.Printf("[BOLD] ObtenerOCrearEnlacePago: sin config activa para contrato %d", contrato.ID) + log.Printf("[BOLD] sin config activa para contrato %d", contrato.ID) return "" } - // Nombre de servicios en la descripción desc := fmt.Sprintf("Renovación contrato #%d", contrato.ID) if len(contrato.Servicios) > 0 { desc = fmt.Sprintf("Renovación: %s", contrato.Servicios[0].Nombre) @@ -257,13 +273,55 @@ func ObtenerOCrearEnlacePago(contrato *models.Contrato) string { result, err := CreateBoldPaymentLink(cfg, req) if err != nil { - log.Printf("[BOLD] ObtenerOCrearEnlacePago: error creando link para contrato %d: %v", contrato.ID, err) + log.Printf("[BOLD] error creando link para contrato %d: %v", contrato.ID, err) return "" } - // Persistir en BD _ = models.GuardarEnlacePago(contrato.ID, result.Payload.PaymentLink, result.Payload.URL) contrato.EnlacePago = result.Payload.URL contrato.EnlacePagoLinkID = result.Payload.PaymentLink return result.Payload.URL } + +// obtenerEnlaceDlocal crea un pago en dLocal y devuelve la URL de redirección. +func obtenerEnlaceDlocal(contrato *models.Contrato) string { + cfg, err := models.GetLastActiveDlocalApi() + if err != nil { + log.Printf("[DLOCAL] sin config activa para contrato %d", contrato.ID) + return "" + } + + desc := fmt.Sprintf("Renovación contrato #%d", contrato.ID) + if len(contrato.Servicios) > 0 { + desc = fmt.Sprintf("Renovación: %s", contrato.Servicios[0].Nombre) + } + + pagoReq := PagoRequest{ + Currency: "COP", + Amount: contrato.PrecioAcordado, + OrderID: int(contrato.ID), + Description: desc, + SuccessURL: cfg.UrlProd + "/pago-exitoso", + BackURL: cfg.UrlProd + "/pago-cancelado", + } + + body, err := CreatePago(*cfg, pagoReq) + if err != nil { + log.Printf("[DLOCAL] error creando pago para contrato %d: %v", contrato.ID, err) + return "" + } + + var resp struct { + RedirectURL string `json:"redirect_url"` + ID string `json:"id"` + } + if err := json.Unmarshal(body, &resp); err != nil || resp.RedirectURL == "" { + log.Printf("[DLOCAL] respuesta sin redirect_url para contrato %d: %s", contrato.ID, string(body)) + return "" + } + + _ = models.GuardarEnlacePago(contrato.ID, resp.ID, resp.RedirectURL) + contrato.EnlacePago = resp.RedirectURL + contrato.EnlacePagoLinkID = resp.ID + return resp.RedirectURL +} diff --git a/resources/views/renovaciones/plantillas.html b/resources/views/renovaciones/plantillas.html index c7bf830..7024b43 100644 --- a/resources/views/renovaciones/plantillas.html +++ b/resources/views/renovaciones/plantillas.html @@ -82,7 +82,9 @@ {{.FechaVencimiento}}, {{.DiasRestantes}}, {{.Total}}, - {{range .Servicios}}...{{.Nombre}} {{.Precio}}...{{end}} + {{range .Servicios}}...{{.Nombre}} {{.Precio}}...{{end}}, + {{.EnlacePago}} + (URL de pago generada automáticamente por Bold o dLocal al enviar)
diff --git a/resources/views/renovaciones/reglas.html b/resources/views/renovaciones/reglas.html index ea1ff69..705e61c 100644 --- a/resources/views/renovaciones/reglas.html +++ b/resources/views/renovaciones/reglas.html @@ -20,6 +20,7 @@ Días antes Plantilla Aplica a + Pasarela Estado @@ -35,6 +36,11 @@ + + +