ip
This commit is contained in:
+23
-10
@@ -203,9 +203,11 @@ func periodicidadAMeses(p string) int {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// MarcarContratoPagado marca el contrato como pagado y renueva la fecha de vencimiento.
|
||||
// Retorna (true, nil) si el contrato pasó de no-pagado a pagado en esta llamada.
|
||||
// Retorna (false, nil) si ya estaba pagado (idempotente).
|
||||
// WHERE pago_confirmado = false garantiza atomicidad ante llamadas concurrentes.
|
||||
func MarcarContratoPagado(contratoID uint) (bool, error) {
|
||||
now := time.Now()
|
||||
|
||||
// Cargar contrato con servicios para calcular nueva fecha de vencimiento
|
||||
@@ -213,12 +215,20 @@ func MarcarContratoPagado(contratoID uint) error {
|
||||
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
|
||||
result := app.Http.Database.DB.Model(&Contrato{}).
|
||||
Where("id = ? AND pago_confirmado = false", contratoID).
|
||||
Updates(map[string]interface{}{
|
||||
"pago_confirmado": true,
|
||||
"fecha_pago": now,
|
||||
"enlace_pago": "",
|
||||
"enlace_pago_link_id": "",
|
||||
})
|
||||
return result.RowsAffected > 0, result.Error
|
||||
}
|
||||
|
||||
// Ya estaba pagado — nada que hacer
|
||||
if c.PagoConfirmado {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
updates := map[string]interface{}{
|
||||
@@ -254,7 +264,10 @@ func MarcarContratoPagado(contratoID uint) error {
|
||||
)
|
||||
}
|
||||
|
||||
return app.Http.Database.DB.Model(&Contrato{}).Where("id = ?", contratoID).Updates(updates).Error
|
||||
result := app.Http.Database.DB.Model(&Contrato{}).
|
||||
Where("id = ? AND pago_confirmado = false", contratoID).
|
||||
Updates(updates)
|
||||
return result.RowsAffected > 0, result.Error
|
||||
}
|
||||
|
||||
// GetEstadoPago devuelve si el contrato tiene pago confirmado (para polling desde el frontend).
|
||||
|
||||
@@ -361,7 +361,7 @@ func TestCicloNotificaciones(t *testing.T) {
|
||||
// ══════════════════════════════════════════════════════════════════════════
|
||||
t.Run("paso2_pago_confirmado", func(t *testing.T) {
|
||||
// 2a. Simular llegada del webhook: marcar contrato como pagado
|
||||
if err := models.MarcarContratoPagado(contrato.ID); err != nil {
|
||||
if _, err := models.MarcarContratoPagado(contrato.ID); err != nil {
|
||||
t.Fatalf("MarcarContratoPagado: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -299,12 +299,6 @@ func EnviarCorreoConfirmacionPago(contratoID uint, pasarela ...string) {
|
||||
}
|
||||
regla := ®las[0]
|
||||
|
||||
// Idempotencia: si ya se envió hoy para esta regla+cliente, no reenviar
|
||||
if models.YaEnviadoHoy(contrato.ClienteID, regla.ID) {
|
||||
log.Printf("[PAGO-CORREO] correo de confirmación ya enviado hoy para cliente %d", contrato.ClienteID)
|
||||
return
|
||||
}
|
||||
|
||||
if err := EnviarNotificacionGrupo(regla, &contrato.Cliente, []models.Contrato{*contrato}, pas); err != nil {
|
||||
log.Printf("[PAGO-CORREO] error enviando correo de confirmación para contrato %d: %v", contratoID, err)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user