163 lines
6.1 KiB
PHP
Executable File
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),
|
|
]);
|
|
}
|
|
}
|