149 lines
4.7 KiB
PHP
149 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Payments;
|
|
|
|
use App\Models\Payment;
|
|
use App\Services\ApplicationUrlService;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
use PayPal\Api\Amount;
|
|
use PayPal\Api\Payer;
|
|
use PayPal\Api\PaymentExecution;
|
|
use PayPal\Api\RedirectUrls;
|
|
use PayPal\Api\Transaction;
|
|
use PayPal\Auth\OAuthTokenCredential;
|
|
use PayPal\Rest\ApiContext;
|
|
|
|
class PaypalPaymentService
|
|
{
|
|
public function __construct(
|
|
private ApplicationUrlService $urls,
|
|
) {}
|
|
|
|
public function getRedirectUrl(): string
|
|
{
|
|
return (string) (config('services.paypal.payment_url')
|
|
?: env('PAYPAL_PAYMENT_URL')
|
|
?: 'https://www.paypal.com/ncp/payment/87FJL3EV8C7NE');
|
|
}
|
|
|
|
public function createPayment(int $paymentId): array
|
|
{
|
|
$payment = Payment::query()->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;
|
|
}
|
|
}
|