105 lines
4.2 KiB
PHP
105 lines
4.2 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace Esun\SeventeenTrack\Controller\Webhook;
|
|
|
|
use Esun\SeventeenTrack\Model\ApiLogWriter;
|
|
use Esun\SeventeenTrack\Model\WebhookProcessor;
|
|
use Magento\Framework\App\Action\HttpPostActionInterface;
|
|
use Magento\Framework\App\CsrfAwareActionInterface;
|
|
use Magento\Framework\App\Request\Http;
|
|
use Magento\Framework\App\Request\InvalidRequestException;
|
|
use Magento\Framework\App\RequestInterface;
|
|
use Magento\Framework\Controller\Result\Json;
|
|
use Magento\Framework\Controller\Result\JsonFactory;
|
|
use Magento\Framework\Exception\AuthenticationException;
|
|
use Magento\Framework\Exception\InputException;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
class Index implements HttpPostActionInterface, CsrfAwareActionInterface
|
|
{
|
|
public function __construct(
|
|
private readonly Http $request,
|
|
private readonly JsonFactory $jsonFactory,
|
|
private readonly WebhookProcessor $processor,
|
|
private readonly ApiLogWriter $apiLog,
|
|
private readonly LoggerInterface $logger
|
|
) {
|
|
}
|
|
|
|
public function execute(): Json
|
|
{
|
|
$result = $this->jsonFactory->create();
|
|
$rawBody = (string)$this->request->getContent();
|
|
$startedAt = microtime(true);
|
|
$context = $this->apiLog->getWebhookContext($rawBody);
|
|
try {
|
|
$processingResult = $this->processor->execute(
|
|
$rawBody,
|
|
(string)$this->request->getHeader('sign')
|
|
);
|
|
$this->writeWebhookLog($context + $processingResult, $rawBody, 200, $startedAt);
|
|
return $result->setData(['success' => true]);
|
|
} catch (AuthenticationException $exception) {
|
|
$this->logger->warning('Rejected a 17TRACK webhook with an invalid signature.');
|
|
$this->writeWebhookLog($context + [
|
|
'status' => 'error',
|
|
'message' => $exception->getMessage(),
|
|
], $rawBody, 401, $startedAt);
|
|
return $result->setHttpResponseCode(401)->setData([
|
|
'success' => false,
|
|
'message' => $exception->getMessage(),
|
|
]);
|
|
} catch (InputException $exception) {
|
|
$this->writeWebhookLog($context + [
|
|
'status' => 'error',
|
|
'message' => $exception->getMessage(),
|
|
], $rawBody, 400, $startedAt);
|
|
return $result->setHttpResponseCode(400)->setData([
|
|
'success' => false,
|
|
'message' => $exception->getMessage(),
|
|
]);
|
|
} catch (\Throwable $exception) {
|
|
$this->logger->critical('Unable to process a 17TRACK webhook.', ['exception' => $exception]);
|
|
$this->writeWebhookLog($context + [
|
|
'status' => 'error',
|
|
'message' => $exception->getMessage(),
|
|
], $rawBody, 500, $startedAt);
|
|
return $result->setHttpResponseCode(500)->setData([
|
|
'success' => false,
|
|
'message' => (string)__('Unable to process the webhook.'),
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function createCsrfValidationException(RequestInterface $request): ?InvalidRequestException
|
|
{
|
|
return null;
|
|
}
|
|
|
|
public function validateForCsrf(RequestInterface $request): ?bool
|
|
{
|
|
// 17TRACK cannot know Magento's form key; authenticity is verified with its signed raw request body.
|
|
return true;
|
|
}
|
|
|
|
private function writeWebhookLog(array $data, string $rawBody, int $httpStatus, float $startedAt): void
|
|
{
|
|
$this->apiLog->write([
|
|
'direction' => 'inbound',
|
|
'operation' => 'webhook',
|
|
'api_path' => $this->apiLog->getWebhookPath(),
|
|
'order_id' => $data['order_id'] ?? null,
|
|
'order_number' => $data['order_number'] ?? null,
|
|
'tracking_number' => $data['tracking_number'] ?? null,
|
|
'event' => $data['event'] ?? null,
|
|
'status' => (string)($data['status'] ?? 'error'),
|
|
'http_status' => $httpStatus,
|
|
'message' => (string)($data['message'] ?? ''),
|
|
'request_data' => $rawBody,
|
|
'response_data' => ['success' => $httpStatus === 200],
|
|
'duration_ms' => max(0, (int)round((microtime(true) - $startedAt) * 1000)),
|
|
]);
|
|
}
|
|
}
|