config = model(Configuration::class); $this->payment = model(PayPalPayment::class); } public function handle() { $config = $this->loadConfig(); if (!$config) { return $this->respondError('PayPal configuration incomplete', Response::HTTP_INTERNAL_SERVER_ERROR); } $payload = $this->payloadData(); if (empty($payload)) { return $this->respondError('Invalid JSON payload', Response::HTTP_BAD_REQUEST); } $requiredHeaders = [ 'paypal-transmission-id', 'paypal-transmission-time', 'paypal-cert-url', 'paypal-auth-algo', 'paypal-transmission-sig', ]; foreach ($requiredHeaders as $header) { if (!$this->laravelRequest->headers->has($header)) { return $this->respondError("Missing required header: {$header}", Response::HTTP_BAD_REQUEST); } } $accessToken = $this->getAccessToken($config['client_id'], $config['secret'], $config['token_url']); if (!$accessToken) { return $this->respondError('Failed to get access token', Response::HTTP_INTERNAL_SERVER_ERROR); } $verificationData = [ 'auth_algo' => $this->laravelRequest->headers->get('paypal-auth-algo'), 'cert_url' => $this->laravelRequest->headers->get('paypal-cert-url'), 'transmission_id' => $this->laravelRequest->headers->get('paypal-transmission-id'), 'transmission_sig' => $this->laravelRequest->headers->get('paypal-transmission-sig'), 'transmission_time' => $this->laravelRequest->headers->get('paypal-transmission-time'), 'webhook_id' => $config['webhook_id'], 'webhook_event' => $payload, ]; if (!$this->verifyWebhookSignature($config['verify_url'], $accessToken, $verificationData)) { return $this->respondError('Webhook verification failed', Response::HTTP_UNAUTHORIZED); } try { $this->processPayment($payload); return $this->success(['status' => 'success'], 'Webhook processed successfully'); } catch (\Throwable $e) { log_message('error', 'Payment processing error: ' . $e->getMessage()); return $this->respondError('Payment processing error', Response::HTTP_INTERNAL_SERVER_ERROR); } } protected function loadConfig(): ?array { $mode = (string) ($this->config->getConfig('paypal_mode') ?? ''); $configs = [ 'sandbox' => [ 'webhook_id' => $this->config->getConfig('sandbox_webhookid_paypal'), 'client_id' => $this->config->getConfig('sandbox_clientid_paypal'), 'secret' => $this->config->getConfig('sandbox_secret_paypal'), 'verify_url' => $this->config->getConfig('sandbox_verifylink_paypal'), 'token_url' => $this->config->getConfig('sandbox_tokenurl_paypal'), ], 'production' => [ 'webhook_id' => $this->config->getConfig('production_webhookid_paypal'), 'client_id' => $this->config->getConfig('production_clientid_paypal'), 'secret' => $this->config->getConfig('production_secret_paypal'), 'verify_url' => $this->config->getConfig('production_verifylink_paypal'), 'token_url' => $this->config->getConfig('production_tokenurl_paypal'), ], ]; if (!isset($configs[$mode])) { return null; } $cfg = $configs[$mode]; if (in_array(null, $cfg, true) || in_array('', $cfg, true)) { return null; } return $cfg + ['mode' => $mode]; } protected function getAccessToken(string $clientId, string $secret, string $urlToken): ?string { try { $response = Http::asForm() ->withBasicAuth($clientId, $secret) ->acceptJson() ->post($urlToken, ['grant_type' => 'client_credentials']); if (!$response->successful()) { log_message('error', 'Access token fetch failed: HTTP ' . $response->status()); return null; } return $response->json('access_token'); } catch (\Throwable $e) { log_message('error', 'Access token fetch failed: ' . $e->getMessage()); return null; } } protected function verifyWebhookSignature(string $verifyUrl, string $accessToken, array $verificationData): bool { try { $response = Http::withToken($accessToken) ->acceptJson() ->post($verifyUrl, $verificationData); if (!$response->successful()) { log_message('error', 'Webhook verification failed: HTTP ' . $response->status()); return false; } return ($response->json('verification_status') ?? '') === 'SUCCESS'; } catch (\Throwable $e) { log_message('error', 'Webhook verification failed: ' . $e->getMessage()); return false; } } protected function processPayment(array $eventData): void { $resource = $eventData['resource'] ?? []; $this->payment->create([ 'webhook_id' => $eventData['id'] ?? null, 'transaction_id' => $resource['id'] ?? null, 'status' => $resource['status'] ?? null, 'amount' => $resource['amount']['value'] ?? null, 'currency' => $resource['amount']['currency_code'] ?? null, 'payer_email' => $resource['payer']['email_address'] ?? null, 'raw_payload' => json_encode($eventData), ]); } }