up
This commit is contained in:
@@ -60,6 +60,7 @@ func Migrate() {
|
||||
&models.BoldWebhookLog{},
|
||||
&models.BoldCallbackLog{},
|
||||
&models.DlocalPaymentLog{},
|
||||
&models.PaypalConfig{},
|
||||
// Documentación por SaaS
|
||||
&models.SaasProducto{},
|
||||
&models.DocCategoria{},
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/sujit-baniya/fiber-boilerplate/app"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// PaypalConfig almacena las credenciales de PayPal.
|
||||
// Solo un registro activo a la vez.
|
||||
type PaypalConfig struct {
|
||||
gorm.Model
|
||||
// Credenciales Sandbox
|
||||
ClientIDSandbox string `json:"client_id_sandbox" gorm:"column:client_id_sandbox;type:text"`
|
||||
ClientSecretSandbox string `json:"client_secret_sandbox" gorm:"column:client_secret_sandbox;type:text"`
|
||||
// Credenciales Producción
|
||||
ClientIDProd string `json:"client_id_prod" gorm:"column:client_id_prod;type:text"`
|
||||
ClientSecretProd string `json:"client_secret_prod" gorm:"column:client_secret_prod;type:text"`
|
||||
// Modo activo: "sandbox" | "live"
|
||||
Modo string `json:"modo" gorm:"column:modo;default:'sandbox'"`
|
||||
// Webhook ID para verificación de firmas
|
||||
WebhookID string `json:"webhook_id" gorm:"column:webhook_id;type:text"`
|
||||
// URLs de retorno
|
||||
ReturnURL string `json:"return_url" gorm:"column:return_url;type:text"`
|
||||
CancelURL string `json:"cancel_url" gorm:"column:cancel_url;type:text"`
|
||||
// Nota interna
|
||||
Nota string `json:"nota" gorm:"column:nota;type:text"`
|
||||
Activo bool `json:"activo" gorm:"column:activo;default:true"`
|
||||
}
|
||||
|
||||
func (PaypalConfig) TableName() string { return "paypal_config" }
|
||||
|
||||
// GetPaypalConfig retorna la configuración activa.
|
||||
func GetPaypalConfig() (*PaypalConfig, error) {
|
||||
var item PaypalConfig
|
||||
if err := app.Http.Database.DB.Where("activo = ?", true).Order("id DESC").First(&item).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
// SavePaypalConfig desactiva la config previa y guarda la nueva (o actualiza si ya tiene ID).
|
||||
func SavePaypalConfig(s PaypalConfig) error {
|
||||
app.Http.Database.DB.Model(&PaypalConfig{}).
|
||||
Where("activo = ?", true).
|
||||
Update("activo", false)
|
||||
s.Activo = true
|
||||
if s.ID > 0 {
|
||||
return app.Http.Database.DB.Model(&s).Updates(map[string]interface{}{
|
||||
"client_id_sandbox": s.ClientIDSandbox,
|
||||
"client_secret_sandbox": s.ClientSecretSandbox,
|
||||
"client_id_prod": s.ClientIDProd,
|
||||
"client_secret_prod": s.ClientSecretProd,
|
||||
"modo": s.Modo,
|
||||
"webhook_id": s.WebhookID,
|
||||
"return_url": s.ReturnURL,
|
||||
"cancel_url": s.CancelURL,
|
||||
"nota": s.Nota,
|
||||
"activo": true,
|
||||
}).Error
|
||||
}
|
||||
return app.Http.Database.DB.Create(&s).Error
|
||||
}
|
||||
@@ -37,6 +37,12 @@
|
||||
:class="dlocalModo === 'prod' ? 'bg-green-500' : 'bg-yellow-500'"></span>
|
||||
dLocal — <span x-text="dlocalModo === 'prod' ? 'Producción' : 'Desarrollo'"></span>
|
||||
</span>
|
||||
<span class="px-3 py-1 rounded-full text-xs font-semibold flex items-center gap-1"
|
||||
:class="paypalModo === 'live' ? 'bg-green-100 text-green-700' : 'bg-yellow-100 text-yellow-700'">
|
||||
<span class="w-2 h-2 rounded-full inline-block"
|
||||
:class="paypalModo === 'live' ? 'bg-green-500' : 'bg-yellow-500'"></span>
|
||||
PayPal — <span x-text="paypalModo === 'live' ? 'Producción' : 'Sandbox'"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,6 +66,15 @@
|
||||
</svg>
|
||||
dLocal
|
||||
</button>
|
||||
<button @click="activeTab = 'paypal'"
|
||||
:class="activeTab === 'paypal' ? 'border-b-2 border-[#8eb02f] text-[#6d8c24] font-semibold' : 'text-gray-500 hover:text-gray-700'"
|
||||
class="pb-2 transition-colors flex items-center gap-2">
|
||||
<!-- PayPal icon -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.566-5.946.36-1.847.174-3.388-.778-4.471z"/>
|
||||
</svg>
|
||||
PayPal
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -475,6 +490,196 @@
|
||||
|
||||
</div><!-- /TAB BOLD -->
|
||||
|
||||
<!-- ═══════════════════════════════════════════════════════════════════ -->
|
||||
<!-- TAB: PAYPAL -->
|
||||
<!-- ═══════════════════════════════════════════════════════════════════ -->
|
||||
<div x-show="activeTab === 'paypal'" x-transition>
|
||||
<div class="grid lg:grid-cols-2 gap-6">
|
||||
|
||||
<!-- Formulario de credenciales PayPal -->
|
||||
<div class="border border-gray-200 rounded-xl p-5">
|
||||
<h2 class="text-base font-semibold mb-1 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-[#003087]" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M7.076 21.337H2.47a.641.641 0 0 1-.633-.74L4.944.901C5.026.382 5.474 0 5.998 0h7.46c2.57 0 4.578.543 5.69 1.81 1.01 1.15 1.304 2.42 1.012 4.287-.023.143-.047.288-.077.437-.983 5.05-4.349 6.797-8.647 6.797h-2.19c-.524 0-.968.382-1.05.9l-1.12 7.106zm14.146-14.42a3.35 3.35 0 0 0-.607-.541c-.013.076-.026.175-.041.254-.93 4.778-4.005 7.201-9.138 7.201h-2.19a.563.563 0 0 0-.556.479l-1.187 7.527h-.506l-.24 1.516a.56.56 0 0 0 .554.647h3.882c.46 0 .85-.334.922-.788.06-.26.76-4.852.816-5.09a.932.932 0 0 1 .923-.788h.58c3.76 0 6.705-1.528 7.566-5.946.36-1.847.174-3.388-.778-4.471z"/>
|
||||
</svg>
|
||||
Credenciales PayPal
|
||||
</h2>
|
||||
<p class="text-xs text-gray-500 mb-4">Panel PayPal → Apps & Credentials → Create App</p>
|
||||
|
||||
<!-- Selector de entorno -->
|
||||
<div class="mb-5">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1.5">Entorno activo</label>
|
||||
<div class="flex gap-3">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" x-model="paypalModo" value="sandbox" class="accent-[#8eb02f]"/>
|
||||
<span class="text-sm px-2 py-0.5 rounded bg-yellow-50 text-yellow-700 font-medium">Sandbox</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" x-model="paypalModo" value="live" class="accent-[#8eb02f]"/>
|
||||
<span class="text-sm px-2 py-0.5 rounded bg-green-50 text-green-700 font-medium">Producción (Live)</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sandbox -->
|
||||
<div class="mb-5 p-4 rounded-lg bg-yellow-50 border border-yellow-200">
|
||||
<p class="text-xs font-semibold text-yellow-700 mb-3 flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z"/>
|
||||
</svg>
|
||||
Sandbox
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Client ID (sandbox)</label>
|
||||
<input x-model="paypal.client_id_sandbox" type="text" placeholder="AXxx..." class="ui-input text-xs font-mono" autocomplete="off"/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Client Secret (sandbox)</label>
|
||||
<input x-model="paypal.client_secret_sandbox" type="password" placeholder="EHxx..." class="ui-input text-xs font-mono" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Producción -->
|
||||
<div class="mb-5 p-4 rounded-lg bg-green-50 border border-green-200">
|
||||
<p class="text-xs font-semibold text-green-700 mb-3 flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
||||
</svg>
|
||||
Producción
|
||||
</p>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Client ID (producción)</label>
|
||||
<input x-model="paypal.client_id_prod" type="text" placeholder="AXxx..." class="ui-input text-xs font-mono" autocomplete="off"/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Client Secret (producción)</label>
|
||||
<input x-model="paypal.client_secret_prod" type="password" placeholder="EHxx..." class="ui-input text-xs font-mono" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Webhook ID -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">
|
||||
Webhook ID
|
||||
<span class="text-gray-400 font-normal">(para verificación de firma)</span>
|
||||
</label>
|
||||
<input x-model="paypal.webhook_id" type="text" placeholder="Ej: 8PT597110X687430N" class="ui-input text-xs font-mono" autocomplete="off"/>
|
||||
</div>
|
||||
|
||||
<!-- URLs -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Return URL</label>
|
||||
<input x-model="paypal.return_url" type="url" placeholder="https://tudominio.com/pago-exitoso" class="ui-input text-xs"/>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Cancel URL</label>
|
||||
<input x-model="paypal.cancel_url" type="url" placeholder="https://tudominio.com/pago-cancelado" class="ui-input text-xs"/>
|
||||
</div>
|
||||
|
||||
<!-- Nota -->
|
||||
<div class="mb-5">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Nota interna</label>
|
||||
<input x-model="paypal.nota" type="text" placeholder="Opcional" class="ui-input text-xs"/>
|
||||
</div>
|
||||
|
||||
<button @click="savePaypal()"
|
||||
class="w-full py-2.5 rounded-xl text-sm font-semibold text-white transition-all"
|
||||
style="background-color:#003087"
|
||||
onmouseover="this.style.backgroundColor='#001f5c'"
|
||||
onmouseout="this.style.backgroundColor='#003087'">
|
||||
Guardar configuración PayPal
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info / Guía PayPal -->
|
||||
<div class="space-y-4">
|
||||
|
||||
<!-- Estado actual -->
|
||||
<div class="border border-gray-200 rounded-xl p-5">
|
||||
<h3 class="text-sm font-semibold mb-3 text-gray-700">Estado actual</h3>
|
||||
<div class="space-y-2 text-xs">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Entorno</span>
|
||||
<span class="font-semibold"
|
||||
:class="paypalModo === 'live' ? 'text-green-600' : 'text-yellow-600'"
|
||||
x-text="paypalModo === 'live' ? 'Producción 🟢' : 'Sandbox 🟡'"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Client ID (activo)</span>
|
||||
<span class="font-mono text-gray-700"
|
||||
x-text="paypalModo === 'live'
|
||||
? (paypal.client_id_prod ? paypal.client_id_prod.slice(0,8)+'...' : '—')
|
||||
: (paypal.client_id_sandbox ? paypal.client_id_sandbox.slice(0,8)+'...' : '—')"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500">Webhook ID</span>
|
||||
<span class="font-mono text-gray-700 truncate max-w-[180px]" x-text="paypal.webhook_id || '—'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Webhook URL -->
|
||||
<div class="border border-gray-200 rounded-xl p-5">
|
||||
<h3 class="text-sm font-semibold mb-2 flex items-center gap-2 text-gray-700">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-[#8eb02f]" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
|
||||
</svg>
|
||||
URL del Webhook
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500 mb-3">Registra esta URL en PayPal → Apps → Webhooks</p>
|
||||
<div class="flex items-center gap-2 bg-gray-50 border border-gray-200 rounded-lg px-3 py-2">
|
||||
<span class="text-xs font-mono text-gray-700 flex-1 truncate">/webhooks/paypal</span>
|
||||
<button @click="copyPaypalWebhook()" title="Copiar URL"
|
||||
class="text-[#8eb02f] hover:text-[#6d8c24] flex-shrink-0">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Eventos soportados -->
|
||||
<div class="border border-gray-200 rounded-xl p-5">
|
||||
<h3 class="text-sm font-semibold mb-3 text-gray-700">Eventos del webhook</h3>
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="font-mono bg-green-50 text-green-700 px-2 py-0.5 rounded">PAYMENT.CAPTURE.COMPLETED</span>
|
||||
<span class="text-gray-500">Pago completado ✅</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="font-mono bg-red-50 text-red-600 px-2 py-0.5 rounded">PAYMENT.CAPTURE.DENIED</span>
|
||||
<span class="text-gray-500">Pago denegado ❌</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="font-mono bg-orange-50 text-orange-600 px-2 py-0.5 rounded">PAYMENT.CAPTURE.REFUNDED</span>
|
||||
<span class="text-gray-500">Devolución 🔄</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="font-mono bg-purple-50 text-purple-600 px-2 py-0.5 rounded">CUSTOMER.DISPUTE.CREATED</span>
|
||||
<span class="text-gray-500">Disputa iniciada ⚠️</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Guía rápida -->
|
||||
<div class="border border-gray-200 rounded-xl p-5">
|
||||
<h3 class="text-sm font-semibold mb-3 text-gray-700">Guía rápida</h3>
|
||||
<ol class="space-y-2 text-xs text-gray-600 list-decimal list-inside">
|
||||
<li>Entra a <span class="font-mono">developer.paypal.com</span> → <strong>Apps & Credentials</strong>.</li>
|
||||
<li>Crea o selecciona una app y copia el <strong>Client ID</strong> y <strong>Secret</strong>.</li>
|
||||
<li>En la misma app activa <strong>Webhooks</strong> y pega la URL de arriba.</li>
|
||||
<li>Copia el <strong>Webhook ID</strong> generado y pégalo en este formulario.</li>
|
||||
<li>Cuando estés listo para producción cambia a <strong>Live</strong> y repite con las credenciales de producción.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /TAB PAYPAL -->
|
||||
|
||||
<!-- Modal detalle de notificación webhook -->
|
||||
<div x-show="showLogModal" x-transition class="fixed inset-0 z-50 flex items-center justify-center bg-black/40 p-4">
|
||||
<div @click.outside="showLogModal = false" class="bg-white rounded-2xl shadow-2xl w-full max-w-lg p-6 relative">
|
||||
@@ -1044,6 +1249,20 @@ function pasarelasApp() {
|
||||
validandoLogID: 0,
|
||||
validandoCallbackID: 0,
|
||||
|
||||
// ─── PayPal ──────────────────────────────────────────────────────
|
||||
paypalModo: 'sandbox',
|
||||
paypal: {
|
||||
id: 0,
|
||||
client_id_sandbox: '',
|
||||
client_secret_sandbox: '',
|
||||
client_id_prod: '',
|
||||
client_secret_prod: '',
|
||||
webhook_id: '',
|
||||
return_url: '',
|
||||
cancel_url: '',
|
||||
nota: '',
|
||||
},
|
||||
|
||||
// ─── dLocal ─────────────────────────────────────────────────────
|
||||
dlocalModo: 'dev',
|
||||
dlocal: {
|
||||
@@ -1093,6 +1312,7 @@ function pasarelasApp() {
|
||||
init() {
|
||||
this.loadBold();
|
||||
this.loadDlocal();
|
||||
this.loadPaypal();
|
||||
// Carga perezosa: se carga cuando el usuario abre esos sub-tabs
|
||||
this.loadBoldLogs();
|
||||
},
|
||||
@@ -1374,6 +1594,47 @@ function pasarelasApp() {
|
||||
}
|
||||
},
|
||||
|
||||
// ─── PayPal helpers ──────────────────────────────────────────────
|
||||
async loadPaypal() {
|
||||
try {
|
||||
const r = await axios.get('/app/pasarelas/paypal/config');
|
||||
if (r.data.data) {
|
||||
const d = r.data.data;
|
||||
this.paypal = {
|
||||
id: d.ID || 0,
|
||||
client_id_sandbox: d.client_id_sandbox || '',
|
||||
client_secret_sandbox: d.client_secret_sandbox || '',
|
||||
client_id_prod: d.client_id_prod || '',
|
||||
client_secret_prod: d.client_secret_prod || '',
|
||||
webhook_id: d.webhook_id || '',
|
||||
return_url: d.return_url || '',
|
||||
cancel_url: d.cancel_url || '',
|
||||
nota: d.nota || '',
|
||||
};
|
||||
this.paypalModo = d.modo || 'sandbox';
|
||||
}
|
||||
} catch (_) {}
|
||||
},
|
||||
|
||||
async savePaypal() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const payload = { ...this.paypal, modo: this.paypalModo };
|
||||
await axios.post('/app/pasarelas/paypal/save', payload);
|
||||
this.showToast('Configuración PayPal guardada ✓');
|
||||
this.loadPaypal();
|
||||
} catch (e) {
|
||||
this.showToast(e.response?.data?.error || 'Error guardando PayPal', 'error');
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
copyPaypalWebhook() {
|
||||
const url = window.location.origin + '/webhooks/paypal';
|
||||
navigator.clipboard.writeText(url).then(() => this.showToast('URL copiada: ' + url));
|
||||
},
|
||||
|
||||
// ─── Toast ───────────────────────────────────────────────────────
|
||||
showToast(msg, type = 'success') {
|
||||
this.toast = { show: true, msg, type };
|
||||
|
||||
@@ -12,11 +12,13 @@ import (
|
||||
func PasarelasPage(c *fiber.Ctx) error {
|
||||
boldCfg, _ := models.GetBoldConfig()
|
||||
dlocalCfg, _ := models.GetLastActiveDlocalApi()
|
||||
paypalCfg, _ := models.GetPaypalConfig()
|
||||
|
||||
data := fiber.Map{
|
||||
"Title": "Pasarelas de Pago",
|
||||
"Bold": boldCfg,
|
||||
"Dlocal": dlocalCfg,
|
||||
"Paypal": paypalCfg,
|
||||
"user": c.Locals("user"),
|
||||
"modules": c.Locals("userModules"),
|
||||
}
|
||||
@@ -543,3 +545,56 @@ func ValidarBoldLog(c *fiber.Ctx) error {
|
||||
"data": entry,
|
||||
})
|
||||
}
|
||||
|
||||
// ─── PayPal ───────────────────────────────────────────────────────────────────
|
||||
|
||||
// GetPaypalConfigAPI devuelve la configuración activa de PayPal.
|
||||
func GetPaypalConfigAPI(c *fiber.Ctx) error {
|
||||
cfg, err := models.GetPaypalConfig()
|
||||
if err != nil {
|
||||
return c.JSON(fiber.Map{"data": nil})
|
||||
}
|
||||
return c.JSON(fiber.Map{"data": cfg})
|
||||
}
|
||||
|
||||
// SavePaypalConfigWeb guarda o actualiza la configuración de PayPal.
|
||||
func SavePaypalConfigWeb(c *fiber.Ctx) error {
|
||||
type body struct {
|
||||
ID uint `json:"id" form:"id"`
|
||||
ClientIDSandbox string `json:"client_id_sandbox" form:"client_id_sandbox"`
|
||||
ClientSecretSandbox string `json:"client_secret_sandbox" form:"client_secret_sandbox"`
|
||||
ClientIDProd string `json:"client_id_prod" form:"client_id_prod"`
|
||||
ClientSecretProd string `json:"client_secret_prod" form:"client_secret_prod"`
|
||||
Modo string `json:"modo" form:"modo"`
|
||||
WebhookID string `json:"webhook_id" form:"webhook_id"`
|
||||
ReturnURL string `json:"return_url" form:"return_url"`
|
||||
CancelURL string `json:"cancel_url" form:"cancel_url"`
|
||||
Nota string `json:"nota" form:"nota"`
|
||||
}
|
||||
var b body
|
||||
if err := c.BodyParser(&b); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "body inválido"})
|
||||
}
|
||||
if b.Modo == "" {
|
||||
b.Modo = "sandbox"
|
||||
}
|
||||
|
||||
cfg := models.PaypalConfig{
|
||||
ClientIDSandbox: b.ClientIDSandbox,
|
||||
ClientSecretSandbox: b.ClientSecretSandbox,
|
||||
ClientIDProd: b.ClientIDProd,
|
||||
ClientSecretProd: b.ClientSecretProd,
|
||||
Modo: b.Modo,
|
||||
WebhookID: b.WebhookID,
|
||||
ReturnURL: b.ReturnURL,
|
||||
CancelURL: b.CancelURL,
|
||||
Nota: b.Nota,
|
||||
}
|
||||
cfg.ID = b.ID
|
||||
|
||||
if err := models.SavePaypalConfig(cfg); err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
|
||||
}
|
||||
return c.JSON(fiber.Map{"message": "Configuración PayPal guardada"})
|
||||
}
|
||||
|
||||
|
||||
@@ -228,6 +228,9 @@ func UserRoutes(app fiber.Router) {
|
||||
protected.Get("/pasarelas/dlocal/logs", controllers.DlocalPaymentLogsPaginated)
|
||||
protected.Post("/pasarelas/dlocal/logs/:id/validar", controllers.ValidarDlocalLog)
|
||||
protected.Post("/pasarelas/dlocal/registro-pago", apiControllers.DlocalRegistrarPago)
|
||||
// PayPal
|
||||
protected.Get("/pasarelas/paypal/config", controllers.GetPaypalConfigAPI)
|
||||
protected.Post("/pasarelas/paypal/save", controllers.SavePaypalConfigWeb)
|
||||
// Bold API (crear link, consultar estado)
|
||||
protected.Post("/pasarelas/bold/crear-link", apiControllers.BoldCreatePaymentLink)
|
||||
protected.Get("/pasarelas/bold/link/:linkID", apiControllers.BoldGetLinkStatus)
|
||||
|
||||
Reference in New Issue
Block a user