> $items * @return array}> */ public function buildCandidates(array $items, ?string $cursorUpdatedAt, ?string $cursorOrderId): array { $result = []; foreach ($items as $row) { $sourceOrderId = $this->normalizeOrderId($this->readPath($row, ['id', 'order_id', 'external_order_id'])); $sourceUpdatedAt = StringHelper::normalizeDateTime((string) $this->readPath($row, ['updated_at', 'date_updated', 'modified_at', 'date_modified', 'created_at', 'date_created'])); if ($sourceOrderId === '' || $sourceUpdatedAt === null) { continue; } if (!$this->isAfterCursor($sourceUpdatedAt, $sourceOrderId, $cursorUpdatedAt, $cursorOrderId)) { continue; } $result[] = [ 'source_order_id' => $sourceOrderId, 'source_updated_at' => $sourceUpdatedAt, 'payload' => $row, ]; } usort($result, static function (array $a, array $b): int { $cmp = strcmp((string) ($a['source_updated_at'] ?? ''), (string) ($b['source_updated_at'] ?? '')); if ($cmp !== 0) { return $cmp; } return strcmp((string) ($a['source_order_id'] ?? ''), (string) ($b['source_order_id'] ?? '')); }); return $result; } /** * @param array $payload * @param array $statusMap * @param array $productImagesById * @return array{ * order:array, * addresses:array>, * items:array>, * payments:array>, * shipments:array>, * notes:array>, * status_history:array> * } */ public function mapOrderAggregate( int $integrationId, array $payload, array $statusMap, string $fallbackOrderId, string $fallbackUpdatedAt, array $productImagesById = [] ): array { $sourceOrderId = $this->normalizeOrderId($this->readPath($payload, ['id', 'order_id', 'external_order_id'])); if ($sourceOrderId === '') { $sourceOrderId = $fallbackOrderId; } $sourceCreatedAt = StringHelper::normalizeDateTime((string) $this->readPath($payload, ['created_at', 'date_created', 'date_add', 'date_order'])); $sourceUpdatedAt = StringHelper::normalizeDateTime((string) $this->readPath($payload, ['updated_at', 'date_updated', 'modified_at', 'date_modified', 'created_at'])); if ($sourceUpdatedAt === null) { $sourceUpdatedAt = $fallbackUpdatedAt !== '' ? $fallbackUpdatedAt : date('Y-m-d H:i:s'); } $originalStatus = strtolower(trim((string) $this->readPath($payload, ['status', 'status_code', 'order_status']))); $effectiveStatus = $statusMap[$originalStatus] ?? $originalStatus; if ($effectiveStatus === '') { $effectiveStatus = 'new'; } $currency = trim((string) $this->readPath($payload, ['currency', 'totals.currency'])); if ($currency === '') { $currency = 'PLN'; } $totalGross = $this->toFloatOrNull($this->readPath($payload, [ 'total_gross', 'total_with_tax', 'summary.total', 'totals.gross', 'summary', 'amount', ])); $transportCost = $this->toFloatOrNull($this->readPath($payload, ['transport_cost', 'delivery_cost', 'shipping.cost'])); if ($totalGross === null && $transportCost !== null) { $productsSum = 0.0; $hasProducts = false; $rawItemsForSummary = $this->readPath($payload, ['products', 'items', 'order_items']); if (is_array($rawItemsForSummary)) { foreach ($rawItemsForSummary as $rawItem) { if (!is_array($rawItem)) { continue; } $itemPrice = $this->toFloatOrNull($this->readPath($rawItem, [ 'price_brutto', 'price_gross', 'gross_price', 'price', ])); $itemQty = $this->toFloatOrDefault($this->readPath($rawItem, ['quantity', 'qty']), 1.0); if ($itemPrice === null) { continue; } $hasProducts = true; $productsSum += ($itemPrice * $itemQty); } } if ($hasProducts) { $totalGross = $productsSum + $transportCost; } } $totalNet = $this->toFloatOrNull($this->readPath($payload, ['total_net', 'total_without_tax', 'totals.net'])); $totalPaid = $this->toFloatOrNull($this->readPath($payload, ['total_paid', 'payments.total_paid', 'payment.total', 'paid_amount'])); $paidFlag = $this->readPath($payload, ['paid', 'is_paid']); $isPaid = $this->normalizePaidFlag($paidFlag); if ($totalPaid === null) { if ($isPaid && $totalGross !== null) { $totalPaid = $totalGross; } } $deliveryLabel = $this->buildDeliveryMethodLabel($payload); $order = [ 'integration_id' => $integrationId, 'source' => IntegrationSources::SHOPPRO, 'source_order_id' => $sourceOrderId, 'external_order_id' => $sourceOrderId, 'external_platform_id' => IntegrationSources::SHOPPRO, 'external_platform_account_id' => null, 'external_status_id' => $effectiveStatus, 'external_payment_type_id' => StringHelper::nullableString((string) $this->readPath($payload, ['payment_method', 'payment.method', 'payments.method'])), 'payment_status' => $this->mapPaymentStatus($payload, $isPaid), 'external_carrier_id' => StringHelper::nullableString($deliveryLabel), 'external_carrier_account_id' => StringHelper::nullableString((string) $this->readPath($payload, [ 'transport_id', 'shipping.method_id', 'delivery.method_id', ])), 'customer_login' => StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_email', 'customer.email', 'buyer.email', 'client.email', 'email', 'customer.login', 'buyer.login', ])), 'is_invoice' => $this->resolveInvoiceRequested($payload), 'is_encrypted' => false, 'is_canceled_by_buyer' => false, 'currency' => $currency, 'total_without_tax' => $totalNet, 'total_with_tax' => $totalGross, 'total_paid' => $totalPaid, 'send_date_min' => null, 'send_date_max' => null, 'ordered_at' => $sourceCreatedAt, 'source_created_at' => $sourceCreatedAt, 'source_updated_at' => $sourceUpdatedAt, 'preferences_json' => null, 'payload_json' => $payload, 'fetched_at' => date('Y-m-d H:i:s'), ]; $addresses = $this->mapAddresses($payload); $items = $this->mapItems($payload, $productImagesById); $payments = $this->mapPayments($payload, $currency, $totalPaid); $shipments = $this->mapShipments($payload); $notes = $this->mapNotes($payload); $statusHistory = [[ 'from_status_id' => null, 'to_status_id' => $effectiveStatus, 'changed_at' => $sourceUpdatedAt, 'change_source' => 'import', 'comment' => $originalStatus !== '' ? 'shopPRO status: ' . $originalStatus : null, 'payload_json' => null, ]]; return [ 'order' => $order, 'addresses' => $addresses, 'items' => $items, 'payments' => $payments, 'shipments' => $shipments, 'notes' => $notes, 'status_history' => $statusHistory, ]; } /** * @param array $payload * @return array> */ private function mapAddresses(array $payload): array { $result = []; $customerData = $this->buildCustomerAddress($payload); $result[] = $customerData['address']; $invoiceAddress = $this->buildInvoiceAddress( $payload, $customerData['name'], $customerData['email'], $customerData['phone'] ); if ($invoiceAddress !== null) { $result[] = $invoiceAddress; } $deliveryAddress = $this->buildDeliveryAddress( $payload, $customerData['name'], $customerData['email'], $customerData['phone'] ); if ($deliveryAddress !== null) { $result[] = $deliveryAddress; } return $result; } /** * @param array $payload * @return array{address:array,name:?string,email:?string,phone:?string} */ private function buildCustomerAddress(array $payload): array { $customerFirstName = StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer.first_name', 'buyer.firstname', 'customer.first_name', 'customer.firstname', 'client.first_name', 'client.firstname', 'billing_address.first_name', 'billing_address.firstname', 'first_name', 'firstname', 'client_name', 'imie', ])); $customerLastName = StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer.last_name', 'buyer.lastname', 'customer.last_name', 'customer.lastname', 'client.last_name', 'client.lastname', 'billing_address.last_name', 'billing_address.lastname', 'last_name', 'lastname', 'client_surname', 'nazwisko', ])); $customerName = StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_name', 'buyer.name', 'customer.name', 'client.name', 'billing_address.name', 'receiver.name', 'client', 'customer_full_name', 'client_full_name', ])) ?? $this->composeName($customerFirstName, $customerLastName, 'Klient'); $customerEmail = StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_email', 'buyer.email', 'customer.email', 'client.email', 'billing_address.email', 'shipping_address.email', 'delivery_address.email', 'email', 'client_email', 'mail', ])); $customerPhone = StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_phone', 'buyer.phone', 'customer.phone', 'client.phone', 'billing_address.phone', 'shipping_address.phone', 'delivery_address.phone', 'phone', 'telephone', 'client_phone', 'phone_number', 'client_phone_number', ])); $address = [ 'address_type' => 'customer', 'name' => $customerName ?? 'Klient', 'phone' => $customerPhone, 'email' => $customerEmail, 'street_name' => StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_address.street', 'customer.address.street', 'billing_address.street', 'client.address.street', 'address.street', 'street', 'client_street', 'ulica', ])), 'street_number' => StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_address.street_number', 'customer.address.street_number', 'billing_address.street_number', 'billing_address.house_number', 'client.address.street_number', 'address.street_number', 'house_number', 'street_no', 'street_number', 'nr_domu', ])), 'city' => StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_address.city', 'customer.address.city', 'billing_address.city', 'client.address.city', 'address.city', 'city', 'client_city', 'miejscowosc', ])), 'zip_code' => StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_address.zip', 'buyer_address.postcode', 'customer.address.zip', 'customer.address.postcode', 'billing_address.zip', 'billing_address.postcode', 'client.address.zip', 'address.zip', 'address.postcode', 'zip', 'postcode', 'client_postal_code', 'kod_pocztowy', ])), 'country' => StringHelper::nullableString((string) $this->readPath($payload, [ 'buyer_address.country', 'customer.address.country', 'billing_address.country', 'client.address.country', 'address.country', 'country', 'kraj', ])), 'payload_json' => [ 'buyer' => $this->readPath($payload, ['buyer']), 'customer' => $this->readPath($payload, ['customer']), 'billing_address' => $this->readPath($payload, ['billing_address']), 'buyer_address' => $this->readPath($payload, ['buyer_address']), 'address' => $this->readPath($payload, ['address']), ], ]; return ['address' => $address, 'name' => $customerName, 'email' => $customerEmail, 'phone' => $customerPhone]; } /** * @param array $payload * @return array|null */ private function buildDeliveryAddress(array $payload, ?string $customerName, ?string $customerEmail, ?string $customerPhone): ?array { $deliveryFirstName = StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.first_name', 'delivery.address.firstname', 'shipping.address.first_name', 'shipping.address.firstname', 'delivery_address.first_name', 'delivery_address.firstname', 'shipping_address.first_name', 'shipping_address.firstname', 'receiver.first_name', 'receiver.firstname', 'delivery_first_name', 'shipping_first_name', ])); $deliveryLastName = StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.last_name', 'delivery.address.lastname', 'shipping.address.last_name', 'shipping.address.lastname', 'delivery_address.last_name', 'delivery_address.lastname', 'shipping_address.last_name', 'shipping_address.lastname', 'receiver.last_name', 'receiver.lastname', 'delivery_last_name', 'shipping_last_name', ])); $deliveryName = StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.name', 'shipping.address.name', 'delivery_address.name', 'shipping_address.name', 'receiver.name', 'delivery_name', 'shipping_name', ])) ?? $this->composeName($deliveryFirstName, $deliveryLastName, null); $pickupData = $this->parsePickupPoint((string) $this->readPath($payload, ['inpost_paczkomat', 'orlen_point', 'pickup_point'])); $fields = [ 'name' => $deliveryName ?? StringHelper::nullableString($this->buildDeliveryMethodLabel($payload)), 'phone' => StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.phone', 'shipping.address.phone', 'delivery_address.phone', 'shipping_address.phone', 'receiver.phone', 'delivery_phone', 'shipping_phone', ])) ?? $customerPhone, 'email' => StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.email', 'shipping.address.email', 'delivery_address.email', 'shipping_address.email', 'receiver.email', 'delivery_email', 'shipping_email', ])) ?? $customerEmail, 'street_name' => StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.street', 'shipping.address.street', 'delivery_address.street', 'shipping_address.street', 'receiver.address.street', 'delivery_street', 'shipping_street', ])) ?? StringHelper::nullableString($pickupData['street'] ?? ''), 'street_number' => StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.street_number', 'shipping.address.street_number', 'delivery_address.street_number', 'shipping_address.street_number', 'delivery.address.house_number', 'shipping.address.house_number', 'receiver.address.street_number', 'receiver.address.house_number', 'delivery_street_number', 'shipping_street_number', ])), 'city' => StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.city', 'shipping.address.city', 'delivery_address.city', 'shipping_address.city', 'receiver.address.city', 'delivery_city', 'shipping_city', ])) ?? StringHelper::nullableString($pickupData['city'] ?? ''), 'zip_code' => StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.zip', 'delivery.address.postcode', 'shipping.address.zip', 'shipping.address.postcode', 'delivery_address.zip', 'delivery_address.postcode', 'shipping_address.zip', 'shipping_address.postcode', 'receiver.address.zip', 'receiver.address.postcode', 'delivery_zip', 'delivery_postcode', 'shipping_zip', 'shipping_postcode', ])) ?? StringHelper::nullableString($pickupData['zip_code'] ?? ''), 'country' => StringHelper::nullableString((string) $this->readPath($payload, [ 'delivery.address.country', 'shipping.address.country', 'delivery_address.country', 'shipping_address.country', 'receiver.address.country', 'delivery_country', 'shipping_country', ])), 'parcel_external_id' => StringHelper::nullableString($pickupData['code'] ?? ''), 'parcel_name' => StringHelper::nullableString($pickupData['label'] ?? ''), 'payload_json' => [ 'delivery' => $this->readPath($payload, ['delivery']), 'shipping' => $this->readPath($payload, ['shipping']), 'delivery_address' => $this->readPath($payload, ['delivery_address']), 'shipping_address' => $this->readPath($payload, ['shipping_address']), 'receiver' => $this->readPath($payload, ['receiver']), 'inpost_paczkomat' => $this->readPath($payload, ['inpost_paczkomat']), 'orlen_point' => $this->readPath($payload, ['orlen_point']), ], ]; if (!$this->hasAddressData($fields)) { return null; } return [ 'address_type' => 'delivery', 'name' => $fields['name'] ?? $customerName ?? 'Dostawa', 'phone' => $fields['phone'] ?? null, 'email' => $fields['email'] ?? $customerEmail, 'street_name' => $fields['street_name'] ?? null, 'street_number' => $fields['street_number'] ?? null, 'city' => $fields['city'] ?? null, 'zip_code' => $fields['zip_code'] ?? null, 'country' => $fields['country'] ?? null, 'parcel_external_id' => $fields['parcel_external_id'] ?? null, 'parcel_name' => $fields['parcel_name'] ?? null, 'payload_json' => is_array($fields['payload_json'] ?? null) ? $fields['payload_json'] : null, ]; } /** * @param array $payload */ private function resolveInvoiceRequested(array $payload): bool { $explicitInvoice = $this->readPath($payload, ['is_invoice', 'invoice.required', 'invoice']); if (!empty($explicitInvoice)) { return true; } $companyName = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.company_name', 'invoice.company', 'billing.company_name', 'billing.company', 'firm_name', 'company_name', 'client_company', 'buyer_company', ])); $taxNumber = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.tax_id', 'invoice.nip', 'billing.tax_id', 'billing.nip', 'firm_nip', 'company_nip', 'tax_id', 'nip', ])); return $companyName !== null || $taxNumber !== null; } /** * @param array $payload * @return array|null */ private function buildInvoiceAddress( array $payload, ?string $customerName, ?string $customerEmail, ?string $customerPhone ): ?array { $companyName = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.company_name', 'invoice.company', 'billing.company_name', 'billing.company', 'firm_name', 'company_name', 'client_company', 'buyer_company', ])); $companyTaxNumber = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.tax_id', 'invoice.nip', 'billing.tax_id', 'billing.nip', 'firm_nip', 'company_nip', 'tax_id', 'nip', ])); $invoiceFirstName = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.first_name', 'invoice.firstname', 'billing_address.first_name', 'billing_address.firstname', 'buyer.first_name', 'customer.first_name', 'client_name', ])); $invoiceLastName = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.last_name', 'invoice.lastname', 'billing_address.last_name', 'billing_address.lastname', 'buyer.last_name', 'customer.last_name', 'client_surname', ])); $invoiceName = $companyName ?? $this->composeName($invoiceFirstName, $invoiceLastName, $customerName ?? 'Faktura'); $streetName = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.address.street', 'invoice.street', 'billing_address.street', 'billing.street', 'firm_street', 'company_street', ])); $streetNumber = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.address.street_number', 'invoice.street_number', 'invoice.house_number', 'billing_address.street_number', 'billing_address.house_number', 'billing.street_number', 'house_number', 'street_number', ])); $city = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.address.city', 'invoice.city', 'billing_address.city', 'billing.city', 'firm_city', 'company_city', ])); $zipCode = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.address.zip', 'invoice.address.postcode', 'invoice.zip', 'invoice.postcode', 'billing_address.zip', 'billing_address.postcode', 'billing.zip', 'billing.postcode', 'firm_postal_code', 'company_postal_code', ])); $country = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.address.country', 'invoice.country', 'billing_address.country', 'billing.country', 'firm_country', 'company_country', ])); $email = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.email', 'billing_address.email', 'billing.email', 'client_email', ])) ?? $customerEmail; $phone = StringHelper::nullableString((string) $this->readPath($payload, [ 'invoice.phone', 'billing_address.phone', 'billing.phone', 'client_phone', ])) ?? $customerPhone; $hasInvoiceData = $companyName !== null || $companyTaxNumber !== null || $streetName !== null || $city !== null || $zipCode !== null; if (!$hasInvoiceData) { return null; } return [ 'address_type' => 'invoice', 'name' => $invoiceName ?? 'Faktura', 'phone' => $phone, 'email' => $email, 'street_name' => $streetName, 'street_number' => $streetNumber, 'city' => $city, 'zip_code' => $zipCode, 'country' => $country, 'company_tax_number' => $companyTaxNumber, 'company_name' => $companyName, 'payload_json' => [ 'invoice' => $this->readPath($payload, ['invoice']), 'billing' => $this->readPath($payload, ['billing']), 'billing_address' => $this->readPath($payload, ['billing_address']), 'firm_name' => $this->readPath($payload, ['firm_name']), 'firm_nip' => $this->readPath($payload, ['firm_nip']), 'firm_street' => $this->readPath($payload, ['firm_street']), 'firm_postal_code' => $this->readPath($payload, ['firm_postal_code']), 'firm_city' => $this->readPath($payload, ['firm_city']), ], ]; } private function composeName(?string $firstName, ?string $lastName, ?string $fallback): ?string { $name = trim(trim((string) $firstName) . ' ' . trim((string) $lastName)); if ($name !== '') { return $name; } $fallbackValue = trim((string) $fallback); return $fallbackValue !== '' ? $fallbackValue : null; } /** * @param array $address */ private function hasAddressData(array $address): bool { $fields = ['name', 'phone', 'email', 'street_name', 'street_number', 'city', 'zip_code', 'country']; foreach ($fields as $field) { $value = trim((string) ($address[$field] ?? '')); if ($value !== '') { return true; } } return false; } /** * @param array $payload * @param array $productImagesById * @return array> */ private function mapItems(array $payload, array $productImagesById = []): array { $rawItems = $this->readPath($payload, ['items']); if (!is_array($rawItems)) { $rawItems = $this->readPath($payload, ['order_items']); } if (!is_array($rawItems)) { $rawItems = $this->readPath($payload, ['products']); } if (!is_array($rawItems)) { return []; } $result = []; $sort = 0; foreach ($rawItems as $row) { if (!is_array($row)) { continue; } $name = trim((string) $this->readPath($row, ['name', 'title'])); if ($name === '') { continue; } $productId = (int) $this->readPath($row, ['product_id']); $parentProductId = (int) $this->readPath($row, ['parent_product_id']); $mediaUrl = StringHelper::nullableString((string) $this->readPath($row, ['image', 'image_url', 'img_url', 'img', 'photo', 'photo_url'])); if ($mediaUrl === null && $productId > 0 && isset($productImagesById[$productId])) { $mediaUrl = StringHelper::nullableString((string) $productImagesById[$productId]); } if ($mediaUrl === null && $parentProductId > 0 && isset($productImagesById[$parentProductId])) { $mediaUrl = StringHelper::nullableString((string) $productImagesById[$parentProductId]); } $result[] = [ 'source_item_id' => StringHelper::nullableString((string) $this->readPath($row, ['id', 'item_id'])), 'external_item_id' => StringHelper::nullableString((string) $this->readPath($row, ['id', 'item_id'])), 'ean' => StringHelper::nullableString((string) $this->readPath($row, ['ean'])), 'sku' => StringHelper::nullableString((string) $this->readPath($row, ['sku', 'symbol', 'code'])), 'original_name' => $name, 'original_code' => StringHelper::nullableString((string) $this->readPath($row, ['code', 'symbol'])), 'original_price_with_tax' => $this->toFloatOrNull($this->readPath($row, ['price_gross', 'gross_price', 'price', 'price_brutto'])), 'original_price_without_tax' => $this->toFloatOrNull($this->readPath($row, ['price_net', 'net_price', 'price_netto'])), 'media_url' => $mediaUrl, 'quantity' => $this->toFloatOrDefault($this->readPath($row, ['quantity', 'qty']), 1.0), 'tax_rate' => $this->toFloatOrNull($this->readPath($row, ['vat', 'tax_rate'])), 'item_status' => null, 'unit' => StringHelper::nullableString((string) $this->readPath($row, ['unit'])), 'item_type' => 'product', 'source_product_id' => StringHelper::nullableString((string) ($productId > 0 ? $productId : $parentProductId)), 'source_product_set_id' => StringHelper::nullableString((string) ($parentProductId > 0 ? $parentProductId : '')), 'sort_order' => $sort++, 'payload_json' => $row, 'personalization' => $this->extractPersonalization($row), ]; } return $result; } /** * @param array $row */ private function extractPersonalization(array $row): ?string { $raw = $this->readPath($row, ['custom_fields']); if ($raw === null || $raw === '' || $raw === false) { return null; } $text = str_replace(['
', '
', '
'], "\n", (string) $raw); $text = html_entity_decode(strip_tags($text), ENT_QUOTES | ENT_HTML5, 'UTF-8'); $text = trim($text); return $text !== '' ? $text : null; } /** * @param array $payload * @return array> */ private function mapPayments(array $payload, string $currency, ?float $totalPaid): array { $paymentMethod = StringHelper::nullableString((string) $this->readPath($payload, ['payment_method', 'payment.method'])); if ($paymentMethod === null && $totalPaid === null) { return []; } return [[ 'source_payment_id' => null, 'external_payment_id' => null, 'payment_type_id' => $paymentMethod ?? 'unknown', 'payment_date' => StringHelper::nullableString((string) $this->readPath($payload, ['payment_date', 'payment.date'])), 'amount' => $totalPaid, 'currency' => $currency, 'comment' => StringHelper::nullableString((string) $this->readPath($payload, ['payment_status', 'payment.status'])), 'payload_json' => null, ]]; } /** * @param array $payload * @return array> */ private function mapShipments(array $payload): array { $tracking = StringHelper::nullableString((string) $this->readPath($payload, ['delivery_tracking_number', 'delivery.tracking_number', 'shipping.tracking_number'])); if ($tracking === null) { return []; } return [[ 'source_shipment_id' => null, 'external_shipment_id' => null, 'tracking_number' => $tracking, 'carrier_provider_id' => $this->sanitizePlainText((string) ($this->readPath($payload, [ 'delivery_method', 'shipping.method', 'transport', 'transport_description', ]) ?? 'unknown')), 'posted_at' => StringHelper::nullableString((string) $this->readPath($payload, ['delivery.posted_at', 'shipping.posted_at'])), 'media_uuid' => null, 'payload_json' => null, ]]; } /** * @param array $payload * @return array> */ private function mapNotes(array $payload): array { $comment = StringHelper::nullableString((string) $this->readPath($payload, ['notes', 'comment', 'customer_comment'])); if ($comment === null) { return []; } return [[ 'source_note_id' => null, 'note_type' => 'message', 'created_at_external' => null, 'comment' => $comment, 'payload_json' => null, ]]; } private function normalizeOrderId(mixed $value): string { return trim((string) $value); } private function normalizePaidFlag(mixed $value): bool { if ($value === true) { return true; } if ($value === false || $value === null) { return false; } $normalized = strtolower(trim((string) $value)); return in_array($normalized, ['1', 'true', 'yes', 'paid'], true); } private function mapPaymentStatus(array $payload, bool $isPaid): ?int { if ($isPaid) { return 2; } $raw = strtolower(trim((string) $this->readPath($payload, ['payment_status', 'payment.status']))); if ($raw === '') { return 0; } return match ($raw) { 'paid', 'finished', 'completed' => 2, 'partially_paid', 'in_progress' => 1, 'cancelled', 'canceled', 'failed', 'unpaid', 'not_paid' => 0, default => 0, }; } private function sanitizePlainText(string $value): string { $withoutTags = strip_tags($value); $decoded = html_entity_decode($withoutTags, ENT_QUOTES | ENT_HTML5, 'UTF-8'); return trim(preg_replace('/\s+/', ' ', $decoded) ?? ''); } private function buildDeliveryMethodLabel(array $payload): string { $label = $this->sanitizePlainText((string) $this->readPath($payload, [ 'delivery_method', 'shipping.method', 'delivery.method', 'shipping_method', 'delivery_name', 'shipping_name', 'transport', 'transport_description', ])); $cost = $this->toFloatOrNull($this->readPath($payload, ['transport_cost', 'delivery_cost', 'shipping.cost'])); if ($label !== '' && $cost !== null) { $label .= ': ' . $this->formatMoneyCompact($cost) . ' zł'; } return $label; } private function formatMoneyCompact(float $amount): string { $formatted = number_format($amount, 2, '.', ''); return rtrim(rtrim($formatted, '0'), '.'); } /** * @return array{code:string,label:string,street:string,zip_code:string,city:string} */ private function parsePickupPoint(string $raw): array { $value = trim($this->sanitizePlainText($raw)); if ($value === '') { return ['code' => '', 'label' => '', 'street' => '', 'zip_code' => '', 'city' => '']; } $code = ''; $address = $value; $parts = preg_split('/\s*\|\s*/', $value); if (is_array($parts) && count($parts) >= 2) { $code = trim((string) ($parts[0] ?? '')); $address = trim((string) ($parts[1] ?? '')); } $street = ''; $zip = ''; $city = ''; if (preg_match('/^(.*?),\s*(\d{2}-\d{3})\s+(.+)$/u', $address, $matches) === 1) { $street = trim((string) ($matches[1] ?? '')); $zip = trim((string) ($matches[2] ?? '')); $city = trim((string) ($matches[3] ?? '')); } elseif (preg_match('/(\d{2}-\d{3})\s+(.+)$/u', $address, $matches) === 1) { $zip = trim((string) ($matches[1] ?? '')); $city = trim((string) ($matches[2] ?? '')); } return [ 'code' => $code, 'label' => $value, 'street' => $street, 'zip_code' => $zip, 'city' => $city, ]; } private function isAfterCursor(string $sourceUpdatedAt, string $sourceOrderId, ?string $cursorUpdatedAt, ?string $cursorOrderId): bool { if ($cursorUpdatedAt === null || $cursorUpdatedAt === '') { return true; } if ($sourceUpdatedAt > $cursorUpdatedAt) { return true; } if ($sourceUpdatedAt < $cursorUpdatedAt) { return false; } if ($cursorOrderId === null || $cursorOrderId === '') { return true; } return strcmp($sourceOrderId, $cursorOrderId) > 0; } private function toFloatOrNull(mixed $value): ?float { if ($value === null || $value === '') { return null; } if (is_string($value)) { $value = str_replace(',', '.', trim($value)); } if (!is_numeric($value)) { return null; } return (float) $value; } private function toFloatOrDefault(mixed $value, float $default): float { $result = $this->toFloatOrNull($value); return $result ?? $default; } private function readPath(mixed $payload, array $paths): mixed { foreach ($paths as $path) { $value = $this->readSinglePath($payload, (string) $path); if ($value !== null && $value !== '') { return $value; } } return null; } private function readSinglePath(mixed $payload, string $path): mixed { if ($path === '') { return null; } $segments = explode('.', $path); $current = $payload; foreach ($segments as $segment) { if (!is_array($current) || !array_key_exists($segment, $current)) { return null; } $current = $current[$segment]; } return $current; } }