up
This commit is contained in:
@@ -115,6 +115,18 @@ func GetContratosYaVencidos() ([]Contrato, error) {
|
|||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetContratosConEnlacePendiente devuelve contratos que tienen un enlace de pago Bold generado
|
||||||
|
// pero cuyo pago aún no ha sido confirmado. Usado para polling periódico de la API de Bold.
|
||||||
|
func GetContratosConEnlacePendiente() ([]Contrato, error) {
|
||||||
|
var items []Contrato
|
||||||
|
if err := app.Http.Database.DB.Preload("Cliente").
|
||||||
|
Where("enlace_pago_link_id != '' AND pago_confirmado = false AND deleted_at IS NULL").
|
||||||
|
Find(&items).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
// syncContratoServicios sincroniza la tabla join contrato_servicios usando SQL directo
|
// syncContratoServicios sincroniza la tabla join contrato_servicios usando SQL directo
|
||||||
// para evitar que GORM intente hacer upsert de los servicios existentes.
|
// para evitar que GORM intente hacer upsert de los servicios existentes.
|
||||||
func syncContratoServicios(contratoID uint, servicioIDs []uint) error {
|
func syncContratoServicios(contratoID uint, servicioIDs []uint) error {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
@@ -25,8 +26,14 @@ func IniciarCron() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Polling Bold — cada 15 minutos para detectar pagos sin webhook
|
||||||
|
if _, err := cronScheduler.AddFunc("*/15 * * * *", VerificarPagosBoldPendientes); err != nil {
|
||||||
|
log.Printf("[CRON] Error registrando tarea bold_polling: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cronScheduler.Start()
|
cronScheduler.Start()
|
||||||
log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM")
|
log.Println("[CRON] Scheduler iniciado — vencimientos próximos 8AM, ya vencidos 9AM, Bold polling cada 15min")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DetenerCron para graceful shutdown
|
// DetenerCron para graceful shutdown
|
||||||
@@ -141,3 +148,63 @@ func procesarContratos(regla *models.NotificacionRegla, contratos []models.Contr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VerificarPagosBoldPendientes consulta la API de Bold para cada contrato que tiene un
|
||||||
|
// enlace de pago generado pero aún no confirmado. Se ejecuta cada 15 minutos via cron.
|
||||||
|
// Esto compensa la ausencia de webhooks de Bold.
|
||||||
|
func VerificarPagosBoldPendientes() {
|
||||||
|
contratos, err := models.GetContratosConEnlacePendiente()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[BOLD-POLL] Error obteniendo contratos pendientes: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(contratos) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
boldCfg, err := models.GetBoldConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[BOLD-POLL] Sin configuración Bold activa: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[BOLD-POLL] Verificando %d contrato(s) con enlace pendiente...", len(contratos))
|
||||||
|
|
||||||
|
for _, c := range contratos {
|
||||||
|
paid, paymentID, monto, err := CheckBoldLinkPaid(boldCfg, c.EnlacePagoLinkID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("[BOLD-POLL] Error consultando link %s (contrato %d): %v", c.EnlacePagoLinkID, c.ID, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !paid {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ref := fmt.Sprintf("contrato-%d", c.ID)
|
||||||
|
log.Printf("[BOLD-POLL] Pago detectado para %s (payment_id=%s, monto=%d)", ref, paymentID, monto)
|
||||||
|
|
||||||
|
if ok, _ := models.MarcarContratoPagado(c.ID); ok {
|
||||||
|
go EnviarCorreoConfirmacionPago(c.ID, "bold")
|
||||||
|
log.Printf("[BOLD-POLL] Contrato %d marcado como pagado", c.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enriquecer callback log si existe
|
||||||
|
go models.EnrichBoldCallbackLog(ref, c.Cliente.Email, monto)
|
||||||
|
|
||||||
|
// Guardar en webhook log como API_CHECK para trazabilidad
|
||||||
|
notifID := "api-check-" + paymentID
|
||||||
|
if paymentID != "" && !models.IsBoldNotificationDuplicate(notifID) {
|
||||||
|
_ = models.SaveBoldWebhookLog(models.BoldWebhookLog{
|
||||||
|
NotificationID: notifID,
|
||||||
|
Tipo: "API_CHECK",
|
||||||
|
PaymentID: paymentID,
|
||||||
|
Referencia: ref,
|
||||||
|
PayerEmail: c.Cliente.Email,
|
||||||
|
Monto: monto,
|
||||||
|
Procesado: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[BOLD-POLL] Verificación completada")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user