142 lines
4.7 KiB
PHP
142 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\CheckoutSession;
|
|
use App\Services\Checkout\CheckoutAssignmentService;
|
|
use App\Services\Checkout\CheckoutSessionService;
|
|
use App\Services\Coupons\CouponRedemptionService;
|
|
use App\Services\PayPal\Exceptions\PayPalException;
|
|
use App\Services\PayPal\PayPalOrderService;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
|
|
class PayPalReturnController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly PayPalOrderService $orders,
|
|
private readonly CheckoutSessionService $sessions,
|
|
private readonly CheckoutAssignmentService $assignment,
|
|
private readonly CouponRedemptionService $couponRedemptions,
|
|
) {}
|
|
|
|
public function __invoke(Request $request): RedirectResponse
|
|
{
|
|
$orderId = $this->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;
|
|
}
|
|
}
|