211 lines
6.3 KiB
PHP
211 lines
6.3 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Shipments;
|
|
|
|
use App\Core\Http\SslCertificateResolver;
|
|
use App\Modules\Settings\InpostIntegrationRepository;
|
|
use Throwable;
|
|
|
|
final class AllegroTrackingService implements ShipmentTrackingInterface
|
|
{
|
|
private const INPOST_API_PRODUCTION = 'https://api-shipx-pl.easypack24.net/v1';
|
|
private const INPOST_API_SANDBOX = 'https://sandbox-api-shipx-pl.easypack24.net/v1';
|
|
|
|
public function __construct(
|
|
private readonly InpostIntegrationRepository $inpostRepository
|
|
) {
|
|
}
|
|
|
|
public function supports(string $provider): bool
|
|
{
|
|
return $provider === 'allegro_wza';
|
|
}
|
|
|
|
public function getDeliveryStatus(array $package): ?array
|
|
{
|
|
$trackingNumber = trim((string) ($package['tracking_number'] ?? ''));
|
|
if ($trackingNumber === '') {
|
|
return null;
|
|
}
|
|
|
|
$carrierId = strtolower(trim((string) ($package['carrier_id'] ?? '')));
|
|
|
|
if (str_contains($carrierId, 'inpost') || str_contains($carrierId, 'paczkomat')) {
|
|
return $this->fetchInpostStatus($trackingNumber);
|
|
}
|
|
|
|
return $this->fetchAllegroEdgeStatus($trackingNumber);
|
|
}
|
|
|
|
private function fetchInpostStatus(string $trackingNumber): ?array
|
|
{
|
|
try {
|
|
$token = $this->resolveInpostToken();
|
|
if ($token === null) {
|
|
return null;
|
|
}
|
|
|
|
$settings = $this->inpostRepository->getSettings();
|
|
$env = (string) ($settings['environment'] ?? 'sandbox');
|
|
$baseUrl = strtolower(trim($env)) === 'production'
|
|
? self::INPOST_API_PRODUCTION
|
|
: self::INPOST_API_SANDBOX;
|
|
|
|
$url = $baseUrl . '/tracking/' . rawurlencode($trackingNumber);
|
|
$response = $this->apiRequest($url, $token);
|
|
|
|
$details = is_array($response['tracking_details'] ?? null) ? $response['tracking_details'] : [];
|
|
if ($details === []) {
|
|
return null;
|
|
}
|
|
|
|
$rawStatus = strtolower(trim((string) ($details[0]['status'] ?? '')));
|
|
if ($rawStatus === '') {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'status' => DeliveryStatus::normalize('inpost', $rawStatus),
|
|
'status_raw' => $rawStatus,
|
|
'description' => DeliveryStatus::description('inpost', $rawStatus),
|
|
];
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function fetchAllegroEdgeStatus(string $trackingNumber): ?array
|
|
{
|
|
try {
|
|
$url = 'https://edge.allegro.pl/ad/tracking?packageNo=' . rawurlencode($trackingNumber);
|
|
$response = $this->edgeApiRequest($url);
|
|
|
|
$statuses = $response['status'] ?? [];
|
|
if (!is_array($statuses) || $statuses === []) {
|
|
return null;
|
|
}
|
|
|
|
$latest = end($statuses);
|
|
$description = trim((string) ($latest['description'] ?? ''));
|
|
if ($description === '') {
|
|
return null;
|
|
}
|
|
|
|
$slug = DeliveryStatus::slugifyAllegroDescription($description);
|
|
|
|
$normalized = DeliveryStatus::normalize('allegro_edge', $slug);
|
|
|
|
if ($normalized === DeliveryStatus::UNKNOWN) {
|
|
$normalized = DeliveryStatus::guessStatusFromDescription($description);
|
|
|
|
error_log(sprintf(
|
|
'[AllegroTracking] Niezmapowany status: "%s" (slug: %s, guessed: %s)',
|
|
$description,
|
|
$slug,
|
|
$normalized
|
|
));
|
|
}
|
|
|
|
return [
|
|
'status' => $normalized,
|
|
'status_raw' => $description,
|
|
'description' => $description,
|
|
];
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function edgeApiRequest(string $url): array
|
|
{
|
|
$ch = curl_init($url);
|
|
if ($ch === false) {
|
|
return [];
|
|
}
|
|
|
|
$opts = [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 15,
|
|
CURLOPT_CONNECTTIMEOUT => 5,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_SSL_VERIFYHOST => 2,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Accept: application/vnd.allegro.internal.v1+json',
|
|
'Content-Type: application/vnd.allegro.internal.v1+json',
|
|
],
|
|
];
|
|
|
|
$caPath = SslCertificateResolver::resolve();
|
|
if ($caPath !== null) {
|
|
$opts[CURLOPT_CAINFO] = $caPath;
|
|
}
|
|
|
|
curl_setopt_array($ch, $opts);
|
|
$body = curl_exec($ch);
|
|
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$ch = null;
|
|
|
|
if ($body === false || $httpCode < 200 || $httpCode >= 300) {
|
|
return [];
|
|
}
|
|
|
|
$json = json_decode((string) $body, true);
|
|
return is_array($json) ? $json : [];
|
|
}
|
|
|
|
private function resolveInpostToken(): ?string
|
|
{
|
|
try {
|
|
$token = $this->inpostRepository->getDecryptedToken();
|
|
return ($token !== null && trim($token) !== '') ? trim($token) : null;
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function apiRequest(string $url, string $token): array
|
|
{
|
|
$ch = curl_init($url);
|
|
if ($ch === false) {
|
|
return [];
|
|
}
|
|
|
|
$opts = [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 15,
|
|
CURLOPT_CONNECTTIMEOUT => 5,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_SSL_VERIFYHOST => 2,
|
|
CURLOPT_HTTPHEADER => [
|
|
'Authorization: Bearer ' . $token,
|
|
'Accept: application/json',
|
|
],
|
|
];
|
|
|
|
$caPath = SslCertificateResolver::resolve();
|
|
if ($caPath !== null) {
|
|
$opts[CURLOPT_CAINFO] = $caPath;
|
|
}
|
|
|
|
curl_setopt_array($ch, $opts);
|
|
$body = curl_exec($ch);
|
|
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
$ch = null;
|
|
|
|
if ($body === false || $httpCode < 200 || $httpCode >= 300) {
|
|
return [];
|
|
}
|
|
|
|
$json = json_decode((string) $body, true);
|
|
return is_array($json) ? $json : [];
|
|
}
|
|
|
|
}
|