Acapadev

AcapaPay · Laravel SDK

Webhooks — Confirmação Assíncrona de Pagamentos

Arquitetura

Quando um pagamento é confirmado (Pay4All, E-Kwanza, Cartão, etc.), o servidor AcapaPay envia um HTTP POST para o endpoint da sua aplicação. O SDK já regista automaticamente esta rota e trata de toda a segurança e processamento: validação HMAC, seguida do disparo do evento AcapaPayInvoicePaid para os seus Listeners.

Rota Registada Automaticamente

O AcapaPayServiceProvider regista automaticamente a seguinte rota no boot da sua aplicação, excluindo-a da proteção CSRF (obrigatório para chamadas server-to-server):

POST /webhooks/acapapay
Route Name: acapapay.webhook
Middleware: Sem CSRF (excluído automaticamente)

Garanta que este URL está configurado no painel de Developer Apps do SSO Acapadev, na secção de Webhooks da sua App.

Validação HMAC Automática

O WebhookController do SDK valida automaticamente a assinatura de cada pedido antes de o processar. O header verificado é X-AcapaDev-Signature:

// Lógica interna do SDK (não precisa de escrever isto)
$expectedSignature = hash_hmac('sha256', $rawPayload, $webhookSecret);

if (!hash_equals($expectedSignature, $receivedSignature)) {
    return response()->json(['error' => 'Assinatura Invalida'], 401);
}

Escutar o Evento no seu Projeto

Registe o Listener no EventServiceProvider da sua aplicação:

<?php

namespace App\Providers;

use AcapaPay\Laravel\Events\AcapaPayInvoicePaid;
use App\Listeners\ActivarAssinatura;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        AcapaPayInvoicePaid::class => [
            ActivarAssinatura::class,
        ],
    ];
}

Implementar o Listener

<?php

namespace App\Listeners;

use AcapaPay\Laravel\Events\AcapaPayInvoicePaid;
use App\Models\User;
use App\Models\Subscription;
use Illuminate\Support\Facades\Log;

class ActivarAssinatura
{
    public function handle(AcapaPayInvoicePaid $event): void
    {
        $subscriptionId = $event->subscriptionId; // UUID da subscrição no AcapaPay
        $metadata       = $event->metadata;        // Os seus dados do Checkout
        $expiresAt      = $event->expiresAt;       // Data de expiração da subscrição
        $rawPayload     = $event->invoicePayload;  // Payload completo original

        $localUserId = $metadata['local_user_id'] ?? null;
        if (!$localUserId) {
            Log::error('AcapaPay Listener: local_user_id em falta na metadata.');
            return;
        }

        $user = User::find($localUserId);
        if (!$user) {
            Log::error("AcapaPay Listener: Utilizador #{$localUserId} não encontrado.");
            return;
        }

        // Protecção contra processamento duplicado (idempotência)
        if (Subscription::where('acapapay_id', $subscriptionId)->exists()) {
            Log::info("AcapaPay: Subscrição {$subscriptionId} já processada. Ignorando.");
            return;
        }

        Subscription::create([
            'user_id'     => $user->id,
            'acapapay_id' => $subscriptionId,
            'expires_at'  => $expiresAt,
            'status'      => 'active',
        ]);

        Log::info("AcapaPay: Subscrição {$subscriptionId} ativada para utilizador #{$user->id}.");
    }
}

Payload do Evento (Estrutura)

PropriedadeTipoDescrição
$event->subscriptionIdstringUUID da subscrição criada no AcapaPay
$event->metadataarrayOs dados enviados por si durante o checkoutSession
$event->expiresAtstring|nullData de expiração da subscrição (ISO 8601)
$event->invoicePayloadarrayPayload completo bruto do webhook para auditoria
Enter para selecionar ESC para sair