decodePayload($request); if (! is_array($payload)) { return response()->json(['status' => 'ignored'], Response::HTTP_ACCEPTED); } if (! $this->verifier->verify($request, $payload)) { Log::warning('PayPal webhook signature verification failed'); return response()->json(['status' => 'invalid'], Response::HTTP_BAD_REQUEST); } $eventType = $payload['event_type'] ?? null; $eventId = $payload['id'] ?? null; $webhookEvent = $this->recorder->recordReceived( 'paypal', is_string($eventId) ? $eventId : null, is_string($eventType) ? $eventType : null, ); $handled = is_string($eventType) ? $this->webhooks->handle($payload) : false; Log::info('PayPal webhook processed', [ 'event_type' => $eventType, 'handled' => $handled, ]); if ($handled) { $this->recorder->markProcessed($webhookEvent, ['handled' => true]); } else { $this->recorder->markIgnored($webhookEvent, ['handled' => false]); } return response()->json([ 'status' => $handled ? 'processed' : 'ignored', ], $handled ? Response::HTTP_OK : Response::HTTP_ACCEPTED); } catch (\Throwable $exception) { $eventId = $this->captureWebhookException($exception); Log::error('PayPal webhook processing failed', [ 'message' => $exception->getMessage(), 'event_type' => (string) data_get($request->json()->all(), 'event_type'), 'sentry_event_id' => $eventId, ]); if (isset($webhookEvent)) { $this->recorder->markFailed($webhookEvent, $exception->getMessage()); } return response()->json(['status' => 'error'], Response::HTTP_INTERNAL_SERVER_ERROR); } } /** * @return array|null */ protected function decodePayload(Request $request): ?array { $payload = $request->getContent(); if (! is_string($payload) || $payload === '') { return null; } $decoded = json_decode($payload, true); return is_array($decoded) ? $decoded : null; } protected function captureWebhookException(\Throwable $exception): ?string { report($exception); if (! app()->bound('sentry') || empty(config('sentry.dsn'))) { return null; } try { $eventId = app('sentry')->captureException($exception); } catch (\Throwable) { return null; } return $eventId ? (string) $eventId : null; } }