Files
soft_usite/rest/controllers/api/pago_externo_controller.go
Lizandro GD e5849549a0 Agrega API de pagos externos: tokens por servicio, atados a pasarela e IP
Permite emitir credenciales (token hasheado + IP/CIDR opcional) desde
/app/pagos-externos para que aplicaciones de terceros pidan cobros a
través de Bold/dLocal/PayPal sin acceso a nada más del sistema:

- POST /api/v1/pagos-externos/solicitar genera el link de cobro real
  usando solo las pasarelas habilitadas para ese servicio.
- Los webhooks existentes de Bold/dLocal/PayPal (firma obligatoria,
  idempotentes) ahora también resuelven referencias "extpay-…" sin
  tocar el flujo de contratos ("contrato-{id}").
- Al confirmarse el pago se notifica por webhook firmado (HMAC) y/o
  Telegram, configurable por servicio.
- CRUD de servicios protegido con SoloAdmin; token y callback_secret
  solo se muestran una vez, en DB se guardan hasheados.
2026-08-04 14:16:13 +00:00

102 lines
3.7 KiB
Go

package controllers
import (
"strings"
"github.com/gofiber/fiber/v2"
"github.com/sujit-baniya/fiber-boilerplate/pkg/models"
"github.com/sujit-baniya/fiber-boilerplate/pkg/services"
)
func servicioPagoDeContexto(c *fiber.Ctx) (*models.ServicioPagoExterno, error) {
servicio, ok := c.Locals("servicio_pago").(*models.ServicioPagoExterno)
if !ok || servicio == nil {
return nil, fiber.NewError(fiber.StatusUnauthorized, "no autenticado")
}
return servicio, nil
}
// PasarelasDisponiblesHandler indica qué pasarelas puede usar el token actual.
// Ruta: GET /api/v1/pagos-externos/pasarelas
func PasarelasDisponiblesHandler(c *fiber.Ctx) error {
servicio, err := servicioPagoDeContexto(c)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": true, "message": err.Error()})
}
return c.JSON(fiber.Map{"pasarelas": servicio.PasarelasList()})
}
// SolicitarPagoExternoHandler crea un cobro nuevo con el token autenticado.
// Ruta: POST /api/v1/pagos-externos/solicitar
func SolicitarPagoExternoHandler(c *fiber.Ctx) error {
servicio, err := servicioPagoDeContexto(c)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": true, "message": err.Error()})
}
var req struct {
ReferenciaExterna string `json:"referencia_externa"`
Pasarela string `json:"pasarela"`
Monto float64 `json:"monto"`
Moneda string `json:"moneda"`
Descripcion string `json:"descripcion"`
ClienteID *uint `json:"cliente_id"`
}
if err := c.BodyParser(&req); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": true, "message": "body inválido"})
}
if strings.TrimSpace(req.ReferenciaExterna) == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": true, "message": "referencia_externa es requerida"})
}
solicitud, err := services.SolicitarPagoExterno(
servicio,
strings.TrimSpace(req.ReferenciaExterna),
strings.ToLower(strings.TrimSpace(req.Pasarela)),
strings.TrimSpace(req.Descripcion),
strings.ToUpper(strings.TrimSpace(req.Moneda)),
req.Monto,
req.ClienteID,
)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": true, "message": err.Error()})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{
"referencia_externa": solicitud.ReferenciaExterna,
"referencia_interna": solicitud.ReferenciaInterna,
"enlace_pago": solicitud.EnlacePago,
"pasarela": solicitud.Pasarela,
"estado": solicitud.Estado,
"monto": solicitud.Monto,
"moneda": solicitud.Moneda,
})
}
// EstadoPagoExternoHandler consulta el estado de un cobro. Solo devuelve datos
// si la solicitud pertenece al mismo servicio autenticado, para que un token
// no pueda enumerar/consultar solicitudes de otro cliente.
// Ruta: GET /api/v1/pagos-externos/:referencia/estado
func EstadoPagoExternoHandler(c *fiber.Ctx) error {
servicio, err := servicioPagoDeContexto(c)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": true, "message": err.Error()})
}
referencia := c.Params("referencia")
solicitud, err := models.GetSolicitudPagoExternaByReferenciaInterna(referencia)
if err != nil || solicitud.ServicioID != servicio.ID {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": true, "message": "solicitud no encontrada"})
}
return c.JSON(fiber.Map{
"referencia_externa": solicitud.ReferenciaExterna,
"referencia_interna": solicitud.ReferenciaInterna,
"estado": solicitud.Estado,
"pasarela": solicitud.Pasarela,
"monto": solicitud.Monto,
"moneda": solicitud.Moneda,
"fecha_pago": solicitud.FechaPago,
})
}