diff --git a/migrations/migrate.go b/migrations/migrate.go
index 6a0bf9a..4e53f80 100755
--- a/migrations/migrate.go
+++ b/migrations/migrate.go
@@ -67,6 +67,7 @@ func Migrate() {
// Integraciones SaaS (dispatcher de pagos)
&models.SaasApiConfig{},
&models.SaasDispatchLog{},
+ &models.SaasWebhookInLog{},
// Documentos de clientes
&models.ClienteDocumento{},
// Telegram
diff --git a/pkg/models/saas_api_config.go b/pkg/models/saas_api_config.go
index 740e141..862b118 100644
--- a/pkg/models/saas_api_config.go
+++ b/pkg/models/saas_api_config.go
@@ -20,14 +20,33 @@ type SaasApiConfig struct {
ApiKeyHeader string `json:"api_key_header" gorm:"column:api_key_header"` // ej: "X-API-Key"
ApiKeyValue string `json:"api_key_value" gorm:"column:api_key_value;type:text"` // valor secreto
// PayloadTemplate es un JSON con marcadores que se reemplazarán antes de enviar.
- // Variables disponibles: {{.ContratoID}} {{.Referencia}} {{.Email}} {{.Monto}} {{.Moneda}} {{.SaasID}} {{.SaasSlug}} {{.Fuente}}
+ // Variables disponibles: {{.ContratoID}} {{.Referencia}} {{.Email}} {{.Monto}} {{.Moneda}} {{.SaasID}} {{.SaasSlug}} {{.Fuente}} {{.WebhookToken}}
PayloadTemplate string `json:"payload_template" gorm:"column:payload_template;type:text"`
TimeoutSeg int `json:"timeout_seg" gorm:"column:timeout_seg;default:10"`
Activo bool `json:"activo" gorm:"column:activo;default:true"`
+ // WebhookToken es un token único por integración que identifica las llamadas entrantes
+ // desde el SaaS externo vía POST /webhooks/saas-in/{token}
+ WebhookToken string `json:"webhook_token" gorm:"column:webhook_token;uniqueIndex;size:64"`
}
func (SaasApiConfig) TableName() string { return "saas_api_configs" }
+// ─── SaasWebhookInLog ─────────────────────────────────────────────────────────
+
+// SaasWebhookInLog registra cada llamada entrante desde un SaaS externo
+// identificado por su WebhookToken único.
+type SaasWebhookInLog struct {
+ gorm.Model
+ SaasApiConfigID uint `json:"saas_api_config_id" gorm:"column:saas_api_config_id;index"`
+ Token string `json:"token" gorm:"column:token;size:64;index"`
+ Metodo string `json:"metodo" gorm:"column:metodo;size:10"`
+ IP string `json:"ip" gorm:"column:ip;size:64"`
+ Body string `json:"body" gorm:"column:body;type:text"`
+ Headers string `json:"headers" gorm:"column:headers;type:text"`
+}
+
+func (SaasWebhookInLog) TableName() string { return "saas_webhook_in_logs" }
+
// ─── SaasDispatchLog ──────────────────────────────────────────────────────────
// SaasDispatchLog registra cada intento de notificación a un SaaS externo.
@@ -102,6 +121,22 @@ func DeleteSaasApiConfig(id uint) error {
return app.Http.Database.DB.Delete(&SaasApiConfig{}, id).Error
}
+// GetSaasApiConfigByWebhookToken busca una integración por su token de webhook entrante.
+func GetSaasApiConfigByWebhookToken(token string) (*SaasApiConfig, error) {
+ var item SaasApiConfig
+ if err := app.Http.Database.DB.Preload("SaasProducto").
+ Where("webhook_token = ? AND activo = true", token).
+ First(&item).Error; err != nil {
+ return nil, err
+ }
+ return &item, nil
+}
+
+// SaveSaasWebhookInLog persiste un log de webhook entrante.
+func SaveSaasWebhookInLog(entry *SaasWebhookInLog) error {
+ return app.Http.Database.DB.Create(entry).Error
+}
+
// ─── Queries SaasDispatchLog ──────────────────────────────────────────────────
func SaveSaasDispatchLog(entry *SaasDispatchLog) error {
diff --git a/pkg/services/saas_dispatch_service.go b/pkg/services/saas_dispatch_service.go
index af22625..b1ed2de 100644
--- a/pkg/services/saas_dispatch_service.go
+++ b/pkg/services/saas_dispatch_service.go
@@ -16,14 +16,15 @@ import (
// DispatchPayload contiene las variables disponibles en el PayloadTemplate.
type DispatchPayload struct {
- ContratoID uint
- Referencia string
- Email string
- Monto float64
- Moneda string
- SaasID uint
- SaasSlug string
- Fuente string // dlocal | bold | manual
+ ContratoID uint
+ Referencia string
+ Email string
+ Monto float64
+ Moneda string
+ SaasID uint
+ SaasSlug string
+ Fuente string // dlocal | bold | manual
+ WebhookToken string // token único de la integración (para incluirlo en el payload saliente)
}
// DispatchSaasPaymentNotification notifica a todos los SaaS externos activos
@@ -89,14 +90,15 @@ func DispatchSaasPaymentNotification(contratoID uint, payerEmail, fuente string,
for _, cfg := range configs {
saas := saasMap[cfg.SaasID]
payload := DispatchPayload{
- ContratoID: contratoID,
- Referencia: referencia,
- Email: payerEmail,
- Monto: monto,
- Moneda: moneda,
- SaasID: saas.ID,
- SaasSlug: saas.Slug,
- Fuente: fuente,
+ ContratoID: contratoID,
+ Referencia: referencia,
+ Email: payerEmail,
+ Monto: monto,
+ Moneda: moneda,
+ SaasID: saas.ID,
+ SaasSlug: saas.Slug,
+ Fuente: fuente,
+ WebhookToken: cfg.WebhookToken,
}
dispatchOne(cfg, payload)
}
diff --git a/resources/views/saas_api.html b/resources/views/saas_api.html
index e94db8c..c021df8 100644
--- a/resources/views/saas_api.html
+++ b/resources/views/saas_api.html
@@ -208,7 +208,7 @@
Variables disponibles:
{{.ContratoID}} {{.Referencia}} {{.Email}}
{{.Monto}} {{.Moneda}} {{.SaasID}}
- {{.SaasSlug}} {{.Fuente}}
+ {{.SaasSlug}} {{.Fuente}} {{.WebhookToken}}
🔗 URL de webhook entrante (para que el SaaS llame a este sistema)
+
+
+ Acepta POST y GET. El token identifica a esta integración de forma única.
+