resolveOrderId($request); $fallback = $this->resolveFallbackUrl(); if (! $orderId) { return redirect()->to($fallback); } $session = CheckoutSession::query() ->where('paypal_order_id', $orderId) ->first(); if (! $session) { return redirect()->to($fallback); } $successUrl = data_get($session->provider_metadata ?? [], 'paypal_success_url'); $cancelUrl = data_get($session->provider_metadata ?? [], 'paypal_cancel_url'); if ($session->status === CheckoutSession::STATUS_COMPLETED) { return redirect()->to($this->resolveSafeRedirect($successUrl, $fallback)); } try { $capture = $this->orders->captureOrder($orderId, [ 'request_id' => $session->id, ]); } catch (PayPalException $exception) { Log::warning('PayPal return capture failed', [ 'session_id' => $session->id, 'order_id' => $orderId, 'message' => $exception->getMessage(), 'status' => $exception->status(), ]); $this->sessions->markFailed($session, 'paypal_capture_failed'); $this->couponRedemptions->recordFailure($session, 'paypal_capture_failed'); return redirect()->to($this->resolveSafeRedirect($cancelUrl, $fallback)); } $status = strtoupper((string) ($capture['status'] ?? '')); $captureId = $this->orders->resolveCaptureId($capture); $totals = $this->orders->resolveTotals($capture); $session->forceFill([ 'paypal_capture_id' => $captureId, 'provider_metadata' => array_merge($session->provider_metadata ?? [], array_filter([ 'paypal_status' => $status ?: null, 'paypal_capture_id' => $captureId, 'paypal_totals' => $totals !== [] ? $totals : null, 'paypal_captured_at' => now()->toIso8601String(), ])), ])->save(); if ($status === 'COMPLETED') { $this->sessions->markProcessing($session, [ 'paypal_status' => $status, 'paypal_capture_id' => $captureId, ]); $this->assignment->finalise($session, [ 'source' => 'paypal_return', 'provider' => CheckoutSession::PROVIDER_PAYPAL, 'provider_reference' => $captureId ?? $orderId, 'payload' => $capture, ]); $this->sessions->markCompleted($session, now()); $this->couponRedemptions->recordSuccess($session, $capture); return redirect()->to($this->resolveSafeRedirect($successUrl, $fallback)); } $this->sessions->markFailed($session, 'paypal_'.$status); $this->couponRedemptions->recordFailure($session, 'paypal_'.$status); return redirect()->to($this->resolveSafeRedirect($cancelUrl, $fallback)); } protected function resolveOrderId(Request $request): ?string { $candidate = $request->query('token') ?? $request->query('order_id'); if (! is_string($candidate) || $candidate === '') { return null; } return $candidate; } protected function resolveFallbackUrl(): string { return rtrim((string) config('app.url', url('/')), '/') ?: url('/'); } protected function resolveSafeRedirect(?string $target, string $fallback): string { if (! $target) { return $fallback; } if (Str::startsWith($target, ['/'])) { return $target; } $appHost = parse_url($fallback, PHP_URL_HOST); $targetHost = parse_url($target, PHP_URL_HOST); if ($appHost && $targetHost && Str::lower($appHost) === Str::lower($targetHost)) { return $target; } return $fallback; } }