Files
orderPRO/src/Modules/Settings/AllegroOrderImportService.php
Jacek Pyziak 5ab87a5a20 feat(07-pre-expansion-fixes): complete phase 07 — milestone v0.2 done
Phase 7 complete (5 plans):
- 07-01: Performance (N+1→LEFT JOIN, static cache, DB indexes)
- 07-02: Stability (SSL verification, cron throttle DB, migration 000014b)
- 07-03: UX (orderpro_to_allegro disable, lista zamówień fixes, SSL hotfix)
- 07-04: Tests (12 unit tests for AllegroTokenManager + AllegroOrderImportService)
- 07-05: InPost ShipX API (natywny provider, workaround remap usunięty)

Additional fixes:
- 5 broken use-statements fixed across 4 files
- vendor/ excluded from ftp-kr auto-upload
- PHPUnit + dg/bypass-finals infrastructure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 00:37:21 +01:00

712 lines
29 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Settings;
use App\Core\Support\StringHelper;
use App\Modules\Orders\OrderImportRepository;
use App\Modules\Orders\OrdersRepository;
use App\Core\Constants\IntegrationSources;
use App\Core\Exceptions\AllegroApiException;
use RuntimeException;
use Throwable;
final class AllegroOrderImportService
{
public function __construct(
private readonly AllegroIntegrationRepository $integrationRepository,
private readonly AllegroTokenManager $tokenManager,
private readonly AllegroApiClient $apiClient,
private readonly OrderImportRepository $orders,
private readonly AllegroStatusMappingRepository $statusMappings,
private readonly OrdersRepository $ordersRepository
) {
}
/**
* @return array<string, mixed>
*/
public function importSingleOrder(string $checkoutFormId): array
{
$orderId = trim($checkoutFormId);
if ($orderId === '') {
throw new AllegroApiException('Podaj ID zamowienia Allegro.');
}
[$accessToken, $env] = $this->tokenManager->resolveToken();
try {
$payload = $this->apiClient->getCheckoutForm($env, $accessToken, $orderId);
} catch (RuntimeException $exception) {
if (trim($exception->getMessage()) !== 'ALLEGRO_HTTP_401') {
throw $exception;
}
[$accessToken, $env] = $this->tokenManager->resolveToken();
$payload = $this->apiClient->getCheckoutForm($env, $accessToken, $orderId);
}
$mapped = $this->mapCheckoutFormPayload($payload, $env, $accessToken);
$saveResult = $this->orders->upsertOrderAggregate(
$mapped['order'],
$mapped['addresses'],
$mapped['items'],
$mapped['payments'],
$mapped['shipments'],
$mapped['notes'],
$mapped['status_history']
);
$savedOrderId = (int) ($saveResult['order_id'] ?? 0);
$wasCreated = !empty($saveResult['created']);
if ($savedOrderId > 0) {
$summary = $wasCreated
? 'Zaimportowano zamowienie z Allegro'
: 'Zaktualizowano zamowienie z Allegro (re-import)';
$this->ordersRepository->recordActivity(
$savedOrderId,
'import',
$summary,
[
'source' => IntegrationSources::ALLEGRO,
'source_order_id' => trim($checkoutFormId),
'created' => $wasCreated,
],
'import',
'Allegro'
);
}
return [
'order_id' => $savedOrderId,
'created' => $wasCreated,
'source_order_id' => (string) ($mapped['order']['source_order_id'] ?? ''),
'image_diagnostics' => (array) ($mapped['image_diagnostics'] ?? []),
];
}
/**
* @param array<string, mixed> $payload
* @return array{
* order:array<string, mixed>,
* addresses:array<int, array<string, mixed>>,
* items:array<int, array<string, mixed>>,
* image_diagnostics:array<string, mixed>,
* payments:array<int, array<string, mixed>>,
* shipments:array<int, array<string, mixed>>,
* notes:array<int, array<string, mixed>>,
* status_history:array<int, array<string, mixed>>
* }
*/
private function mapCheckoutFormPayload(array $payload, string $environment, string $accessToken): array
{
$checkoutFormId = trim((string) ($payload['id'] ?? ''));
if ($checkoutFormId === '') {
throw new AllegroApiException('Odpowiedz Allegro nie zawiera ID zamowienia.');
}
$status = trim((string) ($payload['status'] ?? ''));
$fulfillmentStatus = trim((string) ($payload['fulfillment']['status'] ?? ''));
$rawAllegroStatus = strtolower($fulfillmentStatus !== '' ? $fulfillmentStatus : $status);
$mappedOrderproStatus = $this->statusMappings->findMappedOrderproStatusCode($rawAllegroStatus);
$externalStatus = $mappedOrderproStatus !== null ? $mappedOrderproStatus : $rawAllegroStatus;
$paymentStatusRaw = strtolower(trim((string) ($payload['payment']['status'] ?? '')));
$totalWithTax = $this->amountToFloat($payload['summary']['totalToPay'] ?? null);
$totalPaid = $this->amountToFloat($payload['summary']['paidAmount'] ?? null);
if ($totalPaid === null) {
$totalPaid = $this->amountToFloat($payload['payment']['paidAmount'] ?? null);
}
if ($totalPaid === null) {
$totalPaid = $this->amountToFloat($payload['payment']['amount'] ?? null);
}
$currency = trim((string) ($payload['summary']['totalToPay']['currency'] ?? ''));
if ($currency === '') {
$currency = trim((string) ($payload['payment']['amount']['currency'] ?? 'PLN'));
}
if ($currency === '') {
$currency = 'PLN';
}
$buyer = is_array($payload['buyer'] ?? null) ? $payload['buyer'] : [];
$delivery = is_array($payload['delivery'] ?? null) ? $payload['delivery'] : [];
$invoice = is_array($payload['invoice'] ?? null) ? $payload['invoice'] : [];
$payment = is_array($payload['payment'] ?? null) ? $payload['payment'] : [];
$lineItems = is_array($payload['lineItems'] ?? null) ? $payload['lineItems'] : [];
$deliveryMethod = is_array($delivery['method'] ?? null) ? $delivery['method'] : [];
$deliveryMethodId = trim((string) ($deliveryMethod['id'] ?? ''));
$deliveryMethodName = trim((string) ($deliveryMethod['name'] ?? ''));
$deliveryForm = $deliveryMethodName !== '' ? $deliveryMethodName : $deliveryMethodId;
$deliveryTime = is_array($delivery['time'] ?? null) ? $delivery['time'] : [];
$dispatchTime = is_array($deliveryTime['dispatch'] ?? null) ? $deliveryTime['dispatch'] : [];
$sendDateMin = StringHelper::normalizeDateTime((string) ($dispatchTime['from'] ?? ''));
$sendDateMax = StringHelper::normalizeDateTime((string) ($dispatchTime['to'] ?? ''));
if ($sendDateMin === null) {
$sendDateMin = StringHelper::normalizeDateTime((string) ($deliveryTime['from'] ?? ''));
}
if ($sendDateMax === null) {
$sendDateMax = StringHelper::normalizeDateTime((string) ($deliveryTime['to'] ?? ''));
}
$boughtAt = StringHelper::normalizeDateTime((string) ($payload['boughtAt'] ?? ''));
$updatedAt = StringHelper::normalizeDateTime((string) ($payload['updatedAt'] ?? ''));
$fetchedAt = date('Y-m-d H:i:s');
$order = [
'integration_id' => $this->integrationRepository->getActiveIntegrationId(),
'source' => IntegrationSources::ALLEGRO,
'source_order_id' => $checkoutFormId,
'external_order_id' => $checkoutFormId,
'external_platform_id' => trim((string) ($payload['marketplace']['id'] ?? 'allegro-pl')),
'external_platform_account_id' => null,
'external_status_id' => $externalStatus,
'external_payment_type_id' => trim((string) ($payment['type'] ?? '')),
'payment_status' => $this->mapPaymentStatus($paymentStatusRaw),
'external_carrier_id' => $deliveryForm !== '' ? $deliveryForm : null,
'external_carrier_account_id' => $deliveryMethodId !== '' ? $deliveryMethodId : null,
'customer_login' => trim((string) ($buyer['login'] ?? '')),
'is_invoice' => !empty($invoice['required']),
'is_encrypted' => false,
'is_canceled_by_buyer' => in_array($externalStatus, ['cancelled', 'canceled'], true),
'currency' => strtoupper($currency),
'total_without_tax' => null,
'total_with_tax' => $totalWithTax,
'total_paid' => $totalPaid,
'send_date_min' => $sendDateMin,
'send_date_max' => $sendDateMax,
'ordered_at' => $boughtAt,
'source_created_at' => $boughtAt,
'source_updated_at' => $updatedAt,
'preferences_json' => [
'status' => $status,
'fulfillment_status' => $fulfillmentStatus,
'allegro_status_raw' => $rawAllegroStatus,
'payment_status' => $paymentStatusRaw,
'delivery_method_name' => $deliveryMethodName,
'delivery_method_id' => $deliveryMethodId,
'delivery_cost' => $delivery['cost'] ?? null,
'delivery_time' => $deliveryTime,
],
'payload_json' => $payload,
'fetched_at' => $fetchedAt,
];
$addresses = $this->buildAddresses($buyer, $delivery, $invoice);
$itemsResult = $this->buildItems($lineItems, $environment, $accessToken);
$items = (array) ($itemsResult['items'] ?? []);
$payments = $this->buildPayments($payment, $currency);
$apiShipments = [];
try {
$apiShipments = $this->apiClient->getCheckoutFormShipments($environment, $accessToken, $checkoutFormId);
} catch (Throwable) {
}
$shipments = $this->buildShipments($apiShipments, $delivery);
$notes = $this->buildNotes($payload);
$statusHistory = [[
'from_status_id' => null,
'to_status_id' => $externalStatus !== '' ? $externalStatus : 'unknown',
'changed_at' => $updatedAt !== null ? $updatedAt : $fetchedAt,
'change_source' => 'import',
'comment' => 'Import z Allegro checkout form',
'payload_json' => [
'status' => $status,
'fulfillment_status' => $fulfillmentStatus,
'allegro_status_raw' => $rawAllegroStatus,
],
]];
return [
'order' => $order,
'addresses' => $addresses,
'items' => $items,
'image_diagnostics' => (array) ($itemsResult['image_diagnostics'] ?? []),
'payments' => $payments,
'shipments' => $shipments,
'notes' => $notes,
'status_history' => $statusHistory,
];
}
/**
* @param array<string, mixed> $buyer
* @param array<string, mixed> $delivery
* @param array<string, mixed> $invoice
* @return array<int, array<string, mixed>>
*/
private function buildAddresses(array $buyer, array $delivery, array $invoice): array
{
$result = [];
$customerName = trim((string) (($buyer['firstName'] ?? '') . ' ' . ($buyer['lastName'] ?? '')));
if ($customerName === '') {
$customerName = trim((string) ($buyer['login'] ?? ''));
}
if ($customerName === '') {
$customerName = 'Kupujacy Allegro';
}
$result[] = [
'address_type' => 'customer',
'name' => $customerName,
'phone' => StringHelper::nullableString((string) ($buyer['phoneNumber'] ?? '')),
'email' => StringHelper::nullableString((string) ($buyer['email'] ?? '')),
'street_name' => null,
'street_number' => null,
'city' => null,
'zip_code' => null,
'country' => null,
'department' => null,
'parcel_external_id' => null,
'parcel_name' => null,
'address_class' => null,
'company_tax_number' => null,
'company_name' => null,
'payload_json' => $buyer,
];
$deliveryAddress = is_array($delivery['address'] ?? null) ? $delivery['address'] : [];
$pickupPoint = is_array($delivery['pickupPoint'] ?? null) ? $delivery['pickupPoint'] : [];
$pickupAddress = is_array($pickupPoint['address'] ?? null) ? $pickupPoint['address'] : [];
if ($deliveryAddress !== [] || $pickupAddress !== []) {
$isPickupPointDelivery = $pickupAddress !== [];
// Always use recipient's personal data from delivery.address for name/phone/email.
// For pickup points, delivery.address still holds the recipient's data (not the machine location).
$name = $this->fallbackName($deliveryAddress, 'Dostawa');
$street = $isPickupPointDelivery
? StringHelper::nullableString((string) ($pickupAddress['street'] ?? ''))
: StringHelper::nullableString((string) ($deliveryAddress['street'] ?? ''));
$city = $isPickupPointDelivery
? StringHelper::nullableString((string) ($pickupAddress['city'] ?? ''))
: StringHelper::nullableString((string) ($deliveryAddress['city'] ?? ''));
$zipCode = $isPickupPointDelivery
? StringHelper::nullableString((string) ($pickupAddress['zipCode'] ?? ''))
: StringHelper::nullableString((string) ($deliveryAddress['zipCode'] ?? ''));
$country = $isPickupPointDelivery
? StringHelper::nullableString((string) ($pickupAddress['countryCode'] ?? ''))
: StringHelper::nullableString((string) ($deliveryAddress['countryCode'] ?? ''));
$deliveryPhone = trim((string) ($deliveryAddress['phoneNumber'] ?? ''));
$buyerPhone = trim((string) ($buyer['phoneNumber'] ?? ''));
$result[] = [
'address_type' => 'delivery',
'name' => $name,
'phone' => StringHelper::nullableString($deliveryPhone !== '' ? $deliveryPhone : $buyerPhone),
'email' => StringHelper::nullableString((string) ($deliveryAddress['email'] ?? $buyer['email'] ?? '')),
'street_name' => $street,
'street_number' => null,
'city' => $city,
'zip_code' => $zipCode,
'country' => $country,
'department' => null,
'parcel_external_id' => StringHelper::nullableString((string) ($pickupPoint['id'] ?? '')),
'parcel_name' => StringHelper::nullableString((string) ($pickupPoint['name'] ?? '')),
'address_class' => null,
'company_tax_number' => null,
'company_name' => StringHelper::nullableString((string) ($deliveryAddress['companyName'] ?? '')),
'payload_json' => [
'address' => $deliveryAddress,
'pickup_point' => $pickupPoint,
],
];
}
$invoiceAddress = is_array($invoice['address'] ?? null) ? $invoice['address'] : [];
if ($invoiceAddress !== []) {
$result[] = [
'address_type' => 'invoice',
'name' => $this->fallbackName($invoiceAddress, 'Faktura'),
'phone' => StringHelper::nullableString((string) ($invoiceAddress['phoneNumber'] ?? '')),
'email' => StringHelper::nullableString((string) ($invoiceAddress['email'] ?? '')),
'street_name' => StringHelper::nullableString((string) ($invoiceAddress['street'] ?? '')),
'street_number' => null,
'city' => StringHelper::nullableString((string) ($invoiceAddress['city'] ?? '')),
'zip_code' => StringHelper::nullableString((string) ($invoiceAddress['zipCode'] ?? '')),
'country' => StringHelper::nullableString((string) ($invoiceAddress['countryCode'] ?? '')),
'department' => null,
'parcel_external_id' => null,
'parcel_name' => null,
'address_class' => null,
'company_tax_number' => StringHelper::nullableString((string) ($invoiceAddress['taxId'] ?? '')),
'company_name' => StringHelper::nullableString((string) ($invoiceAddress['companyName'] ?? '')),
'payload_json' => $invoiceAddress,
];
}
return $result;
}
/**
* @param array<int, mixed> $lineItems
* @return array{
* items:array<int, array<string, mixed>>,
* image_diagnostics:array<string, mixed>
* }
*/
private function buildItems(array $lineItems, string $environment, string $accessToken): array
{
$result = [];
$offerImageCache = [];
$diagnostics = [
'total_items' => 0,
'with_image' => 0,
'without_image' => 0,
'source_counts' => [
'checkout_form' => 0,
'offer_api' => 0,
],
'reason_counts' => [],
'sample_issues' => [],
];
$sortOrder = 0;
foreach ($lineItems as $itemRaw) {
if (!is_array($itemRaw)) {
continue;
}
$diagnostics['total_items'] = (int) $diagnostics['total_items'] + 1;
$offer = is_array($itemRaw['offer'] ?? null) ? $itemRaw['offer'] : [];
$name = trim((string) ($offer['name'] ?? ''));
if ($name === '') {
$name = 'Pozycja Allegro';
}
$offerId = trim((string) ($offer['id'] ?? ''));
$mediaUrl = $this->extractLineItemImageUrl($itemRaw);
$imageSource = 'none';
$missingReason = null;
if ($mediaUrl === null && $offerId !== '') {
$offerImageResult = $this->resolveOfferImageUrlFromApi($offerId, $environment, $accessToken, $offerImageCache);
$mediaUrl = $offerImageResult['url'];
if ($mediaUrl !== null) {
$imageSource = 'offer_api';
} else {
$missingReason = $offerImageResult['reason'];
}
} elseif ($mediaUrl === null) {
$missingReason = 'missing_offer_id';
} else {
$imageSource = 'checkout_form';
}
if ($mediaUrl !== null) {
$diagnostics['with_image'] = (int) $diagnostics['with_image'] + 1;
if ($imageSource === 'offer_api') {
$diagnostics['source_counts']['offer_api'] = (int) ($diagnostics['source_counts']['offer_api'] ?? 0) + 1;
} else {
$diagnostics['source_counts']['checkout_form'] = (int) ($diagnostics['source_counts']['checkout_form'] ?? 0) + 1;
}
} else {
$diagnostics['without_image'] = (int) $diagnostics['without_image'] + 1;
$reasonCode = $missingReason ?? 'missing_in_checkout_form';
$reasonCounts = is_array($diagnostics['reason_counts']) ? $diagnostics['reason_counts'] : [];
$reasonCounts[$reasonCode] = (int) ($reasonCounts[$reasonCode] ?? 0) + 1;
$diagnostics['reason_counts'] = $reasonCounts;
$sampleIssues = is_array($diagnostics['sample_issues']) ? $diagnostics['sample_issues'] : [];
if (count($sampleIssues) < 5) {
$sampleIssues[] = [
'offer_id' => $offerId,
'name' => $name,
'reason' => $reasonCode,
];
}
$diagnostics['sample_issues'] = $sampleIssues;
}
$result[] = [
'source_item_id' => StringHelper::nullableString((string) ($itemRaw['id'] ?? '')),
'external_item_id' => StringHelper::nullableString((string) ($offer['id'] ?? '')),
'ean' => null,
'sku' => null,
'original_name' => $name,
'original_code' => StringHelper::nullableString((string) ($offer['id'] ?? '')),
'original_price_with_tax' => $this->amountToFloat($itemRaw['originalPrice'] ?? null),
'original_price_without_tax' => null,
'media_url' => $mediaUrl,
'quantity' => (float) ($itemRaw['quantity'] ?? 1),
'tax_rate' => null,
'item_status' => null,
'unit' => 'pcs',
'item_type' => 'product',
'source_product_id' => StringHelper::nullableString((string) ($offer['id'] ?? '')),
'source_product_set_id' => null,
'sort_order' => $sortOrder++,
'payload_json' => $itemRaw,
];
}
return [
'items' => $result,
'image_diagnostics' => $diagnostics,
];
}
/**
* @param array<string, array{url:?string, reason:?string}> $offerImageCache
* @return array{url:?string, reason:?string}
*/
private function resolveOfferImageUrlFromApi(
string $offerId,
string $environment,
string $accessToken,
array &$offerImageCache
): array {
if (array_key_exists($offerId, $offerImageCache)) {
return $offerImageCache[$offerId];
}
try {
$offerPayload = $this->apiClient->getProductOffer($environment, $accessToken, $offerId);
$url = $this->extractOfferImageUrl($offerPayload);
if ($url !== null) {
$offerImageCache[$offerId] = ['url' => $url, 'reason' => null];
return $offerImageCache[$offerId];
}
$offerImageCache[$offerId] = ['url' => null, 'reason' => 'missing_in_offer_api'];
} catch (Throwable $exception) {
$reason = $this->mapOfferApiErrorToReason($exception->getMessage());
$offerImageCache[$offerId] = ['url' => null, 'reason' => $reason];
}
return $offerImageCache[$offerId];
}
private function mapOfferApiErrorToReason(string $message): string
{
$normalized = strtoupper(trim($message));
if (str_contains($normalized, 'HTTP 403')) {
return 'offer_api_access_denied_403';
}
if (str_contains($normalized, 'HTTP 401')) {
return 'offer_api_unauthorized_401';
}
if (str_contains($normalized, 'HTTP 404')) {
return 'offer_api_not_found_404';
}
if (preg_match('/HTTP\s+(\d{3})/', $normalized, $matches) === 1) {
return 'offer_api_http_' . $matches[1];
}
return 'offer_api_request_failed';
}
/**
* @param array<string, mixed> $offerPayload
*/
private function extractOfferImageUrl(array $offerPayload): ?string
{
$candidates = [
(string) ($offerPayload['imageUrl'] ?? ''),
(string) ($offerPayload['image']['url'] ?? ''),
];
$images = $offerPayload['images'] ?? null;
if (is_array($images)) {
$firstImage = $images[0] ?? null;
if (is_array($firstImage)) {
$candidates[] = (string) ($firstImage['url'] ?? '');
} elseif (is_string($firstImage)) {
$candidates[] = $firstImage;
}
}
$productSet = $offerPayload['productSet'] ?? null;
if (is_array($productSet)) {
$firstSet = $productSet[0] ?? null;
if (is_array($firstSet)) {
$product = is_array($firstSet['product'] ?? null) ? $firstSet['product'] : [];
$productImage = is_array($product['images'] ?? null) ? ($product['images'][0] ?? null) : null;
if (is_array($productImage)) {
$candidates[] = (string) ($productImage['url'] ?? '');
} elseif (is_string($productImage)) {
$candidates[] = $productImage;
}
}
}
foreach ($candidates as $candidate) {
$url = trim($candidate);
if ($url === '') {
continue;
}
if (str_starts_with($url, '//')) {
return 'https:' . $url;
}
if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
continue;
}
return $url;
}
return null;
}
/**
* @param array<string, mixed> $itemRaw
*/
private function extractLineItemImageUrl(array $itemRaw): ?string
{
$offer = is_array($itemRaw['offer'] ?? null) ? $itemRaw['offer'] : [];
$candidates = [
(string) ($itemRaw['imageUrl'] ?? ''),
(string) ($offer['imageUrl'] ?? ''),
(string) ($offer['image']['url'] ?? ''),
];
$images = $offer['images'] ?? null;
if (is_array($images)) {
$firstImage = $images[0] ?? null;
if (is_array($firstImage)) {
$candidates[] = (string) ($firstImage['url'] ?? '');
} elseif (is_string($firstImage)) {
$candidates[] = $firstImage;
}
}
foreach ($candidates as $candidate) {
$url = trim($candidate);
if ($url === '') {
continue;
}
if (str_starts_with($url, '//')) {
return 'https:' . $url;
}
if (!str_starts_with($url, 'http://') && !str_starts_with($url, 'https://')) {
continue;
}
return $url;
}
return null;
}
/**
* @param array<string, mixed> $payment
* @return array<int, array<string, mixed>>
*/
private function buildPayments(array $payment, string $fallbackCurrency): array
{
$paymentId = trim((string) ($payment['id'] ?? ''));
if ($paymentId === '') {
return [];
}
$amount = $this->amountToFloat($payment['paidAmount'] ?? null);
if ($amount === null) {
$amount = $this->amountToFloat($payment['amount'] ?? null);
}
return [[
'source_payment_id' => $paymentId,
'external_payment_id' => $paymentId,
'payment_type_id' => trim((string) ($payment['type'] ?? 'allegro')),
'payment_date' => StringHelper::normalizeDateTime((string) ($payment['finishedAt'] ?? '')),
'amount' => $amount,
'currency' => StringHelper::nullableString((string) ($payment['amount']['currency'] ?? $fallbackCurrency)),
'comment' => StringHelper::nullableString((string) ($payment['provider'] ?? '')),
'payload_json' => $payment,
]];
}
/**
* @param array<int, array<string, mixed>> $apiShipments
* @param array<string, mixed> $delivery
* @return array<int, array<string, mixed>>
*/
private function buildShipments(array $apiShipments, array $delivery): array
{
$result = [];
foreach ($apiShipments as $shipmentRaw) {
if (!is_array($shipmentRaw)) {
continue;
}
$trackingNumber = trim((string) ($shipmentRaw['waybill'] ?? ''));
if ($trackingNumber === '') {
continue;
}
$carrierId = trim((string) ($shipmentRaw['carrierId'] ?? $delivery['method']['id'] ?? ''));
$carrierName = trim((string) ($shipmentRaw['carrierName'] ?? ''));
$result[] = [
'source_shipment_id' => StringHelper::nullableString((string) ($shipmentRaw['id'] ?? '')),
'external_shipment_id' => StringHelper::nullableString((string) ($shipmentRaw['id'] ?? '')),
'tracking_number' => $trackingNumber,
'carrier_provider_id' => $carrierId !== '' ? $carrierId : ($carrierName !== '' ? $carrierName : 'allegro'),
'posted_at' => StringHelper::normalizeDateTime((string) ($shipmentRaw['createdAt'] ?? '')),
'media_uuid' => null,
'payload_json' => $shipmentRaw,
];
}
return $result;
}
/**
* @param array<string, mixed> $payload
* @return array<int, array<string, mixed>>
*/
private function buildNotes(array $payload): array
{
$message = trim((string) ($payload['messageToSeller'] ?? ''));
if ($message === '') {
return [];
}
return [[
'source_note_id' => null,
'note_type' => 'buyer_message',
'created_at_external' => StringHelper::normalizeDateTime((string) ($payload['updatedAt'] ?? '')),
'comment' => $message,
'payload_json' => ['messageToSeller' => $message],
]];
}
private function mapPaymentStatus(string $status): ?int
{
return match ($status) {
'paid', 'finished', 'completed' => 2,
'partially_paid', 'in_progress' => 1,
'cancelled', 'canceled', 'failed', 'unpaid' => 0,
default => null,
};
}
private function amountToFloat(mixed $amountNode): ?float
{
if (!is_array($amountNode)) {
return null;
}
$value = trim((string) ($amountNode['amount'] ?? ''));
if ($value === '' || !is_numeric($value)) {
return null;
}
return (float) $value;
}
/**
* @param array<string, mixed> $address
*/
private function fallbackName(array $address, string $fallback): string
{
$name = trim((string) (($address['firstName'] ?? '') . ' ' . ($address['lastName'] ?? '')));
if ($name !== '') {
return $name;
}
$company = trim((string) ($address['companyName'] ?? ''));
if ($company !== '') {
return $company;
}
return $fallback;
}
}