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 \PayPal\Api\Payer(); $payer->setPaymentMethod('paypal'); $amount = new \PayPal\Api\Amount(); $amount->setCurrency('USD')->setTotal($payment->balance ?? $payment->total_amount ?? 0); $transaction = new \PayPal\Api\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 \PayPal\Api\RedirectUrls(); $redirectUrls->setReturnUrl(url('/api/v1/finance/paypal/execute')) ->setCancelUrl(url('/api/v1/finance/paypal/cancel')); $paypalPayment->setRedirectUrls($redirectUrls); $paypalPayment->create($apiContext); session()->put('paypalPaymentId', $paypalPayment->getId()); session()->put('paymentId', $paymentId); return [ 'redirect_url' => $paypalPayment->getApprovalLink(), 'mode' => 'sdk', ]; } 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): void { if (!$this->sdkAvailable()) { throw new \RuntimeException('PayPal SDK not installed.'); } $paymentId = $paypalPaymentId ?: (string) session()->get('paypalPaymentId'); if (!$paymentId) { throw new \RuntimeException('Missing PayPal payment ID.'); } $apiContext = $this->buildApiContext(); $payment = \PayPal\Api\Payment::get($paymentId, $apiContext); $execution = new \PayPal\Api\PaymentExecution(); $execution->setPayerId($payerId); $payment->execute($execution, $apiContext); $internalPaymentId = (int) session()->get('paymentId'); if ($internalPaymentId) { Payment::query()->whereKey($internalPaymentId)->update(['status' => 'Completed']); } } public function cancelPayment(): void { // no-op (client handles redirect) } private function sdkAvailable(): bool { return class_exists(\PayPal\Rest\ApiContext::class) && class_exists(\PayPal\Auth\OAuthTokenCredential::class); } private function buildApiContext(): \PayPal\Rest\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 \PayPal\Rest\ApiContext( new \PayPal\Auth\OAuthTokenCredential($clientId, $secret) ); $apiContext->setConfig([ 'mode' => $mode, 'http.headers' => ['Connection' => 'Close'], ]); return $apiContext; } }