'test20cztest', 'key' => 'QKCufcWs', ]; protected $request_url = 'https://api.balikobot.cz'; protected $request_url_v2 = 'https://apiv2.balikobot.cz'; public $orders = []; public $IDs = []; /** @var string Balikobot carrier code (eg: cp, gls) */ protected $carrierCode; /** @var CurrencyContext */ protected $currencyContext; /** @var PriceConverter|null */ protected $priceConverter; /** @var OrderItemInfo */ protected $orderItemInfo; /** * @required */ public BalikobotAdapterUtil $balikobotAdapterUtil; protected ?ReturnsUtil $returnsUtil; protected ?ReturnDeliveries $returnDeliveriesUtil; /** * @var int */ public $response; /** * @var bool */ public $authorizationFailed; private $credentials = [ 'user' => null, 'key' => null, ]; public function __construct(CurrencyContext $currencyContext) { $this->currencyContext = $currencyContext; } /** * @required */ public function setOrderItemInfo(OrderItemInfo $orderItemInfo): void { $this->orderItemInfo = $orderItemInfo; } /** * @required */ public function setPriceConverter(?PriceConverter $priceConverter = null): void { $this->priceConverter = $priceConverter; } /** * @required */ public function setReturnsUtil(?ReturnsUtil $returnsUtil): void { $this->returnsUtil = $returnsUtil; } /** * @required */ public function setReturnDeliveriesUtil(?ReturnDeliveries $returnDeliveriesUtil): void { $this->returnDeliveriesUtil = $returnDeliveriesUtil; } public function setCredentials($user, $key) { $this->credentials['user'] = $user; $this->credentials['key'] = $key; return $this; } public function setCredentialsByName($name) { if ($credentials = $this->getCredentialsByName($name)) { $this->setCredentials($credentials['user'], $credentials['key']); return true; } throw new BalikonosException('Balíky se nepodařilo odeslat. Uživatel "'.$name.'" nebyl nalezen.'); } public function getCredentialsByName($name) { foreach ($this->getCollectionPlaces() as $user) { if ($user['user'] == $name) { return $user; } } if (isDevelopment()) { return $this->testCredentials; } return null; } public function getCollectionPlaces() { $dbcfg = \Settings::getDefault(); return $dbcfg->balikobot['users'] ?? []; } public function hasMultipleCollectionPlaces() { $users = $this->getCollectionPlaces(); if (count($users) > 1) { return true; } return false; } public function getSavedBalikobotUser() { if ($balikobotUser = getVal('balikobot_user')) { return $balikobotUser; } return $_COOKIE['balikobot_user'] ?? null; } public function checkCredentials() { if (empty($this->credentials['user']) || empty($this->credentials['key'])) { if ($this->hasMultipleCollectionPlaces()) { throw new BalikonosException('Nevybrali jste svozové místo!'); } $users = $this->getCollectionPlaces(); if (empty($users)) { throw new BalikonosException('Nemáte vyplněné žádně svozové místo v Nastavení / Nastavení Eshopu / Balíkobot '); } $user = reset($users); $this->setCredentials($user['user'], $user['key']); } } public function getUser(): ?string { $config = Config::get(); $this->checkCredentials(); return ($config['Modules']['balikonos']['test'] ?? false) ? $this->testCredentials['user'] : $this->credentials['user']; } public function getKey(): ?string { $config = Config::get(); $this->checkCredentials(); return ($config['Modules']['balikonos']['test'] ?? false) ? $this->testCredentials['key'] : $this->credentials['key']; } public function createCurl($encodedBody, string $apiResourceType, $v2 = false) { if ($apiResourceType == 'zasilkovna/services') { $apiResourceType = 'v2/'.$apiResourceType; } $requestUrl = $this->request_url; if ($v2) { $requestUrl = $this->request_url_v2; } // TODO: rozlisit api verzi podle příznaku v body $url = $requestUrl.'/'.$apiResourceType; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_HEADER, false); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_POSTFIELDS, $encodedBody); curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Authorization: Basic '.base64_encode($this->getUser().':'.$this->getKey()), 'Content-Type: application/json', 'BB-Partner: .k.nwwKUE50aJP4a', ]); return $curl; } public function curlExecute($curl) { return curl_exec($curl); } private function isResponseAuthorized($response) { if (strpos($response, '401 Unauthorized') !== false) { return false; } if (strpos($response, '403 Forbidden') !== false) { return false; } return true; } public function getOrdersData($complete = true) { $dbcfg = \Settings::getDefault(); foreach ($this->IDs as $ID => $data) { $realOrder = true; $type = $data['type'] ?? self::TYPE_ORDER; if ($type != self::TYPE_ORDER) { $ID = null; $realOrder = false; } if ($data['order'] ?? false) { $orderCls = $data['order']; } else { $orderCls = new \Order(); $orderCls->createFromDB($ID, true); } $balikobot = $orderCls->getDeliveryType($orderCls->getDeliveryId())->getDelivery()->getCustomData()['balikobot'] ?? null; $info = [ 'invoice_email' => $orderCls->invoice_email, 'invoice_name' => $orderCls->delivery_name ?: $orderCls->invoice_name, 'invoice_surname' => $orderCls->delivery_surname ?: $orderCls->invoice_surname, 'invoice_firm' => $orderCls->delivery_firm, 'invoice_street' => !empty($orderCls->delivery_street) ? $orderCls->delivery_street : $orderCls->invoice_street, 'invoice_custom_address' => !empty($orderCls->delivery_custom_address) ? $orderCls->delivery_custom_address : $orderCls->invoice_custom_address, 'invoice_city' => !empty($orderCls->delivery_city) ? $orderCls->delivery_city : $orderCls->invoice_city, 'invoice_zip' => !empty($orderCls->delivery_zip) ? $orderCls->delivery_zip : $orderCls->invoice_zip, 'invoice_country' => !empty($orderCls->delivery_country) ? $orderCls->delivery_country : $orderCls->delivery_country, 'invoice_phone' => !empty($orderCls->delivery_phone) ? $orderCls->delivery_phone : $orderCls->invoice_phone, 'order_no' => $orderCls->order_no, 'total_price' => $orderCls->total_price, 'currency' => $orderCls->currency ?? $this->currencyContext->getDefaultId(), 'id' => $ID, 'size' => $data['size'] ?? null, 'packages' => !empty($data['packages']) ? $data['packages'] : null, 'note' => $data['note'] ?? null, 'type' => $type, ]; if ($weight = (!empty($data['weights']) ? $data['weights'] : $orderCls->getTotalWeight())) { $info['weight'] = $weight; } if ($info['invoice_country'] == 'SK') { // SK - balikobot vrati chybu - Špatný formát PSČ příjemce. PSČ musí obsahovat 5 čísel a začínat číslicí 0 / 8 / 9. $info['invoice_zip'] = str_pad($info['invoice_zip'], 5, '0', STR_PAD_LEFT); } $info = array_merge($info, array_filter($data)); $info['id_delivery'] = !empty($info['id_delivery']) ? $info['id_delivery'] : $orderCls->getDeliveryType()->id_delivery; if (!$this->isDeliverySupported($info['id_delivery'])) { throw new BalikonosException( sprintf('Odesíláte objednávku s dopravou %s, která není spárována s Balíkobotem. Spárování nastavte v Nastavení Eshopu, na záložce Balíkobot', \Delivery::getAll()[$info['id_delivery']]->name) ); } // set proper carrierCode for current order $info['carrier_code'] = $dbcfg['balikobot']['delivery_type'][$info['id_delivery']]['carrier']; $this->carrierCode = $info['carrier_code']; if ($complete) { $orderNumber = $info['order_id'] ?? $orderCls->order_no; $order = [ 'balikobot_array' => [ // TODO: eid must be unique for each package (multiple packages for single order?) 'eid' => $orderNumber.'_'.time(), // order_id is not order_no, but package id // 'order_id' => $orderCls->order_no, 'real_order_id' => $orderNumber, 'vs' => $orderNumber, 'service_type' => $dbcfg['balikobot']['delivery_type'][$info['id_delivery']]['carrierService'], 'price' => \Decimal::max(toDecimal($info['total_price'] ?? 0), \DecimalConstants::one())->printFloatValue(2), 'ins_currency' => $orderCls->currency ?? $this->currencyContext->getDefaultId(), 'rec_phone' => $info['invoice_phone'], 'rec_name' => $info['invoice_name'].' '.$info['invoice_surname'], 'rec_firm' => implode(', ', array_filter([$info['invoice_firm'], $info['invoice_custom_address']])), 'rec_street' => $info['invoice_street'], 'rec_city' => $info['invoice_city'], 'rec_email' => $info['invoice_email'], 'rec_zip' => $this->getPostalCode($orderCls, $info['invoice_zip'], $info['id_delivery']), 'rec_country' => empty($info['invoice_country']) ? 'CZ' : $info['invoice_country'], 'del_evening' => false, 'size' => $info['size'], 'note' => $info['note'], // return track URL 'return_track' => true, // display text errors? 'return_full_errors' => true, 'reference' => $orderNumber, ], ]; if ($realOrder) { $order['balikobot_array']['content_invoice_number'] = $orderCls->getInvoiceNo(); $order['balikobot_array']['content_issue_date'] = ($orderCls->date_handle ?? new \DateTime())->format('Y-m-d'); } $maxPrice = $balikobot['max_order_price'] ?? null; if (is_numeric($maxPrice) && $maxPrice < $order['balikobot_array']['price']) { $order['balikobot_array']['price'] = $maxPrice; } if ((float) ($info['weight'] ?? 0) > 0) { $order['balikobot_array']['weight'] = (float) $info['weight']; } if ($realOrder && (!empty($balikobot))) { $order = $this->addCustomField($order, $balikobot, $orderCls); } } else { $order = []; } if ($realOrder && ($remaining = $orderCls->getRemainingPayment()) > 0 && $complete) { if (!findModule('currencies') || $orderCls->currency == 'CZK') { $scale = 0; } else { $scale = 2; } $delivery_type = $orderCls->getDeliveryType($orderCls->getDeliveryId()); $activeCurrency = $orderCls->currency ?? $this->currencyContext->getDefaultId(); if ((($dbcfg->balikobot['cod_remaining'] ?? false) == 'Y') || (!empty($delivery_type->payment_class) && $delivery_type->payment_class->getPayMethod() == \Payment::METHOD_COD)) { $contextManager = ServiceContainer::getService(ContextManager::class); $contextManager->activateContexts([CurrencyContext::class => $activeCurrency], function () use (&$order, $remaining, $scale, $activeCurrency) { $order['balikobot_array']['cod_price'] = roundPrice(toDecimal($remaining), decimal: 2, bata: false)->printFloatValue($scale); $order['balikobot_array']['cod_currency'] = $activeCurrency; }); } } if (isset($data['cod_price'])) { $order['balikobot_array']['cod_price'] = $data['cod_price']; $info['cod_price'] = $data['cod_price']; $order['balikobot_array']['cod_currency'] = $orderCls->currency ?? $this->currencyContext->getDefaultId(); } if (!empty($data)) { $order = array_merge($order, $data); } $tmpData = array_merge($order, $info); if ($complete) { if (!in_array($tmpData['balikobot_array']['rec_country'], CountryContext::EU_COUNTRIES)) { $tmpData['balikobot_array'] = $this->addDeliveryCosts($orderCls, $tmpData['balikobot_array']); $tmpData['balikobot_array'] = $this->addContentData($orderCls, $tmpData['balikobot_array']); } $adapter = ServiceContainer::getService('kupshop.balikobot.adapter.'.$this->carrierCode, ContainerInterface::NULL_ON_INVALID_REFERENCE); if ($adapter instanceof IBalikobotAdapter) { $tmpData['balikobot_array'] = $adapter->transformOrderData($orderCls, $tmpData['balikobot_array'], $info); } } if ($info['packages'] > 1) { $zeroPriceForSubPackages = $tmpData['balikobot_array']['_zeroPriceForSubPackages'] ?? true; unset($tmpData['balikobot_array']['_zeroPriceForSubPackages']); if ($weight) { $tmpData['balikobot_array']['weight'] = toDecimal($tmpData['balikobot_array']['weight'])->div(toDecimal($info['packages']))->asFloat(); } $tmpData['balikobot_array']['order_number'] = 1; $tmpPackagesData = $tmpData; for ($i = 2; $i <= $info['packages']; $i++) { $tmpPackagesData['balikobot_array']['order_number'] = $i; $tmpPackagesData['balikobot_array']['cod_price'] = 0; if ($zeroPriceForSubPackages) { $tmpPackagesData['balikobot_array']['price'] = 0; } $tmpData['additional_orders'][] = $tmpPackagesData['balikobot_array']; } } $this->orders[] = $tmpData; } return $this->orders; } protected function addDeliveryCosts(\Order $order, array $data): array { $deliveryPrice = 0; foreach ($order->fetchItems() as $item) { if ($this->orderItemInfo->getItemType($item) === OrderItemInfo::TYPE_DELIVERY) { $deliveryPrice = $item['total_price']['value_with_vat']->asFloat(); break; } } $data['delivery_costs'] = $deliveryPrice; if ($this->priceConverter && ($currencyEUR = ($this->currencyContext->getAll()['EUR'] ?? null))) { $data['delivery_costs_eur'] = $this->priceConverter->convert( $order->getCurrency(), $currencyEUR, $deliveryPrice ); } return $data; } protected function addContentData(\Order $order, array $data) { $contentData = []; foreach ($order->fetchItems() as $item) { if (!empty($item['product'])) { /** @var \Product $product */ $product = $item['product']; $contentDataItem = [ 'content_name_en' => $item['descr'], 'content_name' => $item['descr'], 'content_weight' => $product->weight, 'content_pieces' => $item['pieces'], 'content_price' => $item['total_price']['value_with_vat']->asFloat(), 'content_description' => $item['descr'], 'content_country' => 'CZ', 'content_currency' => $order->getCurrency(), 'content_customs_code' => substr($product->getCN(), 0, 8), ]; if (!empty($item['ean'])) { $contentDataItem['content_ean'] = $item['ean']; } if ($this->priceConverter && ($currencyEUR = ($this->currencyContext->getAll()['EUR'] ?? null))) { $contentDataItem['content_price_eur'] = $this->priceConverter->convert( $order->getCurrency(), $currencyEUR, $item['total_price']['value_with_vat'] )->asFloat(); } $contentData[] = $contentDataItem; } } $data['ins_currency'] = $order->getCurrency(); $data['content_data'] = $contentData; return $data; } private function getPostalCode($orderCls, $zip, $deliveryId) { $dbcfg = \Settings::getDefault(); if ($dbcfg['balikobot']['delivery_type'][$deliveryId]['carrierService'] == 'NP') { $delivery_data = $orderCls->getData('delivery_data'); return !empty($delivery_data['psc']) ? $delivery_data['psc'] : $zip; } else { return $zip; } } protected function sendToBalikonos() { foreach ($this->orders as &$order) { $qb = sqlQueryBuilder() ->select('*') ->from('balikonos') ->where('id_order = :order_id AND user = :user AND id_delivery IS NOT NULL and close IN (0, 1)') ->setParameters(['order_id' => $order['id'], 'user' => $this->getUser()]) ->sendToMaster() ->execute(); if ($qb->rowCount() > 0) { $order['response'] = 'Chyba! Objednávka už v Balíkobotu existuje.'; // skip orders that are already in process continue; } $requestBody = json_encode(array_merge([$order['balikobot_array']], $order['additional_orders'] ?? [])); logError(__FILE__, __LINE__, 'Balikobot odeslani baliku: '.print_r($requestBody, true)); $curl = $this->createCurl($requestBody, $order['carrier_code'].'/add'); $response = $this->curlExecute($curl); logError(__FILE__, __LINE__, 'Balikobot odpoved: '.print_r($response, true).' na balik '.print_r($requestBody, true)); if ($response) { $answr = json_decode($response, true); $order['response'] = $answr; } else { $order['response'] = 'curl error: '.curl_error($curl); } if ($order['response']['status'] == '400') { logError(__FILE__, __LINE__, 'Balikobot API UNDEFINED error sended_data: '.print_r($requestBody, true)); break; } } } public function saveToDB() { try { $error = false; foreach ($this->orders as &$order) { if (!empty($order['response']['status'])) { if (in_array($order['response']['status'], ['200', '208'])) { foreach ($order['response'] as $key => $response) { if (is_numeric($key) && is_array($response)) { sqlQuery('INSERT INTO balikonos (user,id_order,id_delivery,id_shop_delivery, data) VALUES (:user, :order_id, :package_id, :id_shop_delivery, :data) ON DUPLICATE KEY UPDATE id_delivery=:package_id, id_shop_delivery=:id_shop_delivery, data=:data, close=0', [ 'user' => $this->getUser(), 'order_id' => $order['id'], 'package_id' => $response['package_id'], 'id_shop_delivery' => $order['id_delivery'], 'data' => json_encode($order), ] ); $order['id_balikonos'] = sqlInsertId(); $order['package_id'] = $response['carrier_id']; $order['error'] = 'OK'; } } $this->updateSQL('orders', ['package_id' => $order['response'][0]['carrier_id']], ['id' => $order['id']]); } else { $order['error'] = $this->getErrorMessage($order['response']); $error = true; } } elseif ($error) { $order['error'] = ''; } else { $order['error'] = $order['response']; } } $this->response = $error ? 3 : 4; } catch (\Doctrine\DBAL\DBALException $e) { echo $e->getMessage(); } } public function getErrorMessage($response) { $tmpErrors = []; if (isset($response[0]['errors'])) { $tmpErrors = array_map( function ($error) { return $error['message'] ?? ($error['type'].':'.$error['attribute']); }, $response[0]['errors'] ); } $msg = join(' ', $tmpErrors); if (!empty($msg)) { logError(__FILE__, __LINE__, "Balikobot API error: {$msg} \n Response:".print_r($response, true)); return $msg; } var_dump($response); logError(__FILE__, __LINE__, 'Balikobot API UNDEFINED error: '.print_r($response, true)); return 'Nedefinovaná chyba'; } public function sendDeliveries() { $this->getOrdersData(); $this->sendToBalikonos(); $this->saveToDB(); } public function deletePackage($id) { $data = $this->selectSQL('balikonos', ['id' => $id])->fetch(); if ($data) { $dbcfg = \Settings::getDefault(); $this->setCredentialsByName($data['user']); $carrierCode = $dbcfg['balikobot']['delivery_type'][$data['id_shop_delivery']]['carrier']; $packageId = $data['id_delivery']; if ($data_data = json_decode($data['data'] ?? '', true)) { $package = $data_data['response'][0]['carrier_id'] ?? ''; } $log_message = 'Smazán balík: '.($package ?? '').' (Balíkobot ID: '.$packageId.')'; $curl = $this->createCurl(json_encode(['id' => $packageId]), $carrierCode.'/drop'); $response = $this->curlExecute($curl); if ($response) { $response = json_decode($response, true); $responseStatus = $response[0]['status'] ?? ($response['status'] ?? 500); switch ($responseStatus) { case 200: writeDownActivity($log_message); $this->deleteSQL('balikonos', ['id' => $id]); return true; case 403: throw new BalikonosException('Balík se nepodařilo smazat, protože je z vícekusové zásilky a není posledním balíkem.'); default: writeDownActivity($log_message); $this->deleteSQL('balikonos', ['id' => $id]); break; } } throw new BalikonosException('Objednávku se nepodařilo odstranit ze systému Balíkobot a je potřeba ji z něj odstranit ručně.'); } throw new BalikonosException('Při odebírání objednávky ze systému Balíkobot došlo k chybě.'); } public function setIDs(array $IDs) { $this->IDs = $IDs; } public function closeDeliveries($id_deliver, $packageIDs = []) { if (empty($packageIDs)) { return false; } $data = ['package_ids' => $packageIDs]; $dbcfg = \Settings::getDefault(); $this->carrierCode = $dbcfg['balikobot']['delivery_type'][$id_deliver]['carrier']; $curl = $this->createCurl(json_encode($data), $this->carrierCode.'/order'); $response = $this->curlExecute($curl); logError(__FILE__, __LINE__, 'Balikobot API ORDER close request: '.print_r($data, true).' response: '.print_r($response, true)); if ($response) { $batchResponse = json_decode($response); } else { $batchResponse = 'curl error: '.curl_error($curl); } if (!empty($batchResponse->status) && $batchResponse->status == '200') { $this->response = 2; } else { logError(__FILE__, __LINE__, 'Balikobot API ORDER close error: '.print_r($batchResponse, true)); throw new BalikonosException('Balíkobot vrátil chybu. Nepodařilo se uzavřít svoz.', $batchResponse->status ?? 500); } } /** * Print PDF. * * @param int[] $deliver_ids * @param string $printFormat * @param int[] $orderIds * @param bool $return * @param int[] $balikobotIds */ public function printTickets($deliver_ids, $position, $printFormat = 'default', $orderIds = [], $return = false, $balikobotIds = [], $shop_delivery_ids = []) { $dbcfg = \Settings::getDefault(); $qbResult = sqlQueryBuilder()->select('b.id, b.id_delivery, b.close, b.id_shop_delivery, dtd.id_delivery AS delivery_type_delivery_id') ->from('balikonos', 'b') ->leftJoin('b', 'orders', 'o', 'o.id=b.id_order') ->leftJoin('o', 'delivery_type', 'dtd', 'dtd.id=o.id_delivery') ->andWhere(Operator::equals(['b.user' => $this->getUser()])); if ($balikobotIds) { $qbResult->andWhere(Operator::inIntArray($balikobotIds, 'b.id')); } elseif ($orderIds) { $qbResult->andWhere(Operator::inIntArray($orderIds, 'o.id')) ->andWhere('b.close < 2'); } elseif ($deliver_ids) { $qbResult->andWhere(Operator::inIntArray($deliver_ids, 'b.id_delivery')) ->andWhere(Operator::isNotNull('b.id_delivery')); } elseif ($shop_delivery_ids) { $qbResult->andWhere(Operator::inIntArray($shop_delivery_ids, 'b.id_shop_delivery')) ->andWhere(Operator::isNotNull('b.id_delivery')) ->andWhere('b.close < 2'); } $packageIDs = []; $balikobotIds = []; foreach ($qbResult->execute() as $row) { // set carrierCode for future use in API requests $id_delivery = !empty($row['id_shop_delivery']) ? $row['id_shop_delivery'] : $row['delivery_type_delivery_id']; $this->carrierCode = $dbcfg['balikobot']['delivery_type'][$id_delivery]['carrier']; $packageIDs[] = $row['id_delivery']; $balikobotIds[] = $row['id']; } $curl = $this->createCurl(json_encode(['package_ids' => $packageIDs]), $this->carrierCode.'/labels'); $response = json_decode($this->curlExecute($curl)); if (empty($response->status) || $response->status != '200') { logError(__FILE__, __LINE__, 'Balikobot API LABELS error: '.print_r($response, true)); throw new BalikonosException('Balíkobot vrátil chybu. Nelze vytisknout štítky. Chyba: '.($response->status ?? 'Balíkobot nevrátil žádnou odpověd.')); } else { sqlQueryBuilder()->update('balikonos') ->set('close', 'GREATEST(close, 1)') ->where(Operator::inIntArray($balikobotIds, 'id')) ->execute(); } // set $position if specified $url = $response->labels_url.(empty($position) ? '' : ('?p='.$position)); if ($return) { return $url; } redirection($url); } public function getResult() { $result = []; foreach ($this->orders as $key => $order) { $result[$key] = [ 'id_balikonos' => $order['id_balikonos'] ?? null, 'package_id' => $order['package_id'] ?? null, 'success' => ($order['error'] == 'OK') ? true : false, 'error_message' => $order['error'], 'package_ids' => $this->parseValueFromResult($order['response'] ?? [], 'carrier_id'), 'balikonos_ids' => $this->parseValueFromResult($order['response'] ?? [], 'package_id'), 'labels_url' => $order['response']['labels_url'], ]; } return $result; } private function parseValueFromResult(array $result, string $key): array { $values = array_map(function ($item) use ($key) { return is_array($item) && isset($item[$key]) ? $item[$key] : null; }, $result); return array_filter($values); } public function getError() { foreach ($this->orders as $order) { if (!empty($order['response']) && is_array($order['response'])) { if (!in_array($order['response']['status'], ['200', '208'])) { return $this->getErrorMessage($order['response']); } } } return null; } public function deleteDeliverBatch($deliver) { $packages = sqlQueryBuilder() ->select('b.*') ->from('balikonos', 'b') ->where('(b.id_shop_delivery = :id_delivery) AND b.id_delivery IS NOT NULL AND b.close = 1') ->andWhere(Operator::equals(['b.user' => $this->getUser()])) ->setParameter('id_delivery', $deliver) ->execute()->fetchAll(); $packageIDs = array_column($packages, 'id_delivery'); try { $this->closeDeliveries($deliver, $packageIDs); } catch (BalikonosException $e) { // 208 == přeprava pod zaslaným eid již byla objednána dříve, takze objednavka uz musi v balikobotu byt if ($e->getCode() != 208) { throw $e; } } $dbcfg = \Settings::getDefault(); $responseIsSet = false; $notifiedOrders = []; foreach ($packages as $package) { // set carrierCode for future use in API requests $this->carrierCode = $dbcfg['balikobot']['delivery_type'][$package['id_shop_delivery']]['carrier']; $data = json_decode($package['data']); $delivery_number = $data->response->{0}->carrier_id ?? ''; $cod = $data->balikobot_array->cod_price ?? ''; $weight = $data->balikobot_array->weight ?? ''; $changeStatus = true; $cfg = Config::get(); if (array_key_exists('post_default_to', $cfg['Modules']['balikonos']) && $cfg['Modules']['balikonos']['post_default_to'] === null) { $changeStatus = false; } if (!isset($notifiedOrders[$package['id_order']]) && $changeStatus && $package['id_order'] !== null) { try { $result = $this->ChangeStatus( $package['id_order'], findModule('balikonos', 'post_default_to', 2), $delivery_number, $cod, $weight ); if (!$result) { $responseIsSet = true; $this->response = 'Svoz byl odeslán, ale nepodařilo se odeslat e-mail. Chyba: Neni prirazena \'Zprava uzivatelum\'! V nastaveni eshopu priradte ke kazde doprave zpravu!'; } $notifiedOrders[$package['id_order']] = true; } catch (\Exception $e) { getRaven()->captureException($e); } } sqlQueryBuilder()->update('balikonos')->set('close', 2) ->andWhere(Operator::equals(['id' => $package['id']])) ->execute(); } if (!$responseIsSet) { $this->response = '1'; } } public function getCarriers(): array { $carriers = getCache('balikobot_carriers'); if (is_array($carriers)) { return $carriers; } $carriers = []; $enableCache = true; try { $curl = $this->createCurl('', 'info/carriers'); $response = $this->curlExecute($curl); $response = json_decode_strict($response); } catch (\JsonException|BalikonosException $e) { // disable cache on error $enableCache = false; } if (!empty($response->status) && $response->status == '200' && !empty($response->carriers)) { $carriers = Mapping::mapKeys((array) $response->carriers, function ($index, $carrier) { return [$carrier->slug, $carrier->name]; }); } if ($enableCache) { // cache for a day setCache('balikobot_carriers', $carriers, 86400); } if (!empty($carriers)) { return $carriers; } // Fallback to static list if fetch failed return [ 'cp' => 'Česká pošta s.p.', 'dhl' => 'DHL Express', 'dhlsk' => 'DHL Parcel Slovensko', 'dhlde' => 'DHL DE', 'dpd' => 'Direct Parcel Distribution CZ s.r.o.', 'geis' => 'Geis CZ s.r.o.', 'gls' => 'General Logistics Systems Czech Republic s.r.o.', 'intime' => 'IN TIME - WE DO', 'pbh' => 'Pošta bez hranic (Frogman s.r.o.)', 'ppl' => 'PPL + DHL Freight', 'sp' => 'Slovenská pošta a.s.', 'sps' => 'Slovak Parcel Service s.r.o.', 'tnt' => 'TNT', 'toptrans' => 'TOPTRANS EU a.s.', 'ulozenka' => 'Uloženka - WE DO', 'ups' => 'UPS', 'zasilkovna' => 'Zásilkovna s.r.o.', 'gw' => 'Gebrüder Weiss Slovensko', 'gwcz' => 'Gebrüder Weiss Česká republika', 'messenger' => 'Messenger', 'fedex' => 'Fedex', 'fofr' => 'Fofr', 'japo' => 'JAPO Transport', 'lockers' => 'Lockers', 'kurier' => '123kurier', 'raben' => 'Raben Logistics', ]; } public function getAllCarrierServices(): array { $carrierServices = getCache('balikobot_carrier_services'); if (is_array($carrierServices)) { return $carrierServices; } $carrierServices = []; $enableCache = true; foreach ($this->getCarriers() as $carrierCode => $carrierName) { $curl = $this->createCurl('', $carrierCode.'/services'); try { $response = $this->curlExecute($curl); if (!$this->isResponseAuthorized($response)) { $enableCache = false; $this->authorizationFailed = true; } $response = json_decode_strict($response); } catch (\JsonException $e) { // disable cache on error $enableCache = false; } if (!empty($response->status) && $response->status == '200' && !empty($response->service_types)) { $carrierServices[$carrierCode] = (array) $response->service_types; } } if ($enableCache) { // cache for a day setCache('balikobot_carrier_services', $carrierServices, 86400); } return $carrierServices; } /** * @param string[] $packageIDs * * @return array|false eg: [0 => ['status_id' => 1, 'status_text' => 'Zásilka byla doručena příjemci.']] */ public function trackPackages(string $carrierCode, array $packageIDs, $timeout = null) { $packageIDs = array_map(function ($element) { return ['id' => $element]; }, $packageIDs); $curl = $this->createCurl( json_encode($packageIDs), 'v2/'.$carrierCode.'/track' ); if (isset($timeout)) { curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); } try { $response = json_decode_strict($this->curlExecute($curl), true); } catch (\JsonException $e) { return false; } return $response; } /** * @param null $timeout * * @return array|null eg: [0 => ['date' => '2019-01-22 00:00:00', 'name' => 'Doručení', 'status_id' => 1]] */ public function trackSinglePackage(string $carrierCode, string $packageID, $timeout = null) { $packagesTrackingInfo = $this->trackPackages($carrierCode, [$packageID], $timeout); return is_array($packagesTrackingInfo) ? array_pop($packagesTrackingInfo) : null; } public function isDeliverySupported($id_delivery) { return !empty(\Settings::getDefault()['balikobot']['delivery_type'][$id_delivery]['carrier']); } public function getHandoverUrl(string $carrierCode, $timeout = null) { $curl = $this->createCurl('', $carrierCode.'/orderview'); if (isset($timeout)) { curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); } try { $response = json_decode_strict($this->curlExecute($curl), true); } catch (\JsonException $e) { return null; } return $response['handover_url'] ?? null; } protected function addCustomField(array $order, array $balikobot, \Order $ordCls): array { foreach ($balikobot as $key => $value) { $order['balikobot_array'][$key] = $value; } return $order; } /** * @return array|false * * @throws BalikobotB2AValidationException * @throws BalikonosException */ public function getReturnDeliveryData(ReturnEntity|ReclamationEntity $entity) { $returnDeliveryEntity = $this->returnDeliveriesUtil->getReturnDeliveryEntity($entity->getIdReturnDelivery()); $balikobotName = $returnDeliveryEntity->getCustomData()['balikobot_user'] ?? false; if (!$balikobotName) { throw new BalikonosException('Nemáte vyplněné svozové místo v nastavení dopravy pro vratky.'); } $allCredentials = $this->getCollectionPlaces(); $selectedName = null; foreach ($allCredentials as $credentials) { if ($credentials['name'] == $balikobotName) { $selectedName = $credentials['user']; break; } } if (!$selectedName) { throw new BalikonosException('Neplatné svozové místo v nastavení dopravy pro vratky.'); } if ($entity instanceof ReturnEntity) { $orderIds = $this->returnsUtil->getOrderIdsByReturn($entity->getId()); $orderId = reset($orderIds); $orderId = $orderId['id']; } else { $orderId = $entity->getIdOrder(); } /** * @var IBalikobotB2AAdapter $adapter */ $adapter = $this->balikobotAdapterUtil->getB2AClassAdapter($returnDeliveryEntity->getType()); $baseBody = $returnDeliveryEntity->getClass()->getBaseLabelData($entity); $baseBody['eid'] = $entity->getCode(); if (isLocalDevelopment()) { $baseBody['eid'] .= 'LD'; } elseif (isDevelopment()) { $baseBody['eid'] .= 'D'; } $body = $adapter->transformData($entity, $orderId, $baseBody); $body = ['packages' => [$body]]; $carrierCode = $adapter->getCarrierCode(); $this->setCredentialsByName($selectedName); $curl = $this->createCurl(json_encode($body), $carrierCode.'/b2a', true); $response = json_decode($this->curlExecute($curl), true); if (($response['status'] ?? 500) != 200 || ($response['packages'][0]['status'] ?? 500) != 200) { $this->handleB2AErrors($response, $body); } return $adapter->transformResponse($response['packages'][0]); } protected function handleB2AErrors($response, $request) { $data = [ 'request' => $request, 'response' => $response, ]; $reasons = array_filter(array_map(function ($error) { return $error['message'] ?? false; }, $response['packages'][0]['errors'] ?? [])); if (($response['packages'][0]['status'] ?? 500) == 200) { getRaven()->captureMessage('Balikobot B2A api error', [], ['extra' => $data]); } throw new BalikobotB2AValidationException(translate('b2a_balikobot_error', 'Returns', isAdministration: true), data: $data, reasons: $reasons); } }