up
This commit is contained in:
@@ -69,6 +69,7 @@ func main() {
|
|||||||
migrations.SeedSaas()
|
migrations.SeedSaas()
|
||||||
migrations.SeedServidores()
|
migrations.SeedServidores()
|
||||||
migrations.SeedAdministracion()
|
migrations.SeedAdministracion()
|
||||||
|
migrations.SeedPlantillasBase()
|
||||||
// Iniciar cron de vencimientos
|
// Iniciar cron de vencimientos
|
||||||
services.IniciarCron()
|
services.IniciarCron()
|
||||||
defer services.DetenerCron()
|
defer services.DetenerCron()
|
||||||
|
|||||||
@@ -105,6 +105,18 @@ func GetBoldWebhookLogs(limit int) ([]BoldWebhookLog, error) {
|
|||||||
return logs, nil
|
return logs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBoldWebhookLogsByRef devuelve los webhooks recibidos para una referencia dada.
|
||||||
|
func GetBoldWebhookLogsByRef(referencia string) ([]BoldWebhookLog, error) {
|
||||||
|
var logs []BoldWebhookLog
|
||||||
|
if err := app.Http.Database.DB.
|
||||||
|
Where("referencia = ?", referencia).
|
||||||
|
Order("id DESC").
|
||||||
|
Find(&logs).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetBoldWebhookLogsPaginated devuelve los logs con paginación y filtro por tipo.
|
// GetBoldWebhookLogsPaginated devuelve los logs con paginación y filtro por tipo.
|
||||||
// El valor especial "APROBADOS" incluye tanto SALE_APPROVED como API_CHECK.
|
// El valor especial "APROBADOS" incluye tanto SALE_APPROVED como API_CHECK.
|
||||||
func GetBoldWebhookLogsPaginated(page, limit int, tipo string) ([]BoldWebhookLog, int64, error) {
|
func GetBoldWebhookLogsPaginated(page, limit int, tipo string) ([]BoldWebhookLog, int64, error) {
|
||||||
@@ -161,8 +173,9 @@ type BoldCallbackLog struct {
|
|||||||
Params string `json:"params" gorm:"column:params;type:text"`
|
Params string `json:"params" gorm:"column:params;type:text"`
|
||||||
// Estado: pendiente | pagado | fallido | revertido
|
// Estado: pendiente | pagado | fallido | revertido
|
||||||
Estado string `json:"estado" gorm:"column:estado;type:varchar(20);default:'pendiente'"`
|
Estado string `json:"estado" gorm:"column:estado;type:varchar(20);default:'pendiente'"`
|
||||||
// Datos del cliente si están disponibles
|
// Datos del pago (enriquecidos desde el webhook cuando llega)
|
||||||
PayerEmail string `json:"payer_email" gorm:"column:payer_email;type:varchar(255)"`
|
PayerEmail string `json:"payer_email" gorm:"column:payer_email;type:varchar(255)"`
|
||||||
|
Monto int64 `json:"monto" gorm:"column:monto;default:0"`
|
||||||
// Datos de red (para análisis)
|
// Datos de red (para análisis)
|
||||||
IP string `json:"ip" gorm:"column:ip;type:varchar(45)"`
|
IP string `json:"ip" gorm:"column:ip;type:varchar(45)"`
|
||||||
UserAgent string `json:"user_agent" gorm:"column:user_agent;type:text"`
|
UserAgent string `json:"user_agent" gorm:"column:user_agent;type:text"`
|
||||||
@@ -186,6 +199,27 @@ func UpdateBoldCallbackEstado(referencia, estado string) {
|
|||||||
Update("estado", estado)
|
Update("estado", estado)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EnrichBoldCallbackLog enriquece los intentos de pago con email y monto del webhook.
|
||||||
|
// Se llama cuando llega SALE_APPROVED que sí tiene esos datos.
|
||||||
|
func EnrichBoldCallbackLog(referencia, payerEmail string, monto int64) {
|
||||||
|
if referencia == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
updates := map[string]interface{}{}
|
||||||
|
if payerEmail != "" {
|
||||||
|
updates["payer_email"] = payerEmail
|
||||||
|
}
|
||||||
|
if monto > 0 {
|
||||||
|
updates["monto"] = monto
|
||||||
|
}
|
||||||
|
if len(updates) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.Http.Database.DB.Model(&BoldCallbackLog{}).
|
||||||
|
Where("referencia = ?", referencia).
|
||||||
|
Updates(updates)
|
||||||
|
}
|
||||||
|
|
||||||
// GetBoldCallbackLogByID carga un intento de pago por su PK.
|
// GetBoldCallbackLogByID carga un intento de pago por su PK.
|
||||||
func GetBoldCallbackLogByID(id uint, out *BoldCallbackLog) error {
|
func GetBoldCallbackLogByID(id uint, out *BoldCallbackLog) error {
|
||||||
return app.Http.Database.DB.First(out, id).Error
|
return app.Http.Database.DB.First(out, id).Error
|
||||||
|
|||||||
@@ -408,7 +408,7 @@
|
|||||||
<tr class="bg-gray-50 text-left border-b">
|
<tr class="bg-gray-50 text-left border-b">
|
||||||
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Estado</th>
|
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Estado</th>
|
||||||
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Referencia</th>
|
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Referencia</th>
|
||||||
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Payment Link</th>
|
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Monto</th>
|
||||||
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Email</th>
|
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Email</th>
|
||||||
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">IP</th>
|
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">IP</th>
|
||||||
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Fecha intento</th>
|
<th class="py-3 px-4 font-semibold text-gray-500 uppercase tracking-wide text-[10px]">Fecha intento</th>
|
||||||
@@ -429,7 +429,7 @@
|
|||||||
x-text="cb.estado || 'pendiente'"></span>
|
x-text="cb.estado || 'pendiente'"></span>
|
||||||
</td>
|
</td>
|
||||||
<td class="py-3 px-4 font-mono text-gray-600" x-text="cb.referencia || '—'"></td>
|
<td class="py-3 px-4 font-mono text-gray-600" x-text="cb.referencia || '—'"></td>
|
||||||
<td class="py-3 px-4 font-mono text-gray-500" x-text="(cb.payment_link || '—').slice(0,12) + ((cb.payment_link||'').length > 12 ? '…' : '')"></td>
|
<td class="py-3 px-4 font-semibold text-gray-800" x-text="cb.monto ? '$ ' + Number(cb.monto).toLocaleString('es-CO') : '—'"></td>
|
||||||
<td class="py-3 px-4 text-gray-600" x-text="cb.payer_email || '—'"></td>
|
<td class="py-3 px-4 text-gray-600" x-text="cb.payer_email || '—'"></td>
|
||||||
<td class="py-3 px-4 font-mono text-gray-400" x-text="cb.ip || '—'"></td>
|
<td class="py-3 px-4 font-mono text-gray-400" x-text="cb.ip || '—'"></td>
|
||||||
<td class="py-3 px-4 text-gray-400" x-text="cb.CreatedAt ? new Date(cb.CreatedAt).toLocaleString('es-CO',{dateStyle:'short',timeStyle:'short'}) : '—'"></td>
|
<td class="py-3 px-4 text-gray-400" x-text="cb.CreatedAt ? new Date(cb.CreatedAt).toLocaleString('es-CO',{dateStyle:'short',timeStyle:'short'}) : '—'"></td>
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ func BoldWebhook(c *fiber.Ctx) error {
|
|||||||
if paymentID != "" {
|
if paymentID != "" {
|
||||||
models.UpdateBoldCallbackEstado(paymentID, estimado)
|
models.UpdateBoldCallbackEstado(paymentID, estimado)
|
||||||
}
|
}
|
||||||
|
// Enriquecer con email y monto del webhook (solo cuando hay datos reales)
|
||||||
|
if estimado == "pagado" {
|
||||||
|
models.EnrichBoldCallbackLog(referencia, payerEmail, monto)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solo continuar lógica de negocio para SALE_APPROVED ─────────────────────
|
// Solo continuar lógica de negocio para SALE_APPROVED ─────────────────────
|
||||||
|
|||||||
@@ -63,15 +63,34 @@ func PagoExitosoPage(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// Bold confirmó el pago directamente en la URL de retorno
|
// Bold confirmó el pago directamente en la URL de retorno
|
||||||
if boldTxStatus == "approved" {
|
if boldTxStatus == "approved" {
|
||||||
|
// IP real (detrás de nginx)
|
||||||
|
ip := c.Get("X-Real-IP")
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.Get("X-Forwarded-For")
|
||||||
|
}
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.IP()
|
||||||
|
}
|
||||||
_ = models.SaveBoldCallbackLog(models.BoldCallbackLog{
|
_ = models.SaveBoldCallbackLog(models.BoldCallbackLog{
|
||||||
Referencia: ref, PaymentLink: paymentLink, Params: paramsJSON,
|
Referencia: ref, PaymentLink: paymentLink, Params: paramsJSON,
|
||||||
Estado: "pagado", IP: c.IP(), UserAgent: string(c.Request().Header.UserAgent()),
|
Estado: "pagado", IP: ip, UserAgent: string(c.Request().Header.UserAgent()),
|
||||||
})
|
})
|
||||||
// Marcar contrato si aún no está confirmado
|
// Marcar contrato si aún no está confirmado
|
||||||
var contratoID uint
|
var contratoID uint
|
||||||
if _, err := fmt.Sscanf(ref, "contrato-%d", &contratoID); err == nil && contratoID > 0 {
|
if _, err := fmt.Sscanf(ref, "contrato-%d", &contratoID); err == nil && contratoID > 0 {
|
||||||
_ = models.MarcarContratoPagado(contratoID)
|
_ = models.MarcarContratoPagado(contratoID)
|
||||||
go services.EnviarCorreoConfirmacionPago(contratoID, "bold")
|
go services.EnviarCorreoConfirmacionPago(contratoID, "bold")
|
||||||
|
// Intentar enriquecer con datos del webhook log si ya llegó
|
||||||
|
go func() {
|
||||||
|
if wLogs, err := models.GetBoldWebhookLogsByRef(ref); err == nil {
|
||||||
|
for _, wl := range wLogs {
|
||||||
|
if wl.PayerEmail != "" || wl.Monto > 0 {
|
||||||
|
models.EnrichBoldCallbackLog(ref, wl.PayerEmail, wl.Monto)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
estado, fechaPago := verificarPago(ref)
|
estado, fechaPago := verificarPago(ref)
|
||||||
return c.Render("pago_exitoso", fiber.Map{
|
return c.Render("pago_exitoso", fiber.Map{
|
||||||
@@ -81,12 +100,19 @@ func PagoExitosoPage(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
// Registrar el intento de pago en la tabla de callbacks
|
// Registrar el intento de pago en la tabla de callbacks
|
||||||
if ref != "" || paymentLink != "" {
|
if ref != "" || paymentLink != "" {
|
||||||
|
ip := c.Get("X-Real-IP")
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.Get("X-Forwarded-For")
|
||||||
|
}
|
||||||
|
if ip == "" {
|
||||||
|
ip = c.IP()
|
||||||
|
}
|
||||||
entry := models.BoldCallbackLog{
|
entry := models.BoldCallbackLog{
|
||||||
Referencia: ref,
|
Referencia: ref,
|
||||||
PaymentLink: paymentLink,
|
PaymentLink: paymentLink,
|
||||||
Params: paramsJSON,
|
Params: paramsJSON,
|
||||||
Estado: "pendiente",
|
Estado: "pendiente",
|
||||||
IP: c.IP(),
|
IP: ip,
|
||||||
UserAgent: string(c.Request().Header.UserAgent()),
|
UserAgent: string(c.Request().Header.UserAgent()),
|
||||||
}
|
}
|
||||||
_ = models.SaveBoldCallbackLog(entry)
|
_ = models.SaveBoldCallbackLog(entry)
|
||||||
|
|||||||
Reference in New Issue
Block a user