← Volver al inicio

🎯 Sistema de Eventos de Pagos

Cómo ejecutar acciones automáticas al completar pagos, sin importar el proveedor

🤔 ¿Qué Problema Resuelve?

❌ Antes (Sin Eventos)

// En StripeController
if ($result->success) {
    Payment::create([...]);
    Mail::to($customer)->send(...);
    Notification::send($admin, ...);
    Inventory::reduce(...);
}

// En RedsysController
if ($result->success) {
    Payment::create([...]);           // DUPLICADO
    Mail::to($customer)->send(...);   // DUPLICADO
    Notification::send($admin, ...);  // DUPLICADO
    Inventory::reduce(...);           // DUPLICADO
}

// Y así con cada proveedor... 😫

Problemas: Código duplicado, difícil de mantener, mezcla lógica de negocio.

✅ Ahora (Con Eventos)

// En TODOS los controllers
if ($result->success) {
    event(new PaymentCompleted(
        provider: PaymentProvider::STRIPE,
        result: $result,
        orderId: $orderId,
        amount: $amount,
        currency: 'EUR'
    ));
}

// ✨ Los listeners se ejecutan automáticamente:
//  → LogPaymentToDatabase
//  → SendConfirmationEmail
//  → SendAdminNotification
//  → UpdateInventory

Ventajas: Sin duplicación, fácil de mantener, separación de responsabilidades.

🔄 Flujo de Ejecución

1️⃣ Usuario Completa el Pago

En Stripe, Redsys o PayPal

2️⃣ Controller Captura el Resultado

$result = $gateway->capture($paymentId);

3️⃣ Si Exitoso, Dispara el Evento

event(new PaymentCompleted(...));

4️⃣ Laravel Ejecuta los Listeners

⚡ LogPaymentToDatabase (inmediato)

⚡ UpdateInventory (inmediato)

⏱️ SendConfirmationEmail (cola)

⏱️ SendAdminNotification (cola)

5️⃣ Usuario Recibe Respuesta

Página de éxito o error

6️⃣ Workers Procesan Cola (Background)

Segundos después, los listeners asíncronos se ejecutan sin bloquear la respuesta

🧩 Componentes del Sistema

📢 Evento: PaymentCompleted

Encapsula toda la información de un pago completado, independiente del proveedor.

// app/Events/PaymentCompleted.php

event(new PaymentCompleted(
    provider: PaymentProvider::STRIPE,        // STRIPE, REDSYS, PAYPAL
    result: $paymentResult,                   // Resultado del pago
    orderId: 'ORDER-123',                    // ID de la orden
    amount: 99.99,                             // Cantidad pagada
    currency: 'EUR',                           // Moneda
    metadata: ['items' => [...]],              // Datos adicionales
    customerEmail: 'cliente@example.com'      // Email del cliente
));

👂 Listeners (Escuchadores)

Clases que se ejecutan automáticamente cuando el evento se dispara.

💾 LogPaymentToDatabase

⚡ SÍNCRONO

Guarda el pago en la base de datos. Se ejecuta inmediatamente porque es crítico.

Payment::create([
    'order_id'     => $event->orderId,
    'payment_id'   => $event->result->paymentId,
    'provider'     => $event->provider,
    'amount'       => $event->amount,
    'currency'     => $event->currency,
    'completed_at' => now(),
]);

📧 SendPaymentConfirmationEmail

⏱️ ASÍNCRONO

Envía email de confirmación al cliente. Se ejecuta en background para no bloquear la respuesta.

Mail::to($event->customerEmail)
    ->send(new PaymentConfirmationMail($event));

// shouldQueue() retorna true
// → Se ejecuta en cola (background)

📢 SendAdminNotification

⏱️ ASÍNCRONO

Notifica al administrador (email, Slack, SMS, etc.). También en background.

// Puedes usar múltiples canales:

// Email
Mail::to($adminEmail)->send(...);

// Slack
Notification::route('slack', $webhook)
    ->notify(new PaymentNotification($event));

// SMS, WhatsApp, Discord, etc.

📦 UpdateInventory

⚡ SÍNCRONO

Actualiza inventario, activa suscripciones, reduce stock. Inmediato para evitar sobre-ventas.

$items = $event->metadata['items'] ?? [];

foreach ($items as $item) {
    $product = Product::find($item['product_id']);
    
    if ($product) {
        $product->decrement('stock', $item['quantity']);
    }
}

⚡ Síncrono vs Asíncrono

Aspecto ⚡ Síncrono ⏱️ Asíncrono (Cola)
Ejecución Inmediata, antes de responder Background, después de responder
Bloquea respuesta ✅ Sí ❌ No
Velocidad Ralentiza la respuesta Respuesta inmediata
Uso recomendado Operaciones críticas (BD, stock) Emails, notificaciones, logs
Reintentos No automáticos ✅ Automáticos si falla
Ejemplos LogPaymentToDatabase, UpdateInventory SendEmail, SendNotification

✨ Ventajas del Sistema

🔄 Agnóstico del Proveedor

El mismo evento funciona para Stripe, Redsys, PayPal y cualquier proveedor futuro. No importa cómo se hizo el pago.

➕ Fácil Añadir Acciones

¿Quieres enviar a Discord? Crea un listener, regístralo. ¡Listo! Sin tocar controllers ni servicios.

🔌 Fácil Desactivar

Comenta una línea en AppServiceProvider para desactivar cualquier acción. Sin borrar código.

🧪 Testing Simplificado

Event::fake() te permite testear fácilmente que los eventos se disparan correctamente.

📊 Monitoreo

Crea listeners para métricas, analytics, APM. Un solo lugar para todo el seguimiento.

🔁 Ejecución Condicional

Ejecuta acciones solo bajo ciertas condiciones (montos, usuarios, productos específicos).

➕ Cómo Añadir una Nueva Acción

Paso 1: Crear el Listener

php artisan make:listener NombreListener --event=PaymentCompleted

Paso 2: Implementar la Lógica

class NombreListener
{
    public function handle(PaymentCompleted $event): void
    {
        // Tu código aquí
        // Acceso a datos:
        //   $event->provider
        //   $event->amount
        //   $event->orderId
        //   $event->customerEmail
        //   $event->metadata
    }
    
    // Opcional: ejecutar en cola (background)
    public function shouldQueue(): bool
    {
        return true;
    }
}

Paso 3: Registrar en AppServiceProvider

// app/Providers/AppServiceProvider.php

Event::listen(PaymentCompleted::class, [
    LogPaymentToDatabase::class,
    SendPaymentConfirmationEmail::class,
    SendAdminNotification::class,
    UpdateInventory::class,
    NombreListener::class,  ← Tu nuevo listener
]);
🎉 ¡Listo! Tu nueva acción se ejecutará automáticamente en TODOS los pagos exitosos, sin importar si fueron con Stripe, Redsys o PayPal.

💼 Ejemplo Real de Uso

Imaginemos que queremos enviar una notificación a Discord cuando un pago sea mayor a €100:

// app/Listeners/SendDiscordHighValueAlert.php

namespace App\Listeners;

use App\Events\PaymentCompleted;
use Illuminate\Support\Facades\Http;

class SendDiscordHighValueAlert
{
    public function handle(PaymentCompleted $event): void
    {
        // Solo si el pago es >= 100€
        if ($event->amount < 100) {
            return;
        }
        
        // Construir mensaje para Discord
        $webhook = config('services.discord.webhook');
        $message = "💰 ¡Pago grande! €{$event->amount} via {$event->provider->value}";
        
        // Enviar a Discord
        Http::post($webhook, [
            'content' => $message,
            'embeds' => [[
                'title' => 'Nuevo Pago de Alto Valor',
                'color' => 0x00ff00,
                'fields' => [
                    [
                        'name'   => 'Cantidad',
                        'value'  => "€{$event->amount}",
                        'inline' => true
                    ],
                    [
                        'name'   => 'Proveedor',
                        'value'  => $event->provider->value,
                        'inline' => true
                    ],
                    [
                        'name'  => 'Order ID',
                        'value' => $event->orderId
                    ],
                ]
            ]]
        ]);
    }
    
    public function shouldQueue(): bool
    {
        return true;  // Ejecutar en background
    }
}
💡 Resultado: Cada vez que alguien pague €100 o más (con cualquier proveedor), recibirás automáticamente una notificación en Discord. ¡Sin modificar ningún controller!

📚 Recursos Adicionales

📖 Laravel Events Docs

Documentación oficial de Laravel sobre eventos

⏱️ Laravel Queues Docs

Cómo configurar colas para listeners asíncronos

🏠 Volver al Inicio

Probar pagos con Stripe, Redsys o PayPal