1
0
Files
services_core/app/Http/Controllers/Notification/NotificationWebhookController.php
T
2026-07-31 13:57:11 +07:00

91 lines
3.5 KiB
PHP

<?php
namespace App\Http\Controllers\Notification;
use App\Http\Controllers\Controller;
use App\Models\NotificationChannel;
use App\Models\NotificationDelivery;
use App\Models\NotificationWebhookLog;
use Illuminate\Http\Request;
class NotificationWebhookController extends Controller
{
public function verify(Request $request, NotificationChannel $notificationChannel)
{
abort_unless(
$request->query('hub.mode') === 'subscribe'
&& hash_equals((string) data_get($notificationChannel->credentials, 'webhook_verify_token'), (string) $request->query('hub.verify_token')),
403,
);
return response($request->query('hub.challenge'), 200);
}
public function handle(Request $request, NotificationChannel $notificationChannel)
{
$valid = $this->validSignature($request, $notificationChannel);
$payload = $request->all();
$updates = $this->updates($payload);
$log = NotificationWebhookLog::create([
'notification_channel_id' => $notificationChannel->id,
'provider' => $notificationChannel->provider,
'event_type' => data_get($payload, 'entry.0.changes.0.field') ?? data_get($payload, 'event'),
'provider_message_id' => data_get($updates, '0.id'),
'signature_valid' => $valid,
'payload' => $payload,
]);
abort_unless($valid, 401);
foreach ($updates as $update) {
$delivery = NotificationDelivery::where('provider_message_id', $update['id'] ?? null)->first();
if (! $delivery) {
continue;
}
$status = match ($update['status'] ?? null) {
'delivered' => 'delivered',
'read' => 'read',
'failed' => 'failed',
default => 'sent',
};
$delivery->update([
'status' => $status,
'delivered_at' => $status === 'delivered' ? now() : $delivery->delivered_at,
'read_at' => $status === 'read' ? now() : $delivery->read_at,
'failed_at' => $status === 'failed' ? now() : $delivery->failed_at,
'error_code' => data_get($update, 'errors.0.code'),
'error_message' => data_get($update, 'errors.0.title'),
]);
}
$log->update(['processed_at' => now()]);
return response()->json(['success' => true]);
}
private function validSignature(Request $request, NotificationChannel $channel): bool
{
if ($channel->channel === 'whatsapp_official') {
$secret = data_get($channel->credentials, 'app_secret');
$signature = $request->header('X-Hub-Signature-256');
return $secret && $signature && hash_equals('sha256='.hash_hmac('sha256', $request->getContent(), $secret), $signature);
}
$secret = data_get($channel->credentials, 'webhook_secret');
return $secret && hash_equals((string) $secret, (string) $request->header('X-Webhook-Secret'));
}
private function updates(array $payload): array
{
$statuses = data_get($payload, 'entry.0.changes.0.value.statuses');
if (is_array($statuses)) {
return $statuses;
}
return [[
'id' => data_get($payload, 'message_id') ?? data_get($payload, 'id'),
'status' => data_get($payload, 'status') ?? data_get($payload, 'event'),
'errors' => data_get($payload, 'errors', []),
]];
}
}