find($paymentId); if (! $payment) { throw new \RuntimeException('Payment not found.'); } if (! $this->sdkAvailable()) { return [ 'redirect_url' => $this->getRedirectUrl(), 'mode' => 'hosted', 'message' => 'PayPal SDK not installed; using hosted redirect URL.', ]; } try { $apiContext = $this->buildApiContext(); $payer = new Payer; $payer->setPaymentMethod('paypal'); $amount = new Amount; $amount->setCurrency('USD')->setTotal($payment->balance ?? $payment->total_amount ?? 0); $transaction = new Transaction; $transaction->setAmount($amount) ->setDescription('Payment for school fees') ->setInvoiceNumber(uniqid('inv_', true)); $paypalPayment = new \PayPal\Api\Payment; $paypalPayment->setIntent('sale') ->setPayer($payer) ->setTransactions([$transaction]); $redirectUrls = new RedirectUrls; $redirectUrls->setReturnUrl($this->urls->paypalExecuteReturnUrl()) ->setCancelUrl($this->urls->paypalCancelUrl()); $paypalPayment->setRedirectUrls($redirectUrls); $paypalPayment->create($apiContext); Cache::put($this->cacheKey((string) $paypalPayment->getId()), $paymentId, now()->addHours(2)); return [ 'redirect_url' => $paypalPayment->getApprovalLink(), 'mode' => 'sdk', 'paypal_payment_id' => $paypalPayment->getId(), 'payment_id' => $paymentId, ]; } catch (\Throwable $e) { Log::error('PayPal create payment failed: '.$e->getMessage()); throw new \RuntimeException('Unable to create PayPal payment.'); } } public function executePayment(string $payerId, ?string $paypalPaymentId = null, ?int $paymentId = null): void { if (! $this->sdkAvailable()) { throw new \RuntimeException('PayPal SDK not installed.'); } $paypalId = $paypalPaymentId ?: ''; if ($paypalId === '') { throw new \RuntimeException('Missing PayPal payment ID.'); } $apiContext = $this->buildApiContext(); $payment = \PayPal\Api\Payment::get($paypalId, $apiContext); $execution = new PaymentExecution; $execution->setPayerId($payerId); $payment->execute($execution, $apiContext); $internalPaymentId = $paymentId; if (! $internalPaymentId && $paypalId !== '') { $internalPaymentId = (int) Cache::get($this->cacheKey($paypalId)); } if ($internalPaymentId) { Payment::query()->whereKey($internalPaymentId)->update(['status' => 'Completed']); if ($paypalId !== '') { Cache::forget($this->cacheKey($paypalId)); } } } public function cancelPayment(): void { // no-op (client handles redirect) } private function sdkAvailable(): bool { return class_exists(ApiContext::class) && class_exists(OAuthTokenCredential::class); } private function buildApiContext(): ApiContext { $clientId = (string) (config('services.paypal.client_id') ?: env('PAYPAL_CLIENT_ID')); $secret = (string) (config('services.paypal.secret') ?: env('PAYPAL_SECRET')); $mode = (string) (config('services.paypal.mode') ?: env('PAYPAL_MODE') ?: 'sandbox'); $apiContext = new ApiContext( new OAuthTokenCredential($clientId, $secret) ); $apiContext->setConfig([ 'mode' => $mode, 'http.headers' => ['Connection' => 'Close'], ]); return $apiContext; } private function cacheKey(string $paypalPaymentId): string { return 'paypal_payment:'.$paypalPaymentId; } }