'inpost_courier_allegro', '85c3ad2f-4ec1-446c-866e-63473ed10e26' => 'inpost_courier_allegro', '9081532b-5ad3-467d-80bc-9252982e9dd8' => 'inpost_letter_allegro', '98f86f81-0018-41c5-ac83-073a56fc7021' => 'inpost_letter_allegro', '2488f7b7-5d1c-4d65-b85c-4cbcf253fd93' => 'inpost_locker_allegro', 'b20ef9e1-faa2-4f25-9032-adbea23e5cb9' => 'inpost_locker_allegro', '685d8b40-2571-4111-8937-9220b1710d4c' => 'inpost_locker_standard', '2653ca13-67c8-48c3-bbf8-ff9aa3f70ed3' => 'inpost_locker_standard', '1a228763-c17a-4b2d-88b0-63b082b04da6' => 'inpost_courier_standard', '999f8753-6340-48a0-8eba-46096f9749aa' => 'inpost_courier_standard', ]; const COURIER_SERVICES = [ 'inpost_courier_allegro', 'inpost_courier_standard', 'inpost_letter_allegro', ]; const DISPATCH_ORDER_STATUS = [ 'accepted' => 'Zaakceptowane', 'canceled' => 'Anulowane', 'sent' => 'Wysłane', 'new' => 'Nowe', 'rejected' => 'Odrzucone', 'done' => 'Wykonane', ]; /** * Singleton * * @var stInPostApi */ protected static $instance = null; /** * Konfiguracja inpost * * @var stConfig */ protected $config = null; /** * Poprawność konfiguracji api * * @var bool */ protected $isValid = null; /** * Ostatni błąd Api * * @var stdClass */ protected $lastError = null; /** * Ostatnie komunikat błędu Api * * @var string * */ protected $lastErrorMessage = null; /** * Lista statusów paczki * * @var array */ protected $statuses = null; /** * Lista metod wysyłki * * @var array */ protected $sendingMethods = array(); const SANDBOX_ENDPOINT = 'https://sandbox-api-shipx-pl.easypack24.net/v1'; const PRODUCTION_ENDPOINT = 'https://api-shipx-pl.easypack24.net/v1'; const VALIDATION_FAILED = 'validation_failed'; const ACCESS_FORBIDDEN = 'access_forbidden'; const INVALID_PARAMETER = 'invalid_parameter'; const OFFER_EXPIRED = 'offer_expired'; const TOKEN_INVALID = 'token_invalid'; const ROUTING_NOT_FOUND = 'routing_not_found'; /** * Zwraca instancję klasy stInPostApi * * @return stInPostApi */ public static function getInstance() { if (null === self::$instance) { self::$instance = new self(); self::$instance->initialize(); } return self::$instance; } /** * Sprawdza poprawność konfiguracji api * * @return bool */ public function isValid() { if ($this->config->get('token') && null === $this->isValid) { try { $this->getOrganizations(); $this->isValid = true; } catch(stInPostApiException $e) { $this->isValid = false; } } return $this->isValid; } public function initialize() { $this->config = stConfig::getInstance('stPaczkomatyBackend'); } /** * Zwraca paczkomat * * @param mixed $name Nazwa paczkomatu * @return stdClass|null * @throws stInPostApiException * @throws stInPostApiException */ public function getPoint($name) { return $this->callApi('get', '/points/' . $name); } /** * Zwraca listę paczkomatów * * @param array|null $params * @return stdClass|null * @throws stInPostApiException */ public function getPoints(array $params = null) { return $this->callApi('get', '/points', $params); } /** * Tworzy zlecenie odbioru * @param array $shipmentIds Id przesyłek * @return stdClass|null * @throws stInPostApiException */ public function createDispatchOrders(array $shipmentIds) { $config = stConfig::getInstance('stPaczkomatyBackend'); return $this->callApiWithOrganization('post', '/dispatch_orders', [ 'shipments' => $shipmentIds, 'address' => [ "street" => $config->get('sender_street'), "building_number" => $config->get('sender_building'), "city" => $config->get('sender_city'), "post_code" => $config->get('sender_post_code'), "country_code" => $config->get('sender_country_code'), ], 'name' => 'SOTESHOP', 'phone' => $config->get('sender_phone'), ]); } /** * Zwraca zlecenie odbioru * * @param int $id Id zlecenia odbioru * @return stdClass|null * @throws stInPostApiException */ public function getDispatchOrder(int $id) { return $this->callApi('get', '/dispatch_orders/'.$id); } /** * Zwraca zlecenia odbioru * * @param int $id Id zlecenia odbioru * @return stdClass|null * @throws stInPostApiException */ public function getDispatchOrders(array $ids) { return $this->callApiWithOrganization('get', '/dispatch_orders', [ 'id' => implode(',', $ids), ]); } /** * Usuwa zlecenie odbioru * @param int $id Id zlecenia odbioru * @return stdClass|null * @throws stInPostApiException */ public function deleteDispatchOrder(int $id) { return $this->callApi('delete', '/dispatch_orders/'.$id); } /** * Pobiera wydruk zlecenia odbioru * * @param int $id Id zlecenia odbioru * @param string $format Format wydruku (dostępne tylko Pdf) * @return string|null * @throws stInPostApiException */ public function getDispatchOrderPrintOut(int $id, string $format = 'Pdf') { return $this->callApi('get', '/dispatch_orders/'.$id.'/printout', ['format' => $format]); } /** * Pobiera listę statusów dla paczki * * @return array * @throws stInPostApiException */ public function getStatuses($lang = null) { $lang = sfContext::getInstance()->getUser()->getCulture(); if ($lang == 'en_US') { $lang = 'en_GB'; } if (!isset($this->statuses[$lang])) { $response = $this->callApi('get', '/statuses', array('lang' => $lang)); $statuses = array(); foreach ($response->items as $item) { $statuses[$item->name] = rtrim($item->title, '.'); } $this->statuses[$lang] = $statuses; } return $this->statuses[$lang]; } /** * Pobiera etykietę dla przesyłki * * @param mixed $id Id przesyłki * @return string|null * @throws stInPostApiException */ public function downloadLabel($id) { $format = $this->config->get('label_format', 'Pdf'); $params = array( 'type' => $format != 'Pdf' ? 'A6' : $this->config->get('label_type', 'normal'), 'format' => $format, ); return $this->callApi('get', '/shipments/' . $id . '/label', $params); } /** * Zwraca tytuł zlecenia odbioru * * @param string $name Nazwa statusu * @return string|null */ public function getDispatchOrderStatusTitleByName(string $name): ?string { return isset(self::DISPATCH_ORDER_STATUS[$name]) ? self::DISPATCH_ORDER_STATUS[$name] : null; } /** * Pobiera tytuł statusu po jego nazwie * * @param string $name * @return string * @throws stInPostApiException */ public function getStatusTitleByName($name) { $statuses = $this->getStatuses(); return isset($statuses[$name]) ? $statuses[$name] : null; } /** * Zwraca listę organizacji * * @param array $params * @return stdClass|null * @throws stInPostApiException */ public function getOrganizations(array $params = array()) { $params = array_merge(array( 'sort_by' => 'name', 'sort_order' => 'asc', ), $params ); return $this->callApi('get', '/organizations', $params); } /** * Zwraca organizację * * @param mixed $id Id organizacji * @return stdClass|null * @throws stInPostApiException */ public function getOrganization($id) { return $this->callApi('get', '/organizations/' . $id); } /** * Zwraca metody nadania * * @param string $service Opcjonalna nazwa serwisu * @return array * @throws stInPostApiException */ public function getSendingMethods($service = null) { if (!isset($this->sendingMethods[$service])) { $response = $this->callApi('get', '/sending_methods', $service ? array('service' => $service) : null); $methods = array(); $i18n = sfContext::getInstance()->getI18N(); foreach ($response->items as $method) { $methods[$method->id] = $i18n->__($method->name, null, 'stPaczkomatyBackend'); } $this->sendingMethods[$service] = $methods; } return $this->sendingMethods[$service]; } /** * Tworzy przesyłkę * * @param array $params * @return stdClass|null * @throws stInPostApiException */ public function createShipment(array $params) { return $this->callApiWithOrganization('post', '/shipments', $params); } /** * Pobiera przesyłki * @param array $params * @return stdClass|null * @throws stInPostApiException */ public function getShipments(array $params) { return $this->callApiWithOrganization('get', '/shipments', $params); } /** * Pobiera przesyłki po numerze id * * @param array $ids Id przesyłek * @return stdClass|null * @throws stInPostApiException */ public function getShipmentsById(array $ids) { return $this->getShipments([ 'id' => implode(',', $ids), ]); } /** * Usuwa przesyłkę * @param $id * @return stdClass|null * @throws stInPostApiException */ public function deleteShipment($id) { return $this->callApi('delete', '/shipments/' . $id); } /** * Pobiera przesyłkę * @param $id * @return stdClass|null * @throws stInPostApiException */ public function getShipment($id) { return $this->callApi('get', '/shipments/' . $id); } /** * Pobiera informacje o statusie przesyłki * @param $number * @return stdClass|null * @throws stInPostApiException */ public function getTracking($number) { return $this->callApi('get', '/tracking/' . $number); } /** * Pobiera przesyłkę po numerze trackingowym * @param $number * @return mixed|null * @throws stInPostApiException */ public function getShipmentByTrackingNumber($number) { $response = $this->getShipments(array('tracking_number' => $number)); return $response->items ? $response->items[0] : null; } /** * Zwraca ostatni błąd api * * @return stdClass */ public function getLastError() { return $this->lastError; } /** * Zwraca ostatni komunikat błędu api * * @return string */ public function getLastErrorMessage() { return $this->lastErrorMessage; } public function isSandbox() { return $this->config->get('sandbox'); } /** * Wykonuje żądanie API dla danej organizacji * * @param string $method Metoda (post,get,put,delete) * @param string $resource Zasób * @param array|null $data Dodatkowe parametry * @param int|null $organizationId Opcjonalne id orgranizacji (domyślnie brana z konfiguracji) * @return stdClass|null * @throws stInPostApiException */ public function callApiWithOrganization(string $method, string $resource, ?array $data = null, ?int $organizationId = null) { if (null === $organizationId) { $organizationId = $this->config->get('organization'); } return $this->callApi($method, '/organizations/' . $organizationId . $resource, $data); } /** * Wykonuje żądanie API * @param $method Metoda (post,get,put,delete) * @param $resource Zasób * @param array|null $data Dodatkowe parametry * @return stdClass|null * @throws stInPostApiException */ public function callApi($method, $resource, array $data = null) { $this->lastError = null; $this->lastErrorMessage = null; $responseHeaders = array(); $headers = array( 'Content-Type: application/json', 'Authorization: Bearer ' . $this->config->get('token'), // 'Accept-Language: ' . sfContext::getInstance()->getUser()->getCulture(), ); $ch = curl_init(); curl_setopt($ch, CURLINFO_HEADER_OUT, true); $url = $this->getEndpoint() . $resource; if ($method == 'post') { curl_setopt($ch, CURLOPT_POST, 1); } else { curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($method)); } if (($method == 'post' || $method == 'put') && $data) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); } elseif ($data) { $url = $url . '?' . http_build_query($data, '', '&'); } // OPTIONS: curl_setopt($ch, CURLOPT_URL, $url); curl_setopt ($ch, CURLOPT_SSLVERSION, 6); // curl_setopt($ch, CURLINFO_HEADER_OUT, true); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); curl_setopt($ch, CURLOPT_HEADERFUNCTION, function($curl, $header) use (&$responseHeaders) { $len = strlen($header); $header = explode(':', $header, 2); if (count($header) < 2) // ignore invalid headers return $len; $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); return $len; } ); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); if (sfConfig::get('sf_debug') && sfConfig::get('sf_logging_enabled')) { sfLogger::getInstance()->info("{stInPostApi} Calling: " . $url); sfLogger::getInstance()->info("{stInPostApi} With HEADERS: " . json_encode($headers)); if (!empty($data)) { sfLogger::getInstance()->info("{stInPostApi} With Payload: " . json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } } $result = curl_exec($ch); $error = curl_error($ch); curl_close($ch); if ($error) { $this->lastErrorMessage = $error; throw new stInPostApiException($error); } if (false !== strpos($responseHeaders['content-type'], 'application/json')) { $response = json_decode($result); if (sfConfig::get('sf_debug') && sfConfig::get('sf_logging_enabled')) { if (isset($response->error)) { sfLogger::getInstance()->err("{stInPostApi} Response: " . json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } else { sfLogger::getInstance()->info("{stInPostApi} Response: " . json_encode($response, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } } if (isset($response->error)) { $this->lastError = $response; $message = self::translateErrorMessage($response->error); if (null === $message) { if (isset($response->message)) { $message = $response->message; } elseif (isset($response->description)) { $message = $response->description; } } $this->lastErrorMessage = $message; throw new stInPostApiException($this->lastErrorMessage); } if (isset($response->transactions) && !empty($response->transactions)) { foreach ($response->transactions as $transaction) { if ($transaction->status == 'failure') { $this->lastError = $transaction->details; $this->lastErrorMessage = self::formatTransactionError($transaction->details); throw new stInPostApiTransactionException($this->lastErrorMessage, $transaction->details->error); } } } } else { $response = $result; } return $response; } public function getEndpoint() { return $this->isSandbox() ? self::SANDBOX_ENDPOINT : self::PRODUCTION_ENDPOINT; } /** * Zwraca treść błędu zrozumiałą dla klienta * * @param string $error * @param array $i18nMessageParams * @return null|string */ public static function translateErrorMessage(string $error, array $i18nMessageParams = []): ?string { $i18n = sfContext::getInstance()->getI18N(); $errors = array( 'resource_not_found' => 'Szukany zasób nie został odnaleziony.', 'access_forbidden' => 'Dostęp do określonego zasobu jest zabroniony.', 'invalid_parameter' => 'Przekazano niepoprawną wartość dla określonego parametru w URI. Szczegóły dostępne pod kluczem description odpowiedzi błędu.', 'validation_failed' => 'Błąd walidacji. Dane przesłane metodą POST są niepoprawne', 'offer_expired' => 'Zakupienie oferty jest niemożliwe, ponieważ upłynął termin jej ważności.', 'token_invalid' => 'Podany token jest nieprawidłowy', 'routing_not_found' => 'Szukany zasób nie został odnaleziony.', 'invalid_end_of_week_collection' => 'Opcja "Paczka w weekend" jest dostępna tylko w określonym przedziale czasowym opisanym w informacjach ogólnych usługi', 'invalid_target_point_for_end_of_week_collection' => 'Opcja "Paczka w weekend" jest dostępna tylko dla Paczkomatów', 'invalid_allegro_for_end_of_week_collection' => 'Opcja "Paczka w weekend" nie jest dostępna dla przesyłek Allegro', 'invalid_status' => '%%subject%% %%subject_id%% posiada nieprawidłowy status', 'already_dispatched' => 'Zlecenie odbioru zostało już utworzone dla przesyłki %%shipment%%', 'various_types_of_carrier' => 'Przesyłki muszą być tego samego rodzaju', 'should_be_greater_or_equal_than_cod' => 'Wartość powinna być większa lub równa wartości pola Kwota pobrania', 'required' => 'Wartość dla określonego parametru jest wymagana.', 'too_short' => 'Za mała ilość znaków', 'too_small' => 'Za mała wartość', 'too_long' => 'Za duża ilość znaków', 'not_a_number' => 'Wartość nie jest liczbą', 'not_an_integer' => 'Wartość nie jest liczbą całkowitą', 'unknow_error' => 'Wystąpił nieznany błąd', 'invalid' => 'Wprowadzona wartość jest niepoprawna.', 'invalid_service_for_end_of_week_collection' => 'Przesyłka kurierska nie obsługuję Paczki w weekend', 'invalid_end_of_week_collection' => 'Usługa dostępna od czwartku od godziny 20:00 do soboty do godziny 13:00. Nie uwzględnia świąt ani dni wolnych', 'invalid_target_point_for_end_of_week_collection' => 'Usługa nie jest dostępna dla wybranego paczkomatu odbiorcy', 'invalid_target_point_247_for_end_of_week_collection' => 'Usługa nie jest dostępna dla wybranego paczkomatu odbiorcy', 'invalid_allegro_for_end_of_week_collection' => 'Usługa nie jest dostępna dla przesyłek Allegro', 'debt_collection' => 'Transakcja nie może zostać zrealizowana ze względu na saldo Twojego konta.', 'invalid_action' => 'Nie możesz usunąc przesyłki z podanym statusem', 'internal_server_error' => 'Wystąpił wewnętrzny błąd serwera InPost', ); if (isset($errors[$error])) { return $i18n->__($errors[$error], $i18nMessageParams, 'stPaczkomatyBackend'); } return $i18n->__('Wystąpił nieobsługiwany wyjątek (błąd: %error%)', ['%error%' => $error], 'stPaczkomatyBackend'); } /** * Zwraca sformatowaną treść błędów * * @param mixed $errors * @param array $i18nMessageParams * @return string */ public static function formatDetailError($errors, array $i18nMessageParams = []) { $message = []; if (!is_array($errors)) { $errors = array($errors); } foreach ($errors as $error) { if (is_object($error)) { foreach (get_object_vars($error) as $value) { $message[] = self::translateErrorMessage($value, $i18nMessageParams); } } else { $message[] = self::translateErrorMessage($error, $i18nMessageParams); } } return implode("
", $message); } public static function formatTransactionError($transactionError): string { return $transactionError->message == $transactionError->error ? self::translateErrorMessage($transactionError->error) : $transactionError->message; } public static function formatUnavailableError($errors) { $i18n = sfContext::getInstance()->getI18N(); if (isset($errors[0]->sender) && $errors[0]->sender == 'post_code_invalid') { $i18n = sfContext::getInstance()->getI18N(); sfLoader::loadHelpers(array('Helper', 'stUrl')); return $i18n->__('Nieprawidłowy kod pocztowy nadawcy. Przejdź do %link%, aby skorygować problem.', array( '%link%' => st_link_to($i18n->__('konfiguracji'), '@stPaczkomatyPlugin?action=config', array('target' => '_blank')), )); } return $i18n->__('Wystąpił nieobsługiwany wyjątek (błąd: "%error%")', [ '%error%' => json_encode($errors, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); } /** * Zwraca nazwę serwisu InPost na podstawie id dostawy allegro * * @param string $allegroDeliveryId Id dostawy allegro * @return null|string */ public static function getAllegroDeliveryToServiceMapping(string $allegroDeliveryId): ?string { return isset(self::ALLEGRO_DELIVERY_TO_SERVICE_MAPPING[$allegroDeliveryId]) ? self::ALLEGRO_DELIVERY_TO_SERVICE_MAPPING[$allegroDeliveryId] : null; } }