This commit is contained in:
Lizandro Guarnizo
2026-05-13 22:36:09 -05:00
parent 67f9466991
commit ce2fed2587
2 changed files with 80 additions and 1 deletions
+12
View File
@@ -115,6 +115,18 @@ func GetContratosYaVencidos() ([]Contrato, error) {
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
// para evitar que GORM intente hacer upsert de los servicios existentes.
func syncContratoServicios(contratoID uint, servicioIDs []uint) error {
+68 -1
View File
@@ -1,6 +1,7 @@
package services
import (
"fmt"
"log"
"github.com/robfig/cron/v3"
@@ -25,8 +26,14 @@ func IniciarCron() {
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()
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
@@ -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")
}