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)
| Propriedade | Tipo | Descrição |
|---|---|---|
$event->subscriptionId | string | UUID da subscrição criada no AcapaPay |
$event->metadata | array | Os dados enviados por si durante o checkoutSession |
$event->expiresAt | string|null | Data de expiração da subscrição (ISO 8601) |
$event->invoicePayload | array | Payload completo bruto do webhook para auditoria |