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

112 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Esun\SeventeenTrack\Model;
use Magento\Framework\Exception\AuthenticationException;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Serialize\Serializer\Json;
use Psr\Log\LoggerInterface;
class WebhookProcessor
{
private const MAX_BODY_BYTES = 2097152;
public function __construct(
private readonly Config $config,
private readonly TrackingRepository $tracking,
private readonly DeliveryStatusUpdater $deliveryStatusUpdater,
private readonly Json $json,
private readonly LoggerInterface $logger
) {
}
/** @return array{event:string,tracking_number:string,status:string,message:string} */
public function execute(string $rawBody, string $signature): array
{
$apiKey = $this->config->getApiKey();
if (!$this->config->isEnabled() || $apiKey === '') {
throw new \RuntimeException('17TRACK is disabled or the API key is missing.');
}
if ($rawBody === '' || strlen($rawBody) > self::MAX_BODY_BYTES) {
throw new InputException(__('The 17TRACK webhook body is empty or too large.'));
}
$this->verifySignature($rawBody, $signature, $apiKey);
try {
$payload = $this->json->unserialize($rawBody);
} catch (\InvalidArgumentException $exception) {
throw new InputException(__('The 17TRACK webhook body is not valid JSON.'), $exception);
}
if (!is_array($payload) || !is_array($payload['data'] ?? null)) {
throw new InputException(__('The 17TRACK webhook payload is missing data.'));
}
$event = (string)($payload['event'] ?? '');
$data = $payload['data'];
$number = trim((string)($data['number'] ?? ''));
if ($number === '') {
throw new InputException(__('The 17TRACK webhook payload is missing a tracking number.'));
}
if ($event === 'TRACKING_UPDATED') {
$orderIds = $this->tracking->saveTrackingInfo(
$number,
$data,
$this->json->serialize($data)
);
if ($orderIds === []) {
$this->logger->warning('Ignored a 17TRACK webhook for an unknown tracking number.', [
'tracking_number' => $number,
]);
return [
'event' => $event,
'tracking_number' => $number,
'status' => 'ignored',
'message' => 'Unknown tracking number.',
];
}
$this->deliveryStatusUpdater->execute($orderIds);
return [
'event' => $event,
'tracking_number' => $number,
'status' => 'success',
'message' => 'Tracking update processed.',
];
}
if ($event === 'TRACKING_STOPPED') {
$this->tracking->saveStoppedInfo($number, $this->json->serialize($data));
return [
'event' => $event,
'tracking_number' => $number,
'status' => 'success',
'message' => 'Tracking stopped event processed.',
];
}
$this->logger->info('Ignored an unsupported 17TRACK webhook event.', [
'event' => $event,
'tracking_number' => $number,
]);
return [
'event' => $event,
'tracking_number' => $number,
'status' => 'ignored',
'message' => 'Unsupported webhook event.',
];
}
private function verifySignature(string $rawBody, string $signature, string $apiKey): void
{
$signature = strtolower(trim($signature));
if (!preg_match('/^[a-f0-9]{64}$/', $signature)) {
throw new AuthenticationException(__('The 17TRACK webhook signature is missing or invalid.'));
}
$expected = hash('sha256', $rawBody . '/' . $apiKey);
if (!hash_equals($expected, $signature)) {
throw new AuthenticationException(__('The 17TRACK webhook signature is missing or invalid.'));
}
}
}