> */ public function getDeliveryServices(): array { [$accessToken, $env] = $this->tokenManager->resolveToken(); $response = $this->apiClient->getDeliveryServices($env, $accessToken); return is_array($response['services'] ?? null) ? $response['services'] : []; } /** * @param array $formData * @return array */ public function createShipment(int $orderId, array $formData): array { $order = $this->ordersRepository->findDetails($orderId); if ($order === null) { throw new ShipmentException('Zamowienie nie znalezione.'); } $company = $this->companySettings->getSettings(); $sender = $this->companySettings->getSenderAddress(); $this->validateSenderAddress($sender); $deliveryMethodId = trim((string) ($formData['delivery_method_id'] ?? '')); if ($deliveryMethodId === '') { throw new ShipmentException('Nie podano metody dostawy.'); } $receiverAddress = $this->buildReceiverAddress($order, $formData); $senderAddress = $sender; if (trim((string) ($formData['sender_point_id'] ?? '')) !== '') { $senderAddress['point'] = trim((string) $formData['sender_point_id']); } $packageType = strtoupper(trim((string) ($formData['package_type'] ?? 'PACKAGE'))); $lengthCm = (float) ($formData['length_cm'] ?? $company['default_package_length_cm']); $widthCm = (float) ($formData['width_cm'] ?? $company['default_package_width_cm']); $heightCm = (float) ($formData['height_cm'] ?? $company['default_package_height_cm']); $weightKg = (float) ($formData['weight_kg'] ?? $company['default_package_weight_kg']); $labelFormat = strtoupper(trim((string) ($formData['label_format'] ?? $company['default_label_format']))); if (!in_array($labelFormat, ['PDF', 'ZPL'], true)) { $labelFormat = 'PDF'; } $orderData = is_array($order['order'] ?? null) ? $order['order'] : []; $sourceOrderId = trim((string) ($orderData['source_order_id'] ?? '')); $commandId = $this->generateUuid(); $apiPayload = [ 'commandId' => $commandId, 'input' => [ 'deliveryMethodId' => $deliveryMethodId, 'sender' => $senderAddress, 'receiver' => $receiverAddress, 'referenceNumber' => $sourceOrderId !== '' ? $sourceOrderId : (string) $orderId, 'packages' => [[ 'type' => $packageType, 'length' => ['value' => $lengthCm, 'unit' => 'CENTIMETER'], 'width' => ['value' => $widthCm, 'unit' => 'CENTIMETER'], 'height' => ['value' => $heightCm, 'unit' => 'CENTIMETER'], 'weight' => ['value' => $weightKg, 'unit' => 'KILOGRAMS'], ]], 'labelFormat' => $labelFormat, ], ]; $insuranceAmount = (float) ($formData['insurance_amount'] ?? 0); if ($insuranceAmount > 0) { $apiPayload['input']['insurance'] = [ 'amount' => number_format($insuranceAmount, 2, '.', ''), 'currency' => strtoupper(trim((string) ($formData['insurance_currency'] ?? 'PLN'))), ]; } $codAmount = (float) ($formData['cod_amount'] ?? 0); if ($codAmount > 0) { // Allegro WZA manages COD funds internally - iban/ownerName are not accepted. $apiPayload['input']['cashOnDelivery'] = [ 'amount' => number_format($codAmount, 2, '.', ''), 'currency' => strtoupper(trim((string) ($formData['cod_currency'] ?? 'PLN'))), ]; } $credentialsId = trim((string) ($formData['credentials_id'] ?? '')); if ($credentialsId !== '') { $apiPayload['input']['credentialsId'] = $credentialsId; } $packageId = $this->packages->create([ 'order_id' => $orderId, 'provider' => 'allegro_wza', 'delivery_method_id' => $deliveryMethodId, 'credentials_id' => $credentialsId !== '' ? $credentialsId : null, 'command_id' => $commandId, 'status' => 'pending', 'carrier_id' => trim((string) ($formData['carrier_id'] ?? '')), 'package_type' => $packageType, 'weight_kg' => $weightKg, 'length_cm' => $lengthCm, 'width_cm' => $widthCm, 'height_cm' => $heightCm, 'insurance_amount' => $insuranceAmount > 0 ? $insuranceAmount : null, 'insurance_currency' => $insuranceAmount > 0 ? strtoupper(trim((string) ($formData['insurance_currency'] ?? 'PLN'))) : null, 'cod_amount' => $codAmount > 0 ? $codAmount : null, 'cod_currency' => $codAmount > 0 ? strtoupper(trim((string) ($formData['cod_currency'] ?? 'PLN'))) : null, 'label_format' => $labelFormat, 'receiver_point_id' => trim((string) ($formData['receiver_point_id'] ?? '')), 'sender_point_id' => trim((string) ($formData['sender_point_id'] ?? '')), 'reference_number' => $sourceOrderId !== '' ? $sourceOrderId : (string) $orderId, 'payload_json' => $apiPayload, ]); [$accessToken, $env] = $this->tokenManager->resolveToken(); try { $response = $this->apiClient->createShipment($env, $accessToken, $apiPayload); } catch (RuntimeException $exception) { if (trim($exception->getMessage()) === 'ALLEGRO_HTTP_401') { [$accessToken, $env] = $this->tokenManager->resolveToken(); $response = $this->apiClient->createShipment($env, $accessToken, $apiPayload); } else { $this->packages->update($packageId, [ 'status' => 'error', 'error_message' => $exception->getMessage(), ]); throw $exception; } } $returnedCommandId = trim((string) ($response['commandId'] ?? $commandId)); $this->packages->update($packageId, [ 'command_id' => $returnedCommandId, 'payload_json' => $response, ]); return [ 'package_id' => $packageId, 'command_id' => $returnedCommandId, ]; } /** * @return array */ public function checkCreationStatus(int $packageId): array { $package = $this->packages->findById($packageId); if ($package === null) { throw new ShipmentException('Paczka nie znaleziona.'); } $commandId = trim((string) ($package['command_id'] ?? '')); if ($commandId === '') { throw new ShipmentException('Brak command_id dla tej paczki.'); } [$accessToken, $env] = $this->tokenManager->resolveToken(); $response = $this->apiClient->getShipmentCreationStatus($env, $accessToken, $commandId); $status = strtoupper(trim((string) ($response['status'] ?? ''))); $shipmentId = trim((string) ($response['shipmentId'] ?? '')); if ($status === 'SUCCESS' && $shipmentId !== '') { $details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId); $trackingNumber = trim((string) ($details['waybill'] ?? '')); $carrierId = trim((string) ($package['carrier_id'] ?? '')); if ($carrierId === '') { $carrierId = trim((string) ($details['carrierId'] ?? '')); } $this->packages->update($packageId, [ 'status' => 'created', 'shipment_id' => $shipmentId, 'tracking_number' => $trackingNumber !== '' ? $trackingNumber : null, 'carrier_id' => $carrierId !== '' ? $carrierId : ($package['carrier_id'] ?? null), 'payload_json' => $details, ]); $this->pushWaybillToAllegroCheckoutForm( (int) ($package['order_id'] ?? 0), $trackingNumber, $carrierId, $accessToken, $env ); return [ 'status' => 'created', 'shipment_id' => $shipmentId, 'tracking_number' => $trackingNumber, ]; } if ($status === 'ERROR') { $errors = is_array($response['errors'] ?? null) ? $response['errors'] : []; $messages = []; foreach ($errors as $err) { if (is_array($err)) { $messages[] = trim((string) ($err['message'] ?? ($err['userMessage'] ?? ''))); } } $errorMsg = implode('; ', array_filter($messages)) ?: 'Blad tworzenia przesylki.'; $this->packages->update($packageId, [ 'status' => 'error', 'error_message' => $errorMsg, 'payload_json' => $response, ]); return ['status' => 'error', 'error' => $errorMsg]; } return ['status' => 'in_progress']; } /** * @return array */ public function downloadLabel(int $packageId, string $storagePath): array { $package = $this->packages->findById($packageId); if ($package === null) { throw new ShipmentException('Paczka nie znaleziona.'); } $shipmentId = trim((string) ($package['shipment_id'] ?? '')); if ($shipmentId === '') { throw new ShipmentException('Przesylka nie zostala jeszcze utworzona.'); } [$accessToken, $env] = $this->tokenManager->resolveToken(); $labelFormat = trim((string) ($package['label_format'] ?? 'PDF')); $pageSize = $labelFormat === 'ZPL' ? 'A6' : 'A4'; $binary = $this->apiClient->getShipmentLabel($env, $accessToken, [$shipmentId], $pageSize); $dir = rtrim($storagePath, '/\\') . '/labels'; if (!is_dir($dir)) { mkdir($dir, 0775, true); } $ext = $labelFormat === 'ZPL' ? 'zpl' : 'pdf'; $filename = 'label_' . $packageId . '_' . $shipmentId . '.' . $ext; $filePath = $dir . '/' . $filename; file_put_contents($filePath, $binary); $updateFields = [ 'status' => 'label_ready', 'label_path' => 'labels/' . $filename, ]; // Refresh tracking number if not yet saved (may not have been available at creation time). if (trim((string) ($package['tracking_number'] ?? '')) === '') { try { $details = $this->apiClient->getShipmentDetails($env, $accessToken, $shipmentId); $trackingNumber = trim((string) ($details['waybill'] ?? '')); $carrierId = trim((string) ($package['carrier_id'] ?? '')); if ($carrierId === '') { $carrierId = trim((string) ($details['carrierId'] ?? '')); } if ($trackingNumber !== '') { $updateFields['tracking_number'] = $trackingNumber; if ($carrierId !== '') { $updateFields['carrier_id'] = $carrierId; } $this->pushWaybillToAllegroCheckoutForm( (int) ($package['order_id'] ?? 0), $trackingNumber, $carrierId, $accessToken, $env ); } } catch (Throwable) { // non-critical - label is still saved } } $this->packages->update($packageId, $updateFields); return [ 'label_path' => 'labels/' . $filename, 'full_path' => $filePath, ]; } /** * @param array|null $orderDetails * @param array $formData * @return array */ private function buildReceiverAddress(?array $orderDetails, array $formData): array { $addresses = is_array($orderDetails['addresses'] ?? null) ? $orderDetails['addresses'] : []; $deliveryAddr = null; $customerAddr = null; foreach ($addresses as $addr) { $type = (string) ($addr['address_type'] ?? ''); if ($type === 'delivery') { $deliveryAddr = $addr; } if ($type === 'customer') { $customerAddr = $addr; } } $addr = $deliveryAddr ?? $customerAddr ?? []; $email = trim((string) ($formData['receiver_email'] ?? ($addr['email'] ?? ''))); $receiver = [ 'name' => trim((string) ($formData['receiver_name'] ?? ($addr['name'] ?? ''))), 'street' => trim((string) ($formData['receiver_street'] ?? ($addr['street_name'] ?? ''))), 'city' => trim((string) ($formData['receiver_city'] ?? ($addr['city'] ?? ''))), 'postalCode' => trim((string) ($formData['receiver_postal_code'] ?? ($addr['zip_code'] ?? ''))), 'countryCode' => strtoupper(trim((string) ($formData['receiver_country_code'] ?? ($addr['country'] ?? 'PL')))), 'phone' => trim((string) ($formData['receiver_phone'] ?? ($addr['phone'] ?? ''))), 'email' => $email, ]; $buyerEmail = trim((string) ($customerAddr['email'] ?? $email)); if ($buyerEmail !== '') { $receiver['hashedMail'] = hash('sha256', strtolower($buyerEmail)); } $company = trim((string) ($formData['receiver_company'] ?? ($addr['company_name'] ?? ''))); if ($company !== '') { $receiver['company'] = $company; } $pointId = trim((string) ($formData['receiver_point_id'] ?? ($addr['parcel_external_id'] ?? ''))); if ($pointId !== '') { $receiver['point'] = $pointId; } return $receiver; } /** * @param array $sender */ private function validateSenderAddress(array $sender): void { $required = ['street', 'city', 'postalCode', 'phone', 'email']; foreach ($required as $field) { if (trim((string) ($sender[$field] ?? '')) === '') { throw new IntegrationConfigException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak: ' . $field . ').'); } } $name = trim((string) ($sender['name'] ?? '')); $company = trim((string) ($sender['company'] ?? '')); if ($name === '' && $company === '') { throw new IntegrationConfigException('Uzupelnij dane nadawcy w Ustawienia > Dane firmy (brak nazwy/firmy).'); } } private function pushWaybillToAllegroCheckoutForm( int $orderId, string $trackingNumber, string $carrierId, string $accessToken, string $environment ): void { if ($orderId <= 0) { return; } $waybill = trim($trackingNumber); if ($waybill === '') { return; } $orderDetails = $this->ordersRepository->findDetails($orderId); if ($orderDetails === null) { return; } $order = is_array($orderDetails['order'] ?? null) ? $orderDetails['order'] : []; $source = strtolower(trim((string) ($order['source'] ?? ''))); if ($source !== 'allegro') { return; } $checkoutFormId = trim((string) ($order['source_order_id'] ?? '')); if ($checkoutFormId === '') { return; } $normalizedCarrierId = trim($carrierId); if ($normalizedCarrierId === '') { return; } $carrierName = (string) ($this->packages->resolveCarrierName('allegro_wza', $normalizedCarrierId) ?? ''); $carrierName = trim($carrierName); if ($carrierName === '') { $carrierName = $normalizedCarrierId; } try { $this->apiClient->addShipmentToOrder( $environment, $accessToken, $checkoutFormId, $waybill, $normalizedCarrierId, $carrierName ); } catch (RuntimeException $exception) { if (trim($exception->getMessage()) !== 'ALLEGRO_HTTP_401') { return; } try { [$refreshedToken, $refreshedEnvironment] = $this->tokenManager->resolveToken(); $this->apiClient->addShipmentToOrder( $refreshedEnvironment, $refreshedToken, $checkoutFormId, $waybill, $normalizedCarrierId, $carrierName ); } catch (Throwable) { // non-critical - local shipment remains created } } catch (Throwable) { // non-critical - local shipment remains created } } private function generateUuid(): string { $data = random_bytes(16); $data[6] = chr(ord($data[6]) & 0x0f | 0x40); $data[8] = chr(ord($data[8]) & 0x3f | 0x80); return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); } }