Files
2026-09-15 10:35:56 +08:00

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)),
]);
}
}