package models import ( "github.com/sujit-baniya/fiber-boilerplate/app" "gorm.io/gorm" ) // BoldConfig almacena las credenciales de Bold (pasarela de pagos colombiana). // Solo un registro puede estar activo a la vez. type BoldConfig struct { gorm.Model // Claves de producción ApiKeyProd string `json:"api_key_prod" gorm:"column:api_key_prod;type:text"` SecretKeyProd string `json:"secret_key_prod" gorm:"column:secret_key_prod;type:text"` // Claves de prueba / test ApiKeyTest string `json:"api_key_test" gorm:"column:api_key_test;type:text"` // En modo test la secret key es cadena vacía según la doc oficial SecretKeyTest string `json:"secret_key_test" gorm:"column:secret_key_test;type:text"` // Modo activo: "test" | "production" Modo string `json:"modo" gorm:"column:modo;default:'test'"` // URL a la que Bold redirige al usuario tras el pago CallbackUrl string `json:"callback_url" gorm:"column:callback_url;type:text"` // Nota interna Nota string `json:"nota" gorm:"column:nota;type:text"` Activo bool `json:"activo" gorm:"column:activo;default:true"` } func (BoldConfig) TableName() string { return "bold_config" } // GetBoldConfig retorna la configuración activa. func GetBoldConfig() (*BoldConfig, error) { var item BoldConfig if err := app.Http.Database.DB.Where("activo = ?", true).Order("id DESC").First(&item).Error; err != nil { return nil, err } return &item, nil } // SaveBoldConfig desactiva la config previa y guarda la nueva (o actualiza si ya tiene ID). func SaveBoldConfig(s BoldConfig) error { app.Http.Database.DB.Model(&BoldConfig{}). Where("activo = ?", true). Update("activo", false) s.Activo = true if s.ID > 0 { return app.Http.Database.DB.Model(&s).Updates(map[string]interface{}{ "api_key_prod": s.ApiKeyProd, "secret_key_prod": s.SecretKeyProd, "api_key_test": s.ApiKeyTest, "secret_key_test": s.SecretKeyTest, "modo": s.Modo, "callback_url": s.CallbackUrl, "nota": s.Nota, "activo": true, }).Error } return app.Http.Database.DB.Create(&s).Error } // ─── Webhook log para idempotencia ─────────────────────────────────────────── // BoldWebhookLog registra cada notificación recibida de Bold. // La restricción UNIQUE sobre notification_id evita procesar duplicados. type BoldWebhookLog struct { gorm.Model NotificationID string `json:"notification_id" gorm:"column:notification_id;uniqueIndex;type:varchar(64);not null"` Tipo string `json:"tipo" gorm:"column:tipo;type:varchar(30)"` PaymentID string `json:"payment_id" gorm:"column:payment_id;type:varchar(64)"` Referencia string `json:"referencia" gorm:"column:referencia;type:varchar(120)"` PayerEmail string `json:"payer_email" gorm:"column:payer_email;type:varchar(255)"` Monto int64 `json:"monto" gorm:"column:monto"` Procesado bool `json:"procesado" gorm:"column:procesado;default:false"` // Body crudo del webhook para auditoría Raw string `json:"raw" gorm:"column:raw;type:text"` } func (BoldWebhookLog) TableName() string { return "bold_webhook_log" } // IsBoldNotificationDuplicate intenta insertar el log. Devuelve true si ya existía. func IsBoldNotificationDuplicate(notificationID string) bool { result := app.Http.Database.DB. Where("notification_id = ?", notificationID). First(&BoldWebhookLog{}) return result.Error == nil // nil error = registro encontrado = duplicado } // SaveBoldWebhookLog guarda el log del webhook. func SaveBoldWebhookLog(entry BoldWebhookLog) error { return app.Http.Database.DB.Create(&entry).Error } // MarkBoldWebhookProcessed marca la notificación como procesada. func MarkBoldWebhookProcessed(notificationID string) { app.Http.Database.DB.Model(&BoldWebhookLog{}). Where("notification_id = ?", notificationID). Update("procesado", true) } // GetBoldWebhookLogs devuelve los últimos N registros del log. func GetBoldWebhookLogs(limit int) ([]BoldWebhookLog, error) { var logs []BoldWebhookLog if err := app.Http.Database.DB.Order("id DESC").Limit(limit).Find(&logs).Error; err != nil { return nil, err } return logs, nil } // GetBoldWebhookLogsPaginated devuelve los logs con paginación y filtro por tipo. func GetBoldWebhookLogsPaginated(page, limit int, tipo string) ([]BoldWebhookLog, int64, error) { var logs []BoldWebhookLog var total int64 db := app.Http.Database.DB.Model(&BoldWebhookLog{}) if tipo != "" && tipo != "TODOS" { db = db.Where("tipo = ?", tipo) } db.Count(&total) offset := (page - 1) * limit if err := db.Order("id DESC").Limit(limit).Offset(offset).Find(&logs).Error; err != nil { return nil, 0, err } return logs, total, nil } // ─── Callback log (intentos de pago) ───────────────────────────────────────── // BoldCallbackLog registra cada visita a la URL de retorno de Bold. // Esto captura usuarios que iniciaron el proceso de pago (llegaron al checkout) // pero pueden haber abandonado, fallado o completado el pago. type BoldCallbackLog struct { gorm.Model // Referencia principal recibida de Bold (bold-order-id / payment_link / reference) Referencia string `json:"referencia" gorm:"column:referencia;type:varchar(120);index"` PaymentLink string `json:"payment_link" gorm:"column:payment_link;type:varchar(80)"` // Todos los parámetros GET recibidos en JSON (para depuración) Params string `json:"params" gorm:"column:params;type:text"` // Estado: pendiente | pagado | fallido | revertido Estado string `json:"estado" gorm:"column:estado;type:varchar(20);default:'pendiente'"` // Datos del cliente si están disponibles PayerEmail string `json:"payer_email" gorm:"column:payer_email;type:varchar(255)"` // Datos de red (para análisis) IP string `json:"ip" gorm:"column:ip;type:varchar(45)"` UserAgent string `json:"user_agent" gorm:"column:user_agent;type:text"` } func (BoldCallbackLog) TableName() string { return "bold_callback_log" } // SaveBoldCallbackLog guarda un registro de intento de pago. func SaveBoldCallbackLog(entry BoldCallbackLog) error { return app.Http.Database.DB.Create(&entry).Error } // UpdateBoldCallbackEstado actualiza el estado de los intentos que coincidan con la referencia. // Se llama desde el webhook cuando llega un evento SALE_APPROVED, SALE_REJECTED, etc. func UpdateBoldCallbackEstado(referencia, estado string) { if referencia == "" { return } app.Http.Database.DB.Model(&BoldCallbackLog{}). Where("referencia = ? AND estado = 'pendiente'", referencia). Update("estado", estado) } // GetBoldCallbackLogsPaginated devuelve los intentos de pago con paginación y filtro. func GetBoldCallbackLogsPaginated(page, limit int, estado string) ([]BoldCallbackLog, int64, error) { var logs []BoldCallbackLog var total int64 db := app.Http.Database.DB.Model(&BoldCallbackLog{}) if estado != "" && estado != "TODOS" { db = db.Where("estado = ?", estado) } db.Count(&total) offset := (page - 1) * limit if err := db.Order("id DESC").Limit(limit).Offset(offset).Find(&logs).Error; err != nil { return nil, 0, err } return logs, total, nil }