<?php
namespace App\Http\Controllers\Webhooks;
use App\Http\Controllers\Controller;
use App\Models\Vcard;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
/**
* Webhook handler for Brazilian payments (Asaas / PIX)
* Route: POST /webhooks/brasil
*
* Asaas docs: https://docs.asaas.com/reference/webhooks
*/
class BrasilWebhookController extends Controller
{
public function handle(Request $request): JsonResponse
{
// Validate Asaas token signature
$token = $request->header('asaas-access-token')
?? $request->header('x-asaas-token');
$expectedToken = config('services.asaas.webhook_token');
if ($expectedToken && $token !== $expectedToken) {
Log::warning('[BrasilWebhook] Invalid token: ' . $token);
return response()->json(['error' => 'Unauthorized'], 401);
}
$payload = $request->all();
$event = $payload['event'] ?? '';
Log::info('[BrasilWebhook] Event: ' . $event, [
'payment_id' => $payload['payment']['id'] ?? null,
'external_ref' => $payload['payment']['externalReference'] ?? null,
]);
return match ($event) {
'PAYMENT_CONFIRMED',
'PAYMENT_RECEIVED' => $this->handlePaymentConfirmed($payload),
'PAYMENT_OVERDUE' => $this->handlePaymentOverdue($payload),
'PAYMENT_DELETED',
'PAYMENT_REFUNDED' => $this->handlePaymentRefunded($payload),
default => response()->json(['status' => 'event_ignored']),
};
}
private function handlePaymentConfirmed(array $payload): JsonResponse
{
$externalRef = $payload['payment']['externalReference'] ?? null;
if (!$externalRef || !str_starts_with($externalRef, 'vcard_')) {
Log::warning('[BrasilWebhook] Unknown externalReference: ' . $externalRef);
return response()->json(['status' => 'skipped']);
}
$vcardId = (int) str_replace('vcard_', '', $externalRef);
$vcard = Vcard::withTrashed()->find($vcardId);
if (!$vcard) {
Log::error('[BrasilWebhook] VCard not found: #' . $vcardId);
return response()->json(['error' => 'vcard_not_found'], 404);
}
// Activate the card
$vcard->update([
'status' => 'active',
'payment_deadline' => null,
'nfc_status' => $vcard->nfc_status === 'none' ? 'none' : 'pending',
]);
// Restore if soft-deleted before payment
if ($vcard->trashed()) {
$vcard->restore();
}
Log::info("[BrasilWebhook] VCard#{$vcardId} ACTIVATED via PIX/Asaas");
return response()->json(['status' => 'activated', 'vcard_id' => $vcardId]);
}
private function handlePaymentOverdue(array $payload): JsonResponse
{
$externalRef = $payload['payment']['externalReference'] ?? null;
Log::warning('[BrasilWebhook] Payment OVERDUE: ' . $externalRef);
return response()->json(['status' => 'noted']);
}
private function handlePaymentRefunded(array $payload): JsonResponse
{
$externalRef = $payload['payment']['externalReference'] ?? null;
Log::info('[BrasilWebhook] Payment REFUNDED: ' . $externalRef);
return response()->json(['status' => 'noted']);
}
}
|