Files
alrahma_sunday_school_api/app/Http/Controllers/Api/PaypalWebhook.php
T
2026-03-05 12:29:37 -05:00

163 lines
6.1 KiB
PHP
Executable File

<?php
namespace App\Http\Controllers\Api;
use App\Models\Configuration;
use App\Models\PayPalPayment;
use Illuminate\Support\Facades\Http;
use Symfony\Component\HttpFoundation\Response;
class PaypalWebhook extends BaseApiController
{
protected Configuration $config;
protected PayPalPayment $payment;
public function __construct()
{
parent::__construct();
$this->config = model(Configuration::class);
$this->payment = model(PayPalPayment::class);
}
public function handle()
{
$config = $this->loadConfig();
if (!$config) {
return $this->respondError('PayPal configuration incomplete', Response::HTTP_INTERNAL_SERVER_ERROR);
}
$payload = $this->payloadData();
if (empty($payload)) {
return $this->respondError('Invalid JSON payload', Response::HTTP_BAD_REQUEST);
}
$requiredHeaders = [
'paypal-transmission-id',
'paypal-transmission-time',
'paypal-cert-url',
'paypal-auth-algo',
'paypal-transmission-sig',
];
foreach ($requiredHeaders as $header) {
if (!$this->laravelRequest->headers->has($header)) {
return $this->respondError("Missing required header: {$header}", Response::HTTP_BAD_REQUEST);
}
}
$accessToken = $this->getAccessToken($config['client_id'], $config['secret'], $config['token_url']);
if (!$accessToken) {
return $this->respondError('Failed to get access token', Response::HTTP_INTERNAL_SERVER_ERROR);
}
$verificationData = [
'auth_algo' => $this->laravelRequest->headers->get('paypal-auth-algo'),
'cert_url' => $this->laravelRequest->headers->get('paypal-cert-url'),
'transmission_id' => $this->laravelRequest->headers->get('paypal-transmission-id'),
'transmission_sig' => $this->laravelRequest->headers->get('paypal-transmission-sig'),
'transmission_time' => $this->laravelRequest->headers->get('paypal-transmission-time'),
'webhook_id' => $config['webhook_id'],
'webhook_event' => $payload,
];
if (!$this->verifyWebhookSignature($config['verify_url'], $accessToken, $verificationData)) {
return $this->respondError('Webhook verification failed', Response::HTTP_UNAUTHORIZED);
}
try {
$this->processPayment($payload);
return $this->success(['status' => 'success'], 'Webhook processed successfully');
} catch (\Throwable $e) {
log_message('error', 'Payment processing error: ' . $e->getMessage());
return $this->respondError('Payment processing error', Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
protected function loadConfig(): ?array
{
$mode = (string) ($this->config->getConfig('paypal_mode') ?? '');
$configs = [
'sandbox' => [
'webhook_id' => $this->config->getConfig('sandbox_webhookid_paypal'),
'client_id' => $this->config->getConfig('sandbox_clientid_paypal'),
'secret' => $this->config->getConfig('sandbox_secret_paypal'),
'verify_url' => $this->config->getConfig('sandbox_verifylink_paypal'),
'token_url' => $this->config->getConfig('sandbox_tokenurl_paypal'),
],
'production' => [
'webhook_id' => $this->config->getConfig('production_webhookid_paypal'),
'client_id' => $this->config->getConfig('production_clientid_paypal'),
'secret' => $this->config->getConfig('production_secret_paypal'),
'verify_url' => $this->config->getConfig('production_verifylink_paypal'),
'token_url' => $this->config->getConfig('production_tokenurl_paypal'),
],
];
if (!isset($configs[$mode])) {
return null;
}
$cfg = $configs[$mode];
if (in_array(null, $cfg, true) || in_array('', $cfg, true)) {
return null;
}
return $cfg + ['mode' => $mode];
}
protected function getAccessToken(string $clientId, string $secret, string $urlToken): ?string
{
try {
$response = Http::asForm()
->withBasicAuth($clientId, $secret)
->acceptJson()
->post($urlToken, ['grant_type' => 'client_credentials']);
if (!$response->successful()) {
log_message('error', 'Access token fetch failed: HTTP ' . $response->status());
return null;
}
return $response->json('access_token');
} catch (\Throwable $e) {
log_message('error', 'Access token fetch failed: ' . $e->getMessage());
return null;
}
}
protected function verifyWebhookSignature(string $verifyUrl, string $accessToken, array $verificationData): bool
{
try {
$response = Http::withToken($accessToken)
->acceptJson()
->post($verifyUrl, $verificationData);
if (!$response->successful()) {
log_message('error', 'Webhook verification failed: HTTP ' . $response->status());
return false;
}
return ($response->json('verification_status') ?? '') === 'SUCCESS';
} catch (\Throwable $e) {
log_message('error', 'Webhook verification failed: ' . $e->getMessage());
return false;
}
}
protected function processPayment(array $eventData): void
{
$resource = $eventData['resource'] ?? [];
$this->payment->create([
'webhook_id' => $eventData['id'] ?? null,
'transaction_id' => $resource['id'] ?? null,
'status' => $resource['status'] ?? null,
'amount' => $resource['amount']['value'] ?? null,
'currency' => $resource['amount']['currency_code'] ?? null,
'payer_email' => $resource['payer']['email_address'] ?? null,
'raw_payload' => json_encode($eventData),
]);
}
}