resolveOrderId($request); $fallback = $this->resolveFallbackUrl(); if (! $orderId) { return redirect()->to($fallback); } $addon = EventPackageAddon::query() ->where('checkout_id', $orderId) ->first(); if (! $addon) { return redirect()->to($fallback); } $successUrl = Arr::get($addon->metadata ?? [], 'paypal_success_url') ?? Arr::get($addon->metadata ?? [], 'success_url'); $cancelUrl = Arr::get($addon->metadata ?? [], 'paypal_cancel_url') ?? Arr::get($addon->metadata ?? [], 'cancel_url'); if ($addon->status === 'completed') { return redirect()->to($this->resolveSafeRedirect($successUrl, $fallback)); } try { $capture = $this->orders->captureOrder($orderId, [ 'request_id' => 'addon-'.$addon->id, ]); } catch (PayPalException $exception) { $this->addons->fail($addon, 'paypal_capture_failed', [ 'message' => $exception->getMessage(), 'status' => $exception->status(), ]); return redirect()->to($this->resolveSafeRedirect($cancelUrl, $fallback)); } $captureId = $this->resolveCaptureId($capture); $totals = $this->resolveTotals($capture); $this->addons->complete( $addon, $capture, $captureId, $orderId, $totals['total'] ?? null, $totals['currency'] ?? null, [ 'paypal_order_id' => $orderId, 'paypal_capture_id' => $captureId, 'paypal_status' => $capture['status'] ?? null, 'paypal_totals' => $totals ?: null, 'paypal_captured_at' => now()->toIso8601String(), ], ); return redirect()->to($this->resolveSafeRedirect($successUrl, $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; } /** * @param array $capture */ protected function resolveCaptureId(array $capture): ?string { $captureId = Arr::get($capture, 'purchase_units.0.payments.captures.0.id') ?? Arr::get($capture, 'id'); return is_string($captureId) && $captureId !== '' ? $captureId : null; } /** * @param array $capture * @return array{currency?: string, total?: float} */ protected function resolveTotals(array $capture): array { $amount = Arr::get($capture, 'purchase_units.0.payments.captures.0.amount') ?? Arr::get($capture, 'purchase_units.0.amount'); if (! is_array($amount)) { return []; } $currency = Arr::get($amount, 'currency_code'); $total = Arr::get($amount, 'value'); return array_filter([ 'currency' => is_string($currency) ? strtoupper($currency) : null, 'total' => is_numeric($total) ? (float) $total : null, ], static fn ($value) => $value !== null); } }