843 lines
37 KiB
PHP
843 lines
37 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Settings;
|
|
|
|
use App\Core\Constants\IntegrationSources;
|
|
use App\Core\Support\StringHelper;
|
|
|
|
final class ShopproOrderMapper
|
|
{
|
|
/**
|
|
* @param array<int, array<string, mixed>> $items
|
|
* @return array<int, array{source_order_id:string,source_updated_at:string,payload:array<string,mixed>}>
|
|
*/
|
|
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<string, mixed> $payload
|
|
* @param array<string, string> $statusMap
|
|
* @param array<int, string> $productImagesById
|
|
* @return array{
|
|
* order:array<string,mixed>,
|
|
* addresses:array<int,array<string,mixed>>,
|
|
* items:array<int,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>>
|
|
* }
|
|
*/
|
|
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<string, mixed> $payload
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
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<string, mixed> $payload
|
|
* @return array{address:array<string,mixed>,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<string, mixed> $payload
|
|
* @return array<string, mixed>|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<string, mixed> $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<string, mixed> $payload
|
|
* @return array<string, mixed>|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<string, mixed> $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<string, mixed> $payload
|
|
* @param array<int, string> $productImagesById
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
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<string, mixed> $row
|
|
*/
|
|
private function extractPersonalization(array $row): ?string
|
|
{
|
|
$raw = $this->readPath($row, ['custom_fields']);
|
|
if ($raw === null || $raw === '' || $raw === false) {
|
|
return null;
|
|
}
|
|
|
|
$text = str_replace(['<br>', '<br/>', '<br />'], "\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<string, mixed> $payload
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
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<string, mixed> $payload
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
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<string, mixed> $payload
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
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;
|
|
}
|
|
}
|