p
This commit is contained in:
+25
-2
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user