Files
grzanieplus.pl/plugins/stPaczkomatyPlugin/lib/stInPostApi.class.php
2025-03-12 17:06:23 +01:00

773 lines
23 KiB
PHP

<?php
class stInPostApi
{
const ALLEGRO_DELIVERY_TO_SERVICE_MAPPING = [
'5d9c7838-e05f-4dec-afdd-58e884170ba7' => '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%% <b>%%subject_id%%</b> posiada nieprawidłowy status',
'already_dispatched' => 'Zlecenie odbioru zostało już utworzone dla przesyłki <b>%%shipment%%</b>',
'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 <b>Kwota pobrania</b>',
'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("<br>", $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;
}
}