Enable Discovery debug logging for production troubleshooting
- Add DISCOVERY_LOG_LEVEL=debug - Add DISCOVERY_SHOW_PROGRESS=true - Temporary changes for debugging InitializerProcessor fixes on production
This commit is contained in:
203
src/Framework/Webhook/Processing/WebhookRequestHandler.php
Normal file
203
src/Framework/Webhook/Processing/WebhookRequestHandler.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Framework\Webhook\Processing;
|
||||
|
||||
use App\Framework\Core\Events\EventDispatcher;
|
||||
use App\Framework\Http\HttpRequest;
|
||||
use App\Framework\Http\HttpResponse;
|
||||
use App\Framework\Http\JsonResult;
|
||||
use App\Framework\Logging\Logger;
|
||||
use App\Framework\Webhook\Events\WebhookFailed;
|
||||
use App\Framework\Webhook\Events\WebhookReceived;
|
||||
use App\Framework\Webhook\Security\SignatureVerifier;
|
||||
use App\Framework\Webhook\ValueObjects\WebhookPayload;
|
||||
use App\Framework\Webhook\ValueObjects\WebhookProvider;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Central webhook request handler using EventDispatcher
|
||||
* Handles incoming webhook requests, verifies signatures, and dispatches events
|
||||
*/
|
||||
final readonly class WebhookRequestHandler
|
||||
{
|
||||
public function __construct(
|
||||
private SignatureVerifier $signatureVerifier,
|
||||
private EventDispatcher $eventDispatcher,
|
||||
private IdempotencyService $idempotencyService,
|
||||
private Logger $logger
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process incoming webhook request
|
||||
* Returns JSON response with processing status
|
||||
*/
|
||||
public function handle(
|
||||
HttpRequest $request,
|
||||
WebhookProvider $provider,
|
||||
string $secret,
|
||||
array $allowedEvents = []
|
||||
): HttpResponse {
|
||||
$requestId = $this->generateRequestId();
|
||||
$rawPayload = $request->getRawBody();
|
||||
|
||||
try {
|
||||
// Step 1: Verify signature
|
||||
if (! $this->verifySignature($request, $rawPayload, $provider, $secret)) {
|
||||
$this->logger->warning('Webhook signature verification failed', [
|
||||
'provider' => $provider->toString(),
|
||||
'request_id' => $requestId,
|
||||
'ip' => $request->server->getClientIp(),
|
||||
'user_agent' => $request->server->getUserAgent()?->toString(),
|
||||
]);
|
||||
|
||||
return $this->errorResponse('Invalid signature', 401, $requestId);
|
||||
}
|
||||
|
||||
// Step 2: Parse payload
|
||||
$payload = WebhookPayload::fromRaw($rawPayload);
|
||||
$eventType = $payload->getEventType();
|
||||
|
||||
// Step 3: Check allowed events
|
||||
if (! empty($allowedEvents) && ! in_array($eventType, $allowedEvents, true)) {
|
||||
$this->logger->info('Webhook event not allowed', [
|
||||
'provider' => $provider->toString(),
|
||||
'event_type' => $eventType,
|
||||
'allowed_events' => $allowedEvents,
|
||||
'request_id' => $requestId,
|
||||
]);
|
||||
|
||||
return $this->successResponse('Event ignored', $requestId);
|
||||
}
|
||||
|
||||
// Step 4: Check idempotency
|
||||
$webhookId = $payload->getWebhookId();
|
||||
if ($webhookId && $this->idempotencyService->isDuplicate($webhookId, $provider)) {
|
||||
$this->logger->info('Duplicate webhook request detected', [
|
||||
'webhook_id' => $webhookId,
|
||||
'provider' => $provider->toString(),
|
||||
'request_id' => $requestId,
|
||||
]);
|
||||
|
||||
return $this->successResponse('Webhook already processed', $requestId);
|
||||
}
|
||||
|
||||
// Step 5: Mark as processing
|
||||
if ($webhookId) {
|
||||
$this->idempotencyService->markProcessing($webhookId, $provider);
|
||||
}
|
||||
|
||||
// Step 6: Create and dispatch webhook received event
|
||||
$webhookEvent = WebhookReceived::create(
|
||||
provider: $provider,
|
||||
payload: $payload,
|
||||
endpoint: $request->getPath(),
|
||||
eventType: $eventType
|
||||
);
|
||||
|
||||
// Dispatch using EventDispatcher - returns array of handler results
|
||||
$results = $this->eventDispatcher->dispatch($webhookEvent);
|
||||
|
||||
// Step 7: Mark as processed
|
||||
if ($webhookId) {
|
||||
$this->idempotencyService->markProcessed($webhookId, $provider);
|
||||
}
|
||||
|
||||
$this->logger->info('Webhook processed successfully', [
|
||||
'provider' => $provider->toString(),
|
||||
'event_type' => $eventType,
|
||||
'webhook_id' => $webhookId,
|
||||
'request_id' => $requestId,
|
||||
'handlers_executed' => count($results),
|
||||
'processing_time' => microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'],
|
||||
]);
|
||||
|
||||
return $this->successResponse('Webhook processed', $requestId, [
|
||||
'event_type' => $eventType,
|
||||
'webhook_id' => $webhookId,
|
||||
'handlers_executed' => count($results),
|
||||
]);
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Step 8: Handle processing errors
|
||||
if (isset($webhookId)) {
|
||||
$this->idempotencyService->markFailed($webhookId, $provider, $e->getMessage());
|
||||
}
|
||||
|
||||
// Dispatch webhook failed event
|
||||
$failedEvent = WebhookFailed::create(
|
||||
provider: $provider,
|
||||
endpoint: $request->getPath(),
|
||||
error: $e->getMessage(),
|
||||
payload: isset($payload) ? $payload : null
|
||||
);
|
||||
|
||||
$this->eventDispatcher->dispatch($failedEvent);
|
||||
|
||||
$this->logger->error('Webhook processing failed', [
|
||||
'provider' => $provider->toString(),
|
||||
'request_id' => $requestId,
|
||||
'error' => $e->getMessage(),
|
||||
'webhook_id' => $webhookId ?? null,
|
||||
]);
|
||||
|
||||
return $this->errorResponse('Webhook processing failed', 500, $requestId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify webhook signature using provider-specific verification
|
||||
*/
|
||||
private function verifySignature(
|
||||
HttpRequest $request,
|
||||
string $payload,
|
||||
WebhookProvider $provider,
|
||||
string $secret
|
||||
): bool {
|
||||
$signatureHeader = $this->signatureVerifier->getSignatureHeader($provider);
|
||||
$signature = $request->headers->get($signatureHeader);
|
||||
|
||||
if (empty($signature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->signatureVerifier->verify($payload, $signature, $secret, $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique request ID for tracking
|
||||
*/
|
||||
private function generateRequestId(): string
|
||||
{
|
||||
return 'wh_' . bin2hex(random_bytes(8)) . '_' . time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create success response
|
||||
*/
|
||||
private function successResponse(string $message, string $requestId, array $data = []): JsonResult
|
||||
{
|
||||
return new JsonResult([
|
||||
'status' => 'success',
|
||||
'message' => $message,
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => date('c'),
|
||||
...$data,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create error response
|
||||
*/
|
||||
private function errorResponse(string $message, int $statusCode, string $requestId): JsonResult
|
||||
{
|
||||
return new JsonResult([
|
||||
'status' => 'error',
|
||||
'message' => $message,
|
||||
'request_id' => $requestId,
|
||||
'timestamp' => date('c'),
|
||||
], $statusCode);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user