init
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
<?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.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user