This commit is contained in:
Lizandro Guarnizo
2026-05-01 13:44:37 -05:00
parent be28fea806
commit 3f6eb7dddc
7 changed files with 191 additions and 12 deletions
+25 -2
View File
@@ -22,8 +22,11 @@ type Contrato struct {
// Enlace de pago Bold: único por ciclo de pago.
// Se reutiliza en múltiples notificaciones del mismo ciclo.
// Se anula (vacía) cuando SALE_APPROVED llega y se registra el pago.
EnlacePago string `json:"enlace_pago" gorm:"column:enlace_pago;type:text"`
EnlacePagoLinkID string `json:"enlace_pago_link_id" gorm:"column:enlace_pago_link_id;type:varchar(64)"`
EnlacePago string `json:"enlace_pago" gorm:"column:enlace_pago;type:text"`
EnlacePagoLinkID string `json:"enlace_pago_link_id" gorm:"column:enlace_pago_link_id;type:varchar(64)"`
// Confirmación de pago
PagoConfirmado bool `json:"pago_confirmado" gorm:"column:pago_confirmado;default:false"`
FechaPago *time.Time `json:"fecha_pago" gorm:"column:fecha_pago"`
}
func (Contrato) TableName() string { return "contratos" }
@@ -156,3 +159,23 @@ func LimpiarEnlacePago(contratoID uint) error {
"enlace_pago_link_id": "",
}).Error
}
// MarcarContratoPagado marca el contrato como pagado al recibir confirmación de la pasarela.
func MarcarContratoPagado(contratoID uint) error {
now := time.Now()
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
}
// GetEstadoPago devuelve si el contrato tiene pago confirmado (para polling desde el frontend).
func GetEstadoPago(contratoID uint) (bool, *time.Time, error) {
var c Contrato
if err := app.Http.Database.DB.Select("pago_confirmado", "fecha_pago").First(&c, contratoID).Error; err != nil {
return false, nil, err
}
return c.PagoConfirmado, c.FechaPago, nil
}
+24 -5
View File
@@ -1,6 +1,7 @@
package services
import (
"context"
"database/sql"
"fmt"
"strings"
@@ -14,6 +15,9 @@ import (
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
)
// connTimeout es el tiempo máximo para establecer una conexión a la DB externa.
const connTimeout = 8 * time.Second
// QueryResult contiene el resultado de una consulta SQL.
type QueryResult struct {
Columns []string `json:"columns"`
@@ -39,16 +43,16 @@ func openDynamicDB(c models.ConxDb) (*sql.DB, error) {
switch {
case strings.Contains(driver, "postgres"):
driverName = "postgres"
dsn = fmt.Sprintf("host=%s port=%s user=%s password=%s sslmode=disable", host, port, user, pass)
dsn = fmt.Sprintf("host=%s port=%s user=%s password=%s sslmode=disable connect_timeout=8", host, port, user, pass)
case strings.Contains(driver, "mysql") || strings.Contains(driver, "mariadb"):
driverName = "mysql"
dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/", user, pass, host, port)
dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/?timeout=8s&readTimeout=20s&writeTimeout=20s", user, pass, host, port)
case strings.Contains(driver, "sqlite"):
driverName = "sqlite3"
dsn = host // para sqlite el host es la ruta del archivo
case strings.Contains(driver, "sqlserver") || strings.Contains(driver, "mssql"):
driverName = "sqlserver"
dsn = fmt.Sprintf("sqlserver://%s:%s@%s:%s", user, pass, host, port)
dsn = fmt.Sprintf("sqlserver://%s:%s@%s:%s?dial+timeout=8", user, pass, host, port)
default:
return nil, fmt.Errorf("driver no soportado: %s", driver)
}
@@ -59,6 +63,13 @@ func openDynamicDB(c models.ConxDb) (*sql.DB, error) {
}
db.SetConnMaxLifetime(30 * time.Second)
db.SetMaxOpenConns(2)
// Verificar conectividad inmediatamente para fallar rápido en lugar de bloquear al hacer la primera query
ctx, cancel := context.WithTimeout(context.Background(), connTimeout)
defer cancel()
if err := db.PingContext(ctx); err != nil {
db.Close()
return nil, fmt.Errorf("no se pudo conectar al servidor de base de datos: %w", err)
}
return db, nil
}
@@ -169,7 +180,9 @@ func ListDatabases(conx models.ConxDb) ([]string, error) {
return []string{"main"}, nil
}
rows, err := db.Query(query)
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, query)
if err != nil {
return nil, err
}
@@ -253,13 +266,19 @@ func openDynamicDBWithName(c models.ConxDb, dbName string) (*sql.DB, error) {
var dsn string
switch {
case strings.Contains(driver, "postgres"):
dsn = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", host, port, user, pass, dbName)
dsn = fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable connect_timeout=8", host, port, user, pass, dbName)
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, err
}
db.SetConnMaxLifetime(30 * time.Second)
db.SetMaxOpenConns(2)
ctx, cancel := context.WithTimeout(context.Background(), connTimeout)
defer cancel()
if err := db.PingContext(ctx); err != nil {
db.Close()
return nil, fmt.Errorf("no se pudo conectar a la base de datos '%s': %w", dbName, err)
}
return db, nil
default:
return openDynamicDB(c)
+1 -1
View File
@@ -291,7 +291,7 @@ func obtenerEnlaceBold(contrato *models.Contrato) string {
},
Description: desc,
Reference: fmt.Sprintf("contrato-%d", contrato.ID),
CallbackURL: cfg.CallbackUrl,
CallbackURL: cfg.CallbackUrl + fmt.Sprintf("?ref=contrato-%d", contrato.ID),
}
result, err := CreateBoldPaymentLink(cfg, req)