Carrier.php 65 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Ups\Model;
  7. use Magento\Framework\HTTP\ClientFactory;
  8. use Magento\Framework\Xml\Security;
  9. use Magento\Quote\Model\Quote\Address\RateRequest;
  10. use Magento\Quote\Model\Quote\Address\RateResult\Error;
  11. use Magento\Shipping\Model\Carrier\AbstractCarrierOnline;
  12. use Magento\Shipping\Model\Carrier\CarrierInterface;
  13. use Magento\Shipping\Model\Rate\Result;
  14. use Magento\Shipping\Model\Simplexml\Element;
  15. use Magento\Ups\Helper\Config;
  16. /**
  17. * UPS shipping implementation
  18. * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
  19. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  20. */
  21. class Carrier extends AbstractCarrierOnline implements CarrierInterface
  22. {
  23. /**
  24. * Code of the carrier
  25. *
  26. * @var string
  27. */
  28. const CODE = 'ups';
  29. /**
  30. * Delivery Confirmation level based on origin/destination
  31. */
  32. const DELIVERY_CONFIRMATION_SHIPMENT = 1;
  33. const DELIVERY_CONFIRMATION_PACKAGE = 2;
  34. /**
  35. * Code of the carrier
  36. *
  37. * @var string
  38. */
  39. protected $_code = self::CODE;
  40. /**
  41. * Rate request data
  42. *
  43. * @var RateRequest
  44. */
  45. protected $_request;
  46. /**
  47. * Rate result data
  48. *
  49. * @var Result
  50. */
  51. protected $_result;
  52. /**
  53. * Base currency rate
  54. *
  55. * @var float
  56. */
  57. protected $_baseCurrencyRate;
  58. /**
  59. * Xml access request
  60. *
  61. * @var string
  62. */
  63. protected $_xmlAccessRequest;
  64. /**
  65. * Default cgi gateway url
  66. *
  67. * @var string
  68. */
  69. protected $_defaultCgiGatewayUrl = 'http://www.ups.com:80/using/services/rave/qcostcgi.cgi';
  70. /**
  71. * Test urls for shipment
  72. *
  73. * @var array
  74. */
  75. protected $_defaultUrls = [
  76. 'ShipConfirm' => 'https://wwwcie.ups.com/ups.app/xml/ShipConfirm',
  77. 'ShipAccept' => 'https://wwwcie.ups.com/ups.app/xml/ShipAccept',
  78. ];
  79. /**
  80. * Live urls for shipment
  81. *
  82. * @var array
  83. */
  84. protected $_liveUrls = [
  85. 'ShipConfirm' => 'https://onlinetools.ups.com/ups.app/xml/ShipConfirm',
  86. 'ShipAccept' => 'https://onlinetools.ups.com/ups.app/xml/ShipAccept',
  87. ];
  88. /**
  89. * Container types that could be customized for UPS carrier
  90. *
  91. * @var string[]
  92. */
  93. protected $_customizableContainerTypes = ['CP', 'CSP'];
  94. /**
  95. * @var \Magento\Framework\Locale\FormatInterface
  96. */
  97. protected $_localeFormat;
  98. /**
  99. * @var \Psr\Log\LoggerInterface
  100. */
  101. protected $_logger;
  102. /**
  103. * @var Config
  104. */
  105. protected $configHelper;
  106. /**
  107. * @inheritdoc
  108. */
  109. protected $_debugReplacePrivateDataKeys = [
  110. 'UserId', 'Password', 'AccessLicenseNumber'
  111. ];
  112. /**
  113. * @var ClientFactory
  114. */
  115. private $httpClientFactory;
  116. /**
  117. * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
  118. * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory
  119. * @param \Psr\Log\LoggerInterface $logger
  120. * @param Security $xmlSecurity
  121. * @param \Magento\Shipping\Model\Simplexml\ElementFactory $xmlElFactory
  122. * @param \Magento\Shipping\Model\Rate\ResultFactory $rateFactory
  123. * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory
  124. * @param \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory
  125. * @param \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory
  126. * @param \Magento\Shipping\Model\Tracking\Result\StatusFactory $trackStatusFactory
  127. * @param \Magento\Directory\Model\RegionFactory $regionFactory
  128. * @param \Magento\Directory\Model\CountryFactory $countryFactory
  129. * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory
  130. * @param \Magento\Directory\Helper\Data $directoryData
  131. * @param \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry
  132. * @param \Magento\Framework\Locale\FormatInterface $localeFormat
  133. * @param Config $configHelper
  134. * @param ClientFactory $httpClientFactory
  135. * @param array $data
  136. *
  137. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  138. */
  139. public function __construct(
  140. \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
  141. \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
  142. \Psr\Log\LoggerInterface $logger,
  143. Security $xmlSecurity,
  144. \Magento\Shipping\Model\Simplexml\ElementFactory $xmlElFactory,
  145. \Magento\Shipping\Model\Rate\ResultFactory $rateFactory,
  146. \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
  147. \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory,
  148. \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory,
  149. \Magento\Shipping\Model\Tracking\Result\StatusFactory $trackStatusFactory,
  150. \Magento\Directory\Model\RegionFactory $regionFactory,
  151. \Magento\Directory\Model\CountryFactory $countryFactory,
  152. \Magento\Directory\Model\CurrencyFactory $currencyFactory,
  153. \Magento\Directory\Helper\Data $directoryData,
  154. \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
  155. \Magento\Framework\Locale\FormatInterface $localeFormat,
  156. Config $configHelper,
  157. ClientFactory $httpClientFactory,
  158. array $data = []
  159. ) {
  160. parent::__construct(
  161. $scopeConfig,
  162. $rateErrorFactory,
  163. $logger,
  164. $xmlSecurity,
  165. $xmlElFactory,
  166. $rateFactory,
  167. $rateMethodFactory,
  168. $trackFactory,
  169. $trackErrorFactory,
  170. $trackStatusFactory,
  171. $regionFactory,
  172. $countryFactory,
  173. $currencyFactory,
  174. $directoryData,
  175. $stockRegistry,
  176. $data
  177. );
  178. $this->httpClientFactory = $httpClientFactory;
  179. $this->_localeFormat = $localeFormat;
  180. $this->configHelper = $configHelper;
  181. }
  182. /**
  183. * Collect and get rates/errors
  184. *
  185. * @param RateRequest $request
  186. * @return Result|Error|bool
  187. */
  188. public function collectRates(RateRequest $request)
  189. {
  190. $this->setRequest($request);
  191. if (!$this->canCollectRates()) {
  192. return $this->getErrorMessage();
  193. }
  194. $this->setRequest($request);
  195. $this->_result = $this->_getQuotes();
  196. $this->_updateFreeMethodQuote($request);
  197. return $this->getResult();
  198. }
  199. /**
  200. * Prepare and set request to this instance
  201. *
  202. * @param RateRequest $request
  203. * @return $this
  204. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  205. * @SuppressWarnings(PHPMD.NPathComplexity)
  206. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  207. */
  208. public function setRequest(RateRequest $request)
  209. {
  210. $this->_request = $request;
  211. $rowRequest = new \Magento\Framework\DataObject();
  212. if ($request->getLimitMethod()) {
  213. $rowRequest->setAction($this->configHelper->getCode('action', 'single'));
  214. $rowRequest->setProduct($request->getLimitMethod());
  215. } else {
  216. $rowRequest->setAction($this->configHelper->getCode('action', 'all'));
  217. $rowRequest->setProduct('GND' . $this->getConfigData('dest_type'));
  218. }
  219. if ($request->getUpsPickup()) {
  220. $pickup = $request->getUpsPickup();
  221. } else {
  222. $pickup = $this->getConfigData('pickup');
  223. }
  224. $rowRequest->setPickup($this->configHelper->getCode('pickup', $pickup));
  225. if ($request->getUpsContainer()) {
  226. $container = $request->getUpsContainer();
  227. } else {
  228. $container = $this->getConfigData('container');
  229. }
  230. $rowRequest->setContainer($this->configHelper->getCode('container', $container));
  231. if ($request->getUpsDestType()) {
  232. $destType = $request->getUpsDestType();
  233. } else {
  234. $destType = $this->getConfigData('dest_type');
  235. }
  236. $rowRequest->setDestType($this->configHelper->getCode('dest_type', $destType));
  237. if ($request->getOrigCountry()) {
  238. $origCountry = $request->getOrigCountry();
  239. } else {
  240. $origCountry = $this->_scopeConfig->getValue(
  241. \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_COUNTRY_ID,
  242. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  243. $request->getStoreId()
  244. );
  245. }
  246. $rowRequest->setOrigCountry($this->_countryFactory->create()->load($origCountry)->getData('iso2_code'));
  247. if ($request->getOrigRegionCode()) {
  248. $origRegionCode = $request->getOrigRegionCode();
  249. } else {
  250. $origRegionCode = $this->_scopeConfig->getValue(
  251. \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_REGION_ID,
  252. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  253. $request->getStoreId()
  254. );
  255. }
  256. if (is_numeric($origRegionCode)) {
  257. $origRegionCode = $this->_regionFactory->create()->load($origRegionCode)->getCode();
  258. }
  259. $rowRequest->setOrigRegionCode($origRegionCode);
  260. if ($request->getOrigPostcode()) {
  261. $rowRequest->setOrigPostal($request->getOrigPostcode());
  262. } else {
  263. $rowRequest->setOrigPostal(
  264. $this->_scopeConfig->getValue(
  265. \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_ZIP,
  266. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  267. $request->getStoreId()
  268. )
  269. );
  270. }
  271. if ($request->getOrigCity()) {
  272. $rowRequest->setOrigCity($request->getOrigCity());
  273. } else {
  274. $rowRequest->setOrigCity(
  275. $this->_scopeConfig->getValue(
  276. \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_CITY,
  277. \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
  278. $request->getStoreId()
  279. )
  280. );
  281. }
  282. if ($request->getDestCountryId()) {
  283. $destCountry = $request->getDestCountryId();
  284. } else {
  285. $destCountry = self::USA_COUNTRY_ID;
  286. }
  287. //for UPS, puero rico state for US will assume as puerto rico country
  288. if ($destCountry == self::USA_COUNTRY_ID && ($request->getDestPostcode() == '00912' ||
  289. $request->getDestRegionCode() == self::PUERTORICO_COUNTRY_ID)
  290. ) {
  291. $destCountry = self::PUERTORICO_COUNTRY_ID;
  292. }
  293. // For UPS, Guam state of the USA will be represented by Guam country
  294. if ($destCountry == self::USA_COUNTRY_ID && $request->getDestRegionCode() == self::GUAM_REGION_CODE) {
  295. $destCountry = self::GUAM_COUNTRY_ID;
  296. }
  297. // For UPS, Las Palmas and Santa Cruz de Tenerife will be represented by Canary Islands country
  298. if ($destCountry === 'ES' &&
  299. ($request->getDestRegionCode() === 'Las Palmas'
  300. || $request->getDestRegionCode() === 'Santa Cruz de Tenerife')
  301. ) {
  302. $destCountry = 'IC';
  303. }
  304. $country = $this->_countryFactory->create()->load($destCountry);
  305. $rowRequest->setDestCountry($country->getData('iso2_code') ?: $destCountry);
  306. $rowRequest->setDestRegionCode($request->getDestRegionCode());
  307. if ($request->getDestPostcode()) {
  308. $rowRequest->setDestPostal($request->getDestPostcode());
  309. }
  310. $weight = $this->getTotalNumOfBoxes($request->getPackageWeight());
  311. $weight = $this->_getCorrectWeight($weight);
  312. $rowRequest->setWeight($weight);
  313. if ($request->getFreeMethodWeight() != $request->getPackageWeight()) {
  314. $rowRequest->setFreeMethodWeight($request->getFreeMethodWeight());
  315. }
  316. $rowRequest->setValue($request->getPackageValue());
  317. $rowRequest->setValueWithDiscount($request->getPackageValueWithDiscount());
  318. if ($request->getUpsUnitMeasure()) {
  319. $unit = $request->getUpsUnitMeasure();
  320. } else {
  321. $unit = $this->getConfigData('unit_of_measure');
  322. }
  323. $rowRequest->setUnitMeasure($unit);
  324. $rowRequest->setIsReturn($request->getIsReturn());
  325. $rowRequest->setBaseSubtotalInclTax($request->getBaseSubtotalInclTax());
  326. $this->_rawRequest = $rowRequest;
  327. return $this;
  328. }
  329. /**
  330. * Get correct weight
  331. *
  332. * Namely:
  333. * Checks the current weight to comply with the minimum weight standards set by the carrier.
  334. * Then strictly rounds the weight up until the first significant digit after the decimal point.
  335. *
  336. * @param float|int $weight
  337. * @return float
  338. */
  339. protected function _getCorrectWeight($weight)
  340. {
  341. $minWeight = $this->getConfigData('min_package_weight');
  342. if ($weight < $minWeight) {
  343. $weight = $minWeight;
  344. }
  345. //rounds a number to one significant figure
  346. $weight = ceil($weight * 10) / 10;
  347. return $weight;
  348. }
  349. /**
  350. * Get result of request
  351. *
  352. * @return Result
  353. */
  354. public function getResult()
  355. {
  356. return $this->_result;
  357. }
  358. /**
  359. * Do remote request for and handle errors
  360. *
  361. * @return Result|null
  362. */
  363. protected function _getQuotes()
  364. {
  365. switch ($this->getConfigData('type')) {
  366. case 'UPS':
  367. return $this->_getCgiQuotes();
  368. case 'UPS_XML':
  369. return $this->_getXmlQuotes();
  370. default:
  371. break;
  372. }
  373. return null;
  374. }
  375. /**
  376. * Set free method request
  377. *
  378. * @param string $freeMethod
  379. * @return void
  380. */
  381. protected function _setFreeMethodRequest($freeMethod)
  382. {
  383. $r = $this->_rawRequest;
  384. $weight = $this->getTotalNumOfBoxes($r->getFreeMethodWeight());
  385. $weight = $this->_getCorrectWeight($weight);
  386. $r->setWeight($weight);
  387. $r->setAction($this->configHelper->getCode('action', 'single'));
  388. $r->setProduct($freeMethod);
  389. }
  390. /**
  391. * Get cgi rates
  392. *
  393. * @return Result
  394. */
  395. protected function _getCgiQuotes()
  396. {
  397. $rowRequest = $this->_rawRequest;
  398. if (self::USA_COUNTRY_ID == $rowRequest->getDestCountry()) {
  399. $destPostal = substr($rowRequest->getDestPostal(), 0, 5);
  400. } else {
  401. $destPostal = $rowRequest->getDestPostal();
  402. }
  403. $params = [
  404. 'accept_UPS_license_agreement' => 'yes',
  405. '10_action' => $rowRequest->getAction(),
  406. '13_product' => $rowRequest->getProduct(),
  407. '14_origCountry' => $rowRequest->getOrigCountry(),
  408. '15_origPostal' => $rowRequest->getOrigPostal(),
  409. 'origCity' => $rowRequest->getOrigCity(),
  410. '19_destPostal' => $destPostal,
  411. '22_destCountry' => $rowRequest->getDestCountry(),
  412. '23_weight' => $rowRequest->getWeight(),
  413. '47_rate_chart' => $rowRequest->getPickup(),
  414. '48_container' => $rowRequest->getContainer(),
  415. '49_residential' => $rowRequest->getDestType(),
  416. 'weight_std' => strtolower($rowRequest->getUnitMeasure()),
  417. ];
  418. $params['47_rate_chart'] = $params['47_rate_chart']['label'];
  419. $responseBody = $this->_getCachedQuotes($params);
  420. if ($responseBody === null) {
  421. $debugData = ['request' => $params];
  422. try {
  423. $url = $this->getConfigData('gateway_url');
  424. if (!$url) {
  425. $url = $this->_defaultCgiGatewayUrl;
  426. }
  427. $client = new \Zend_Http_Client();
  428. $client->setUri($url);
  429. $client->setConfig(['maxredirects' => 0, 'timeout' => 30]);
  430. $client->setParameterGet($params);
  431. $response = $client->request();
  432. $responseBody = $response->getBody();
  433. $debugData['result'] = $responseBody;
  434. $this->_setCachedQuotes($params, $responseBody);
  435. } catch (\Throwable $e) {
  436. $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
  437. $responseBody = '';
  438. }
  439. $this->_debug($debugData);
  440. }
  441. return $this->_parseCgiResponse($responseBody);
  442. }
  443. /**
  444. * Get shipment by code
  445. *
  446. * @param string $code
  447. * @param string $origin
  448. * @return array|bool
  449. */
  450. public function getShipmentByCode($code, $origin = null)
  451. {
  452. if ($origin === null) {
  453. $origin = $this->getConfigData('origin_shipment');
  454. }
  455. $arr = $this->configHelper->getCode('originShipment', $origin);
  456. if (isset($arr[$code])) {
  457. return $arr[$code];
  458. } else {
  459. return false;
  460. }
  461. }
  462. /**
  463. * Prepare shipping rate result based on response
  464. *
  465. * @param string $response
  466. * @return Result
  467. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  468. */
  469. protected function _parseCgiResponse($response)
  470. {
  471. $costArr = [];
  472. $priceArr = [];
  473. if (strlen(trim($response)) > 0) {
  474. $rRows = explode("\n", $response);
  475. $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
  476. foreach ($rRows as $rRow) {
  477. $row = explode('%', $rRow);
  478. switch (substr($row[0], -1)) {
  479. case 3:
  480. case 4:
  481. if (in_array($row[1], $allowedMethods)) {
  482. $responsePrice = $this->_localeFormat->getNumber($row[8]);
  483. $costArr[$row[1]] = $responsePrice;
  484. $priceArr[$row[1]] = $this->getMethodPrice($responsePrice, $row[1]);
  485. }
  486. break;
  487. case 5:
  488. $errorTitle = $row[1];
  489. $message = __(
  490. 'Sorry, something went wrong. Please try again or contact us and we\'ll try to help.'
  491. );
  492. $this->_logger->debug($message . ': ' . $errorTitle);
  493. break;
  494. case 6:
  495. if (in_array($row[3], $allowedMethods)) {
  496. $responsePrice = $this->_localeFormat->getNumber($row[10]);
  497. $costArr[$row[3]] = $responsePrice;
  498. $priceArr[$row[3]] = $this->getMethodPrice($responsePrice, $row[3]);
  499. }
  500. break;
  501. default:
  502. break;
  503. }
  504. }
  505. asort($priceArr);
  506. }
  507. $result = $this->_rateFactory->create();
  508. if (empty($priceArr)) {
  509. $error = $this->_rateErrorFactory->create();
  510. $error->setCarrier('ups');
  511. $error->setCarrierTitle($this->getConfigData('title'));
  512. $error->setErrorMessage($this->getConfigData('specificerrmsg'));
  513. $result->append($error);
  514. } else {
  515. foreach ($priceArr as $method => $price) {
  516. $rate = $this->_rateMethodFactory->create();
  517. $rate->setCarrier('ups');
  518. $rate->setCarrierTitle($this->getConfigData('title'));
  519. $rate->setMethod($method);
  520. $methodArray = $this->configHelper->getCode('method', $method);
  521. $rate->setMethodTitle($methodArray);
  522. $rate->setCost($costArr[$method]);
  523. $rate->setPrice($price);
  524. $result->append($rate);
  525. }
  526. }
  527. return $result;
  528. }
  529. /**
  530. * Get xml rates
  531. *
  532. * @return Result
  533. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  534. * @SuppressWarnings(PHPMD.NPathComplexity)
  535. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  536. */
  537. protected function _getXmlQuotes()
  538. {
  539. $url = $this->getConfigData('gateway_xml_url');
  540. $this->setXMLAccessRequest();
  541. $xmlRequest = $this->_xmlAccessRequest;
  542. $debugData['accessRequest'] = $this->filterDebugData($xmlRequest);
  543. $rowRequest = $this->_rawRequest;
  544. if (self::USA_COUNTRY_ID == $rowRequest->getDestCountry()) {
  545. $destPostal = substr($rowRequest->getDestPostal(), 0, 5);
  546. } else {
  547. $destPostal = $rowRequest->getDestPostal();
  548. }
  549. $params = [
  550. 'accept_UPS_license_agreement' => 'yes',
  551. '10_action' => $rowRequest->getAction(),
  552. '13_product' => $rowRequest->getProduct(),
  553. '14_origCountry' => $rowRequest->getOrigCountry(),
  554. '15_origPostal' => $rowRequest->getOrigPostal(),
  555. 'origCity' => $rowRequest->getOrigCity(),
  556. 'origRegionCode' => $rowRequest->getOrigRegionCode(),
  557. '19_destPostal' => $destPostal,
  558. '22_destCountry' => $rowRequest->getDestCountry(),
  559. 'destRegionCode' => $rowRequest->getDestRegionCode(),
  560. '23_weight' => $rowRequest->getWeight(),
  561. '47_rate_chart' => $rowRequest->getPickup(),
  562. '48_container' => $rowRequest->getContainer(),
  563. '49_residential' => $rowRequest->getDestType(),
  564. ];
  565. if ($params['10_action'] == '4') {
  566. $params['10_action'] = 'Shop';
  567. $serviceCode = null;
  568. } else {
  569. $params['10_action'] = 'Rate';
  570. $serviceCode = $rowRequest->getProduct() ? $rowRequest->getProduct() : null;
  571. }
  572. $serviceDescription = $serviceCode ? $this->getShipmentByCode($serviceCode) : '';
  573. $xmlParams = <<<XMLRequest
  574. <?xml version="1.0"?>
  575. <RatingServiceSelectionRequest xml:lang="en-US">
  576. <Request>
  577. <TransactionReference>
  578. <CustomerContext>Rating and Service</CustomerContext>
  579. <XpciVersion>1.0</XpciVersion>
  580. </TransactionReference>
  581. <RequestAction>Rate</RequestAction>
  582. <RequestOption>{$params['10_action']}</RequestOption>
  583. </Request>
  584. <PickupType>
  585. <Code>{$params['47_rate_chart']['code']}</Code>
  586. <Description>{$params['47_rate_chart']['label']}</Description>
  587. </PickupType>
  588. <Shipment>
  589. XMLRequest;
  590. if ($serviceCode !== null) {
  591. $xmlParams .= "<Service>" .
  592. "<Code>{$serviceCode}</Code>" .
  593. "<Description>{$serviceDescription}</Description>" .
  594. "</Service>";
  595. }
  596. $xmlParams .= <<<XMLRequest
  597. <Shipper>
  598. XMLRequest;
  599. if ($this->getConfigFlag('negotiated_active') && ($shipperNumber = $this->getConfigData('shipper_number'))) {
  600. $xmlParams .= "<ShipperNumber>{$shipperNumber}</ShipperNumber>";
  601. }
  602. if ($rowRequest->getIsReturn()) {
  603. $shipperCity = '';
  604. $shipperPostalCode = $params['19_destPostal'];
  605. $shipperCountryCode = $params['22_destCountry'];
  606. $shipperStateProvince = $params['destRegionCode'];
  607. } else {
  608. $shipperCity = $params['origCity'];
  609. $shipperPostalCode = $params['15_origPostal'];
  610. $shipperCountryCode = $params['14_origCountry'];
  611. $shipperStateProvince = $params['origRegionCode'];
  612. }
  613. $xmlParams .= <<<XMLRequest
  614. <Address>
  615. <City>{$shipperCity}</City>
  616. <PostalCode>{$shipperPostalCode}</PostalCode>
  617. <CountryCode>{$shipperCountryCode}</CountryCode>
  618. <StateProvinceCode>{$shipperStateProvince}</StateProvinceCode>
  619. </Address>
  620. </Shipper>
  621. <ShipTo>
  622. <Address>
  623. <PostalCode>{$params['19_destPostal']}</PostalCode>
  624. <CountryCode>{$params['22_destCountry']}</CountryCode>
  625. <ResidentialAddress>{$params['49_residential']}</ResidentialAddress>
  626. <StateProvinceCode>{$params['destRegionCode']}</StateProvinceCode>
  627. XMLRequest;
  628. if ($params['49_residential'] === '01') {
  629. $xmlParams .= "<ResidentialAddressIndicator>{$params['49_residential']}</ResidentialAddressIndicator>";
  630. }
  631. $xmlParams .= <<<XMLRequest
  632. </Address>
  633. </ShipTo>
  634. <ShipFrom>
  635. <Address>
  636. <PostalCode>{$params['15_origPostal']}</PostalCode>
  637. <CountryCode>{$params['14_origCountry']}</CountryCode>
  638. <StateProvinceCode>{$params['origRegionCode']}</StateProvinceCode>
  639. </Address>
  640. </ShipFrom>
  641. <Package>
  642. <PackagingType>
  643. <Code>{$params['48_container']}</Code>
  644. </PackagingType>
  645. <PackageWeight>
  646. <UnitOfMeasurement>
  647. <Code>{$rowRequest->getUnitMeasure()}</Code>
  648. </UnitOfMeasurement>
  649. <Weight>{$params['23_weight']}</Weight>
  650. </PackageWeight>
  651. </Package>
  652. XMLRequest;
  653. if ($this->getConfigFlag('negotiated_active')) {
  654. $xmlParams .= "<RateInformation><NegotiatedRatesIndicator/></RateInformation>";
  655. }
  656. if ($this->getConfigFlag('include_taxes')) {
  657. $xmlParams .= "<TaxInformationIndicator/>";
  658. }
  659. $xmlParams .= <<<XMLRequest
  660. </Shipment>
  661. </RatingServiceSelectionRequest>
  662. XMLRequest;
  663. $xmlRequest .= $xmlParams;
  664. $xmlResponse = $this->_getCachedQuotes($xmlRequest);
  665. if ($xmlResponse === null) {
  666. $debugData['request'] = $xmlParams;
  667. try {
  668. $client = $this->httpClientFactory->create();
  669. $client->post($url, $xmlRequest);
  670. $xmlResponse = $client->getBody();
  671. $debugData['result'] = $xmlResponse;
  672. $this->_setCachedQuotes($xmlRequest, $xmlResponse);
  673. } catch (\Throwable $e) {
  674. $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
  675. $xmlResponse = '';
  676. }
  677. $this->_debug($debugData);
  678. }
  679. return $this->_parseXmlResponse($xmlResponse);
  680. }
  681. /**
  682. * Get base currency rate
  683. *
  684. * @param string $code
  685. * @return float
  686. */
  687. protected function _getBaseCurrencyRate($code)
  688. {
  689. if (!$this->_baseCurrencyRate) {
  690. $this->_baseCurrencyRate = $this->_currencyFactory->create()->load(
  691. $code
  692. )->getAnyRate(
  693. $this->_request->getBaseCurrency()->getCode()
  694. );
  695. }
  696. return $this->_baseCurrencyRate;
  697. }
  698. /**
  699. * Map currency alias to currency code
  700. *
  701. * @param string $code
  702. * @return string
  703. */
  704. private function mapCurrencyCode($code)
  705. {
  706. $currencyMapping = [
  707. 'RMB' => 'CNY',
  708. 'CNH' => 'CNY'
  709. ];
  710. return isset($currencyMapping[$code]) ? $currencyMapping[$code] : $code;
  711. }
  712. /**
  713. * Prepare shipping rate result based on response
  714. *
  715. * @param mixed $xmlResponse
  716. * @return Result
  717. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  718. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  719. * @SuppressWarnings(PHPMD.ElseExpression)
  720. */
  721. protected function _parseXmlResponse($xmlResponse)
  722. {
  723. $costArr = [];
  724. $priceArr = [];
  725. if (strlen(trim($xmlResponse)) > 0) {
  726. $xml = new \Magento\Framework\Simplexml\Config();
  727. $xml->loadString($xmlResponse);
  728. $arr = $xml->getXpath("//RatingServiceSelectionResponse/Response/ResponseStatusCode/text()");
  729. $success = (int)$arr[0];
  730. if ($success === 1) {
  731. $arr = $xml->getXpath("//RatingServiceSelectionResponse/RatedShipment");
  732. $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
  733. // Negotiated rates
  734. $negotiatedArr = $xml->getXpath("//RatingServiceSelectionResponse/RatedShipment/NegotiatedRates");
  735. $negotiatedActive = $this->getConfigFlag('negotiated_active')
  736. && $this->getConfigData('shipper_number')
  737. && !empty($negotiatedArr);
  738. $allowedCurrencies = $this->_currencyFactory->create()->getConfigAllowCurrencies();
  739. foreach ($arr as $shipElement) {
  740. $code = (string)$shipElement->Service->Code;
  741. if (in_array($code, $allowedMethods)) {
  742. //The location of tax information is in a different place
  743. // depending on whether we are using negotiated rates or not
  744. if ($negotiatedActive) {
  745. $includeTaxesArr = $xml->getXpath(
  746. "//RatingServiceSelectionResponse/RatedShipment/NegotiatedRates"
  747. . "/NetSummaryCharges/TotalChargesWithTaxes"
  748. );
  749. $includeTaxesActive = $this->getConfigFlag('include_taxes') && !empty($includeTaxesArr);
  750. if ($includeTaxesActive) {
  751. $cost = $shipElement->NegotiatedRates
  752. ->NetSummaryCharges
  753. ->TotalChargesWithTaxes
  754. ->MonetaryValue;
  755. $responseCurrencyCode = $this->mapCurrencyCode(
  756. (string)$shipElement->NegotiatedRates
  757. ->NetSummaryCharges
  758. ->TotalChargesWithTaxes
  759. ->CurrencyCode
  760. );
  761. } else {
  762. $cost = $shipElement->NegotiatedRates->NetSummaryCharges->GrandTotal->MonetaryValue;
  763. $responseCurrencyCode = $this->mapCurrencyCode(
  764. (string)$shipElement->NegotiatedRates->NetSummaryCharges->GrandTotal->CurrencyCode
  765. );
  766. }
  767. } else {
  768. $includeTaxesArr = $xml->getXpath(
  769. "//RatingServiceSelectionResponse/RatedShipment/TotalChargesWithTaxes"
  770. );
  771. $includeTaxesActive = $this->getConfigFlag('include_taxes') && !empty($includeTaxesArr);
  772. if ($includeTaxesActive) {
  773. $cost = $shipElement->TotalChargesWithTaxes->MonetaryValue;
  774. $responseCurrencyCode = $this->mapCurrencyCode(
  775. (string)$shipElement->TotalChargesWithTaxes->CurrencyCode
  776. );
  777. } else {
  778. $cost = $shipElement->TotalCharges->MonetaryValue;
  779. $responseCurrencyCode = $this->mapCurrencyCode(
  780. (string)$shipElement->TotalCharges->CurrencyCode
  781. );
  782. }
  783. }
  784. //convert price with Origin country currency code to base currency code
  785. $successConversion = true;
  786. if ($responseCurrencyCode) {
  787. if (in_array($responseCurrencyCode, $allowedCurrencies)) {
  788. $cost = (double)$cost * $this->_getBaseCurrencyRate($responseCurrencyCode);
  789. } else {
  790. $errorTitle = __(
  791. 'We can\'t convert a rate from "%1-%2".',
  792. $responseCurrencyCode,
  793. $this->_request->getPackageCurrency()->getCode()
  794. );
  795. $error = $this->_rateErrorFactory->create();
  796. $error->setCarrier('ups');
  797. $error->setCarrierTitle($this->getConfigData('title'));
  798. $error->setErrorMessage($errorTitle);
  799. $successConversion = false;
  800. }
  801. }
  802. if ($successConversion) {
  803. $costArr[$code] = $cost;
  804. $priceArr[$code] = $this->getMethodPrice((float)$cost, $code);
  805. }
  806. }
  807. }
  808. } else {
  809. $arr = $xml->getXpath("//RatingServiceSelectionResponse/Response/Error/ErrorDescription/text()");
  810. $errorTitle = (string)$arr[0][0];
  811. $error = $this->_rateErrorFactory->create();
  812. $error->setCarrier('ups');
  813. $error->setCarrierTitle($this->getConfigData('title'));
  814. $error->setErrorMessage($this->getConfigData('specificerrmsg'));
  815. }
  816. }
  817. $result = $this->_rateFactory->create();
  818. if (empty($priceArr)) {
  819. $error = $this->_rateErrorFactory->create();
  820. $error->setCarrier('ups');
  821. $error->setCarrierTitle($this->getConfigData('title'));
  822. if ($this->getConfigData('specificerrmsg') !== '') {
  823. $errorTitle = $this->getConfigData('specificerrmsg');
  824. }
  825. if (!isset($errorTitle)) {
  826. $errorTitle = __('Cannot retrieve shipping rates');
  827. }
  828. $error->setErrorMessage($errorTitle);
  829. $result->append($error);
  830. } else {
  831. foreach ($priceArr as $method => $price) {
  832. $rate = $this->_rateMethodFactory->create();
  833. $rate->setCarrier('ups');
  834. $rate->setCarrierTitle($this->getConfigData('title'));
  835. $rate->setMethod($method);
  836. $methodArr = $this->getShipmentByCode($method);
  837. $rate->setMethodTitle($methodArr);
  838. $rate->setCost($costArr[$method]);
  839. $rate->setPrice($price);
  840. $result->append($rate);
  841. }
  842. }
  843. return $result;
  844. }
  845. /**
  846. * Get tracking
  847. *
  848. * @param string|string[] $trackings
  849. * @return Result
  850. */
  851. public function getTracking($trackings)
  852. {
  853. if (!is_array($trackings)) {
  854. $trackings = [$trackings];
  855. }
  856. if ($this->getConfigData('type') == 'UPS') {
  857. $this->_getCgiTracking($trackings);
  858. } elseif ($this->getConfigData('type') == 'UPS_XML') {
  859. $this->setXMLAccessRequest();
  860. $this->_getXmlTracking($trackings);
  861. }
  862. return $this->_result;
  863. }
  864. /**
  865. * Set xml access request
  866. *
  867. * @return void
  868. */
  869. protected function setXMLAccessRequest()
  870. {
  871. $userId = $this->getConfigData('username');
  872. $userIdPass = $this->getConfigData('password');
  873. $accessKey = $this->getConfigData('access_license_number');
  874. $this->_xmlAccessRequest = <<<XMLAuth
  875. <?xml version="1.0" ?>
  876. <AccessRequest xml:lang="en-US">
  877. <AccessLicenseNumber>$accessKey</AccessLicenseNumber>
  878. <UserId>$userId</UserId>
  879. <Password>$userIdPass</Password>
  880. </AccessRequest>
  881. XMLAuth;
  882. }
  883. /**
  884. * Get cgi tracking
  885. *
  886. * @param string[] $trackings
  887. * @return \Magento\Shipping\Model\Tracking\ResultFactory
  888. */
  889. protected function _getCgiTracking($trackings)
  890. {
  891. //ups no longer support tracking for data streaming version
  892. //so we can only reply the popup window to ups.
  893. $result = $this->_trackFactory->create();
  894. foreach ($trackings as $tracking) {
  895. $status = $this->_trackStatusFactory->create();
  896. $status->setCarrier('ups');
  897. $status->setCarrierTitle($this->getConfigData('title'));
  898. $status->setTracking($tracking);
  899. $status->setPopup(1);
  900. $status->setUrl(
  901. "http://wwwapps.ups.com/WebTracking/processInputRequest?HTMLVersion=5.0&error_carried=true" .
  902. "&tracknums_displayed=5&TypeOfInquiryNumber=T&loc=en_US&InquiryNumber1={$tracking}" .
  903. "&AgreeToTermsAndConditions=yes"
  904. );
  905. $result->append($status);
  906. }
  907. $this->_result = $result;
  908. return $result;
  909. }
  910. /**
  911. * Get xml tracking
  912. *
  913. * @param string[] $trackings
  914. * @return Result
  915. */
  916. protected function _getXmlTracking($trackings)
  917. {
  918. $url = $this->getConfigData('tracking_xml_url');
  919. foreach ($trackings as $tracking) {
  920. /**
  921. * RequestOption==>'1' to request all activities
  922. */
  923. $xmlRequest = <<<XMLAuth
  924. <?xml version="1.0" ?>
  925. <TrackRequest xml:lang="en-US">
  926. <Request>
  927. <RequestAction>Track</RequestAction>
  928. <RequestOption>1</RequestOption>
  929. </Request>
  930. <TrackingNumber>$tracking</TrackingNumber>
  931. <IncludeFreight>01</IncludeFreight>
  932. </TrackRequest>
  933. XMLAuth;
  934. $debugData['request'] = $this->filterDebugData($this->_xmlAccessRequest) . $xmlRequest;
  935. try {
  936. $client = $this->httpClientFactory->create();
  937. $client->post($url, $this->_xmlAccessRequest . $xmlRequest);
  938. $xmlResponse = $client->getBody();
  939. $debugData['result'] = $xmlResponse;
  940. } catch (\Throwable $e) {
  941. $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
  942. $xmlResponse = '';
  943. }
  944. $this->_debug($debugData);
  945. $this->_parseXmlTrackingResponse($tracking, $xmlResponse);
  946. }
  947. return $this->_result;
  948. }
  949. /**
  950. * Parse xml tracking response
  951. *
  952. * @param string $trackingValue
  953. * @param string $xmlResponse
  954. * @return null
  955. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  956. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  957. */
  958. protected function _parseXmlTrackingResponse($trackingValue, $xmlResponse)
  959. {
  960. $errorTitle = 'For some reason we can\'t retrieve tracking info right now.';
  961. $resultArr = [];
  962. $packageProgress = [];
  963. if ($xmlResponse) {
  964. $xml = new \Magento\Framework\Simplexml\Config();
  965. $xml->loadString($xmlResponse);
  966. $arr = $xml->getXpath("//TrackResponse/Response/ResponseStatusCode/text()");
  967. $success = (int)$arr[0][0];
  968. if ($success === 1) {
  969. $arr = $xml->getXpath("//TrackResponse/Shipment/Service/Description/text()");
  970. $resultArr['service'] = (string)$arr[0];
  971. $arr = $xml->getXpath("//TrackResponse/Shipment/PickupDate/text()");
  972. $resultArr['shippeddate'] = (string)$arr[0];
  973. $arr = $xml->getXpath("//TrackResponse/Shipment/Package/PackageWeight/Weight/text()");
  974. $weight = (string)$arr[0];
  975. $arr = $xml->getXpath("//TrackResponse/Shipment/Package/PackageWeight/UnitOfMeasurement/Code/text()");
  976. $unit = (string)$arr[0];
  977. $resultArr['weight'] = "{$weight} {$unit}";
  978. $activityTags = $xml->getXpath("//TrackResponse/Shipment/Package/Activity");
  979. if ($activityTags) {
  980. $index = 1;
  981. foreach ($activityTags as $activityTag) {
  982. $addressArr = [];
  983. if (isset($activityTag->ActivityLocation->Address->City)) {
  984. $addressArr[] = (string)$activityTag->ActivityLocation->Address->City;
  985. }
  986. if (isset($activityTag->ActivityLocation->Address->StateProvinceCode)) {
  987. $addressArr[] = (string)$activityTag->ActivityLocation->Address->StateProvinceCode;
  988. }
  989. if (isset($activityTag->ActivityLocation->Address->CountryCode)) {
  990. $addressArr[] = (string)$activityTag->ActivityLocation->Address->CountryCode;
  991. }
  992. $dateArr = [];
  993. $date = (string)$activityTag->Date;
  994. //YYYYMMDD
  995. $dateArr[] = substr($date, 0, 4);
  996. $dateArr[] = substr($date, 4, 2);
  997. $dateArr[] = substr($date, -2, 2);
  998. $timeArr = [];
  999. $time = (string)$activityTag->Time;
  1000. //HHMMSS
  1001. $timeArr[] = substr($time, 0, 2);
  1002. $timeArr[] = substr($time, 2, 2);
  1003. $timeArr[] = substr($time, -2, 2);
  1004. if ($index === 1) {
  1005. $resultArr['status'] = (string)$activityTag->Status->StatusType->Description;
  1006. $resultArr['deliverydate'] = implode('-', $dateArr);
  1007. //YYYY-MM-DD
  1008. $resultArr['deliverytime'] = implode(':', $timeArr);
  1009. //HH:MM:SS
  1010. $resultArr['deliverylocation'] = (string)$activityTag->ActivityLocation->Description;
  1011. $resultArr['signedby'] = (string)$activityTag->ActivityLocation->SignedForByName;
  1012. if ($addressArr) {
  1013. $resultArr['deliveryto'] = implode(', ', $addressArr);
  1014. }
  1015. } else {
  1016. $tempArr = [];
  1017. $tempArr['activity'] = (string)$activityTag->Status->StatusType->Description;
  1018. $tempArr['deliverydate'] = implode('-', $dateArr);
  1019. //YYYY-MM-DD
  1020. $tempArr['deliverytime'] = implode(':', $timeArr);
  1021. //HH:MM:SS
  1022. if ($addressArr) {
  1023. $tempArr['deliverylocation'] = implode(', ', $addressArr);
  1024. }
  1025. $packageProgress[] = $tempArr;
  1026. }
  1027. $index++;
  1028. }
  1029. $resultArr['progressdetail'] = $packageProgress;
  1030. }
  1031. } else {
  1032. $arr = $xml->getXpath("//TrackResponse/Response/Error/ErrorDescription/text()");
  1033. $errorTitle = (string)$arr[0][0];
  1034. }
  1035. }
  1036. if (!$this->_result) {
  1037. $this->_result = $this->_trackFactory->create();
  1038. }
  1039. if ($resultArr) {
  1040. $tracking = $this->_trackStatusFactory->create();
  1041. $tracking->setCarrier('ups');
  1042. $tracking->setCarrierTitle($this->getConfigData('title'));
  1043. $tracking->setTracking($trackingValue);
  1044. $tracking->addData($resultArr);
  1045. $this->_result->append($tracking);
  1046. } else {
  1047. $error = $this->_trackErrorFactory->create();
  1048. $error->setCarrier('ups');
  1049. $error->setCarrierTitle($this->getConfigData('title'));
  1050. $error->setTracking($trackingValue);
  1051. $error->setErrorMessage($errorTitle);
  1052. $this->_result->append($error);
  1053. }
  1054. return $this->_result;
  1055. }
  1056. /**
  1057. * Get tracking response
  1058. *
  1059. * @return string
  1060. */
  1061. public function getResponse()
  1062. {
  1063. $statuses = '';
  1064. if ($this->_result instanceof \Magento\Shipping\Model\Tracking\Result) {
  1065. $trackings = $this->_result->getAllTrackings();
  1066. if ($trackings) {
  1067. foreach ($trackings as $tracking) {
  1068. $data = $tracking->getAllData();
  1069. if ($data) {
  1070. if (isset($data['status'])) {
  1071. $statuses .= __($data['status']);
  1072. } else {
  1073. $statuses .= __($data['error_message']);
  1074. }
  1075. }
  1076. }
  1077. }
  1078. }
  1079. if (empty($statuses)) {
  1080. $statuses = __('Empty response');
  1081. }
  1082. return $statuses;
  1083. }
  1084. /**
  1085. * Get allowed shipping methods
  1086. *
  1087. * @return array
  1088. */
  1089. public function getAllowedMethods()
  1090. {
  1091. $allowed = explode(',', $this->getConfigData('allowed_methods'));
  1092. $arr = [];
  1093. $isByCode = $this->getConfigData('type') == 'UPS_XML';
  1094. foreach ($allowed as $code) {
  1095. $arr[$code] = $isByCode ? $this->getShipmentByCode($code) : $this->configHelper->getCode('method', $code);
  1096. }
  1097. return $arr;
  1098. }
  1099. /**
  1100. * Form XML for shipment request
  1101. *
  1102. * @param \Magento\Framework\DataObject $request
  1103. * @return string
  1104. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1105. * @SuppressWarnings(PHPMD.NPathComplexity)
  1106. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  1107. */
  1108. protected function _formShipmentRequest(\Magento\Framework\DataObject $request)
  1109. {
  1110. $packageParams = $request->getPackageParams();
  1111. $height = $packageParams->getHeight();
  1112. $width = $packageParams->getWidth();
  1113. $length = $packageParams->getLength();
  1114. $weightUnits = $packageParams->getWeightUnits() == \Zend_Measure_Weight::POUND ? 'LBS' : 'KGS';
  1115. $dimensionsUnits = $packageParams->getDimensionUnits() == \Zend_Measure_Length::INCH ? 'IN' : 'CM';
  1116. $itemsDesc = [];
  1117. $itemsShipment = $request->getPackageItems();
  1118. foreach ($itemsShipment as $itemShipment) {
  1119. $item = new \Magento\Framework\DataObject();
  1120. $item->setData($itemShipment);
  1121. $itemsDesc[] = $item->getName();
  1122. }
  1123. $xmlRequest = $this->_xmlElFactory->create(
  1124. ['data' => '<?xml version = "1.0" ?><ShipmentConfirmRequest xml:lang="en-US"/>']
  1125. );
  1126. $requestPart = $xmlRequest->addChild('Request');
  1127. $requestPart->addChild('RequestAction', 'ShipConfirm');
  1128. $requestPart->addChild('RequestOption', 'nonvalidate');
  1129. $shipmentPart = $xmlRequest->addChild('Shipment');
  1130. if ($request->getIsReturn()) {
  1131. $returnPart = $shipmentPart->addChild('ReturnService');
  1132. // UPS Print Return Label
  1133. $returnPart->addChild('Code', '9');
  1134. }
  1135. $shipmentPart->addChild('Description', substr(implode(' ', $itemsDesc), 0, 35));
  1136. //empirical
  1137. $shipperPart = $shipmentPart->addChild('Shipper');
  1138. if ($request->getIsReturn()) {
  1139. $shipperPart->addChild('Name', $request->getRecipientContactCompanyName());
  1140. $shipperPart->addChild('AttentionName', $request->getRecipientContactPersonName());
  1141. $shipperPart->addChild('ShipperNumber', $this->getConfigData('shipper_number'));
  1142. $shipperPart->addChild('PhoneNumber', $request->getRecipientContactPhoneNumber());
  1143. $addressPart = $shipperPart->addChild('Address');
  1144. $addressPart->addChild('AddressLine1', $request->getRecipientAddressStreet());
  1145. $addressPart->addChild('AddressLine2', $request->getRecipientAddressStreet2());
  1146. $addressPart->addChild('City', $request->getRecipientAddressCity());
  1147. $addressPart->addChild('CountryCode', $request->getRecipientAddressCountryCode());
  1148. $addressPart->addChild('PostalCode', $request->getRecipientAddressPostalCode());
  1149. if ($request->getRecipientAddressStateOrProvinceCode()) {
  1150. $addressPart->addChild('StateProvinceCode', $request->getRecipientAddressStateOrProvinceCode());
  1151. }
  1152. } else {
  1153. $shipperPart->addChild('Name', $request->getShipperContactCompanyName());
  1154. $shipperPart->addChild('AttentionName', $request->getShipperContactPersonName());
  1155. $shipperPart->addChild('ShipperNumber', $this->getConfigData('shipper_number'));
  1156. $shipperPart->addChild('PhoneNumber', $request->getShipperContactPhoneNumber());
  1157. $addressPart = $shipperPart->addChild('Address');
  1158. $addressPart->addChild('AddressLine1', $request->getShipperAddressStreet());
  1159. $addressPart->addChild('AddressLine2', $request->getShipperAddressStreet2());
  1160. $addressPart->addChild('City', $request->getShipperAddressCity());
  1161. $addressPart->addChild('CountryCode', $request->getShipperAddressCountryCode());
  1162. $addressPart->addChild('PostalCode', $request->getShipperAddressPostalCode());
  1163. if ($request->getShipperAddressStateOrProvinceCode()) {
  1164. $addressPart->addChild('StateProvinceCode', $request->getShipperAddressStateOrProvinceCode());
  1165. }
  1166. }
  1167. $shipToPart = $shipmentPart->addChild('ShipTo');
  1168. $shipToPart->addChild('AttentionName', $request->getRecipientContactPersonName());
  1169. $shipToPart->addChild(
  1170. 'CompanyName',
  1171. $request->getRecipientContactCompanyName() ? $request->getRecipientContactCompanyName() : 'N/A'
  1172. );
  1173. $shipToPart->addChild('PhoneNumber', $request->getRecipientContactPhoneNumber());
  1174. $addressPart = $shipToPart->addChild('Address');
  1175. $addressPart->addChild('AddressLine1', $request->getRecipientAddressStreet1());
  1176. $addressPart->addChild('AddressLine2', $request->getRecipientAddressStreet2());
  1177. $addressPart->addChild('City', $request->getRecipientAddressCity());
  1178. $addressPart->addChild('CountryCode', $request->getRecipientAddressCountryCode());
  1179. $addressPart->addChild('PostalCode', $request->getRecipientAddressPostalCode());
  1180. if ($request->getRecipientAddressStateOrProvinceCode()) {
  1181. $addressPart->addChild('StateProvinceCode', $request->getRecipientAddressRegionCode());
  1182. }
  1183. if ($this->getConfigData('dest_type') == 'RES') {
  1184. $addressPart->addChild('ResidentialAddress');
  1185. }
  1186. if ($request->getIsReturn()) {
  1187. $shipFromPart = $shipmentPart->addChild('ShipFrom');
  1188. $shipFromPart->addChild('AttentionName', $request->getShipperContactPersonName());
  1189. $shipFromPart->addChild(
  1190. 'CompanyName',
  1191. $request->getShipperContactCompanyName() ? $request
  1192. ->getShipperContactCompanyName() : $request
  1193. ->getShipperContactPersonName()
  1194. );
  1195. $shipFromAddress = $shipFromPart->addChild('Address');
  1196. $shipFromAddress->addChild('AddressLine1', $request->getShipperAddressStreet1());
  1197. $shipFromAddress->addChild('AddressLine2', $request->getShipperAddressStreet2());
  1198. $shipFromAddress->addChild('City', $request->getShipperAddressCity());
  1199. $shipFromAddress->addChild('CountryCode', $request->getShipperAddressCountryCode());
  1200. $shipFromAddress->addChild('PostalCode', $request->getShipperAddressPostalCode());
  1201. if ($request->getShipperAddressStateOrProvinceCode()) {
  1202. $shipFromAddress->addChild('StateProvinceCode', $request->getShipperAddressStateOrProvinceCode());
  1203. }
  1204. $addressPart = $shipToPart->addChild('Address');
  1205. $addressPart->addChild('AddressLine1', $request->getShipperAddressStreet1());
  1206. $addressPart->addChild('AddressLine2', $request->getShipperAddressStreet2());
  1207. $addressPart->addChild('City', $request->getShipperAddressCity());
  1208. $addressPart->addChild('CountryCode', $request->getShipperAddressCountryCode());
  1209. $addressPart->addChild('PostalCode', $request->getShipperAddressPostalCode());
  1210. if ($request->getShipperAddressStateOrProvinceCode()) {
  1211. $addressPart->addChild('StateProvinceCode', $request->getShipperAddressStateOrProvinceCode());
  1212. }
  1213. if ($this->getConfigData('dest_type') == 'RES') {
  1214. $addressPart->addChild('ResidentialAddress');
  1215. }
  1216. }
  1217. $servicePart = $shipmentPart->addChild('Service');
  1218. $servicePart->addChild('Code', $request->getShippingMethod());
  1219. $packagePart = $shipmentPart->addChild('Package');
  1220. $packagePart->addChild('Description', substr(implode(' ', $itemsDesc), 0, 35));
  1221. //empirical
  1222. $packagePart->addChild('PackagingType')->addChild('Code', $request->getPackagingType());
  1223. $packageWeight = $packagePart->addChild('PackageWeight');
  1224. $packageWeight->addChild('Weight', $request->getPackageWeight());
  1225. $packageWeight->addChild('UnitOfMeasurement')->addChild('Code', $weightUnits);
  1226. // set dimensions
  1227. if ($length || $width || $height) {
  1228. $packageDimensions = $packagePart->addChild('Dimensions');
  1229. $packageDimensions->addChild('UnitOfMeasurement')->addChild('Code', $dimensionsUnits);
  1230. $packageDimensions->addChild('Length', $length);
  1231. $packageDimensions->addChild('Width', $width);
  1232. $packageDimensions->addChild('Height', $height);
  1233. }
  1234. // ups support reference number only for domestic service
  1235. if ($this->_isUSCountry($request->getRecipientAddressCountryCode())
  1236. && $this->_isUSCountry($request->getShipperAddressCountryCode())
  1237. ) {
  1238. if ($request->getReferenceData()) {
  1239. $referenceData = $request->getReferenceData() . $request->getPackageId();
  1240. } else {
  1241. $referenceData = 'Order #' .
  1242. $request->getOrderShipment()->getOrder()->getIncrementId() .
  1243. ' P' .
  1244. $request->getPackageId();
  1245. }
  1246. $referencePart = $packagePart->addChild('ReferenceNumber');
  1247. $referencePart->addChild('Code', '02');
  1248. $referencePart->addChild('Value', $referenceData);
  1249. }
  1250. $deliveryConfirmation = $packageParams->getDeliveryConfirmation();
  1251. if ($deliveryConfirmation) {
  1252. /** @var $serviceOptionsNode Element */
  1253. $serviceOptionsNode = null;
  1254. switch ($this->_getDeliveryConfirmationLevel($request->getRecipientAddressCountryCode())) {
  1255. case self::DELIVERY_CONFIRMATION_PACKAGE:
  1256. $serviceOptionsNode = $packagePart->addChild('PackageServiceOptions');
  1257. break;
  1258. case self::DELIVERY_CONFIRMATION_SHIPMENT:
  1259. $serviceOptionsNode = $shipmentPart->addChild('ShipmentServiceOptions');
  1260. break;
  1261. default:
  1262. break;
  1263. }
  1264. if ($serviceOptionsNode !== null) {
  1265. $serviceOptionsNode->addChild(
  1266. 'DeliveryConfirmation'
  1267. )->addChild(
  1268. 'DCISType',
  1269. $packageParams->getDeliveryConfirmation()
  1270. );
  1271. }
  1272. }
  1273. $shipmentPart->addChild('PaymentInformation')
  1274. ->addChild('Prepaid')
  1275. ->addChild('BillShipper')
  1276. ->addChild('AccountNumber', $this->getConfigData('shipper_number'));
  1277. if ($request->getPackagingType() != $this->configHelper->getCode('container', 'ULE')
  1278. && $request->getShipperAddressCountryCode() == self::USA_COUNTRY_ID
  1279. && ($request->getRecipientAddressCountryCode() == 'CA'
  1280. || $request->getRecipientAddressCountryCode() == 'PR')
  1281. ) {
  1282. $invoiceLineTotalPart = $shipmentPart->addChild('InvoiceLineTotal');
  1283. $invoiceLineTotalPart->addChild('CurrencyCode', $request->getBaseCurrencyCode());
  1284. $invoiceLineTotalPart->addChild('MonetaryValue', ceil($packageParams->getCustomsValue()));
  1285. }
  1286. $labelPart = $xmlRequest->addChild('LabelSpecification');
  1287. $labelPart->addChild('LabelPrintMethod')->addChild('Code', 'GIF');
  1288. $labelPart->addChild('LabelImageFormat')->addChild('Code', 'GIF');
  1289. return $xmlRequest->asXml();
  1290. }
  1291. /**
  1292. * Send and process shipment accept request
  1293. *
  1294. * @param Element $shipmentConfirmResponse
  1295. * @return \Magento\Framework\DataObject
  1296. */
  1297. protected function _sendShipmentAcceptRequest(Element $shipmentConfirmResponse)
  1298. {
  1299. $xmlRequest = $this->_xmlElFactory->create(
  1300. ['data' => '<?xml version = "1.0" ?><ShipmentAcceptRequest/>']
  1301. );
  1302. $request = $xmlRequest->addChild('Request');
  1303. $request->addChild('RequestAction', 'ShipAccept');
  1304. $xmlRequest->addChild('ShipmentDigest', $shipmentConfirmResponse->ShipmentDigest);
  1305. $debugData = ['request' => $this->filterDebugData($this->_xmlAccessRequest) . $xmlRequest->asXML()];
  1306. try {
  1307. $client = $this->httpClientFactory->create();
  1308. $client->post($this->getShipAcceptUrl(), $this->_xmlAccessRequest . $xmlRequest->asXML());
  1309. $xmlResponse = $client->getBody();
  1310. $debugData['result'] = $xmlResponse;
  1311. $this->_setCachedQuotes($xmlRequest, $xmlResponse);
  1312. } catch (\Throwable $e) {
  1313. $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
  1314. $xmlResponse = '';
  1315. }
  1316. try {
  1317. $response = $this->_xmlElFactory->create(['data' => $xmlResponse]);
  1318. } catch (\Throwable $e) {
  1319. $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
  1320. }
  1321. $result = new \Magento\Framework\DataObject();
  1322. if (isset($response->Error)) {
  1323. $result->setErrors((string)$response->Error->ErrorDescription);
  1324. } else {
  1325. $shippingLabelContent = (string)$response->ShipmentResults->PackageResults->LabelImage->GraphicImage;
  1326. $trackingNumber = (string)$response->ShipmentResults->PackageResults->TrackingNumber;
  1327. $result->setShippingLabelContent(base64_decode($shippingLabelContent));
  1328. $result->setTrackingNumber($trackingNumber);
  1329. }
  1330. $this->_debug($debugData);
  1331. return $result;
  1332. }
  1333. /**
  1334. * Get ship accept url
  1335. *
  1336. * @return string
  1337. */
  1338. public function getShipAcceptUrl()
  1339. {
  1340. if ($this->getConfigData('is_account_live')) {
  1341. $url = $this->_liveUrls['ShipAccept'];
  1342. } else {
  1343. $url = $this->_defaultUrls['ShipAccept'];
  1344. }
  1345. return $url;
  1346. }
  1347. /**
  1348. * Do shipment request to carrier web service, obtain Print Shipping Labels and process errors in response
  1349. *
  1350. * @param \Magento\Framework\DataObject $request
  1351. * @return \Magento\Framework\DataObject
  1352. */
  1353. protected function _doShipmentRequest(\Magento\Framework\DataObject $request)
  1354. {
  1355. $this->_prepareShipmentRequest($request);
  1356. $result = new \Magento\Framework\DataObject();
  1357. $rawXmlRequest = $this->_formShipmentRequest($request);
  1358. $this->setXMLAccessRequest();
  1359. $xmlRequest = $this->_xmlAccessRequest . $rawXmlRequest;
  1360. $xmlResponse = $this->_getCachedQuotes($xmlRequest);
  1361. if ($xmlResponse === null) {
  1362. $debugData['request'] = $this->filterDebugData($this->_xmlAccessRequest) . $rawXmlRequest;
  1363. $url = $this->getShipConfirmUrl();
  1364. $client = $this->httpClientFactory->create();
  1365. try {
  1366. $client->post($url, $xmlRequest);
  1367. $xmlResponse = $client->getBody();
  1368. $debugData['result'] = $xmlResponse;
  1369. $this->_setCachedQuotes($xmlRequest, $xmlResponse);
  1370. } catch (\Throwable $e) {
  1371. $debugData['result'] = ['code' => $e->getCode(), 'error' => $e->getMessage()];
  1372. }
  1373. }
  1374. try {
  1375. $response = $this->_xmlElFactory->create(['data' => $xmlResponse]);
  1376. } catch (\Throwable $e) {
  1377. $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
  1378. $result->setErrors($e->getMessage());
  1379. }
  1380. if (isset($response->Response->Error)
  1381. && in_array($response->Response->Error->ErrorSeverity, ['Hard', 'Transient'])
  1382. ) {
  1383. $result->setErrors((string)$response->Response->Error->ErrorDescription);
  1384. }
  1385. $this->_debug($debugData);
  1386. if ($result->hasErrors() || empty($response)) {
  1387. return $result;
  1388. } else {
  1389. return $this->_sendShipmentAcceptRequest($response);
  1390. }
  1391. }
  1392. /**
  1393. * Get ship confirm url
  1394. *
  1395. * @return string
  1396. */
  1397. public function getShipConfirmUrl()
  1398. {
  1399. $url = $this->getConfigData('url');
  1400. if (!$url) {
  1401. if ($this->getConfigData('is_account_live')) {
  1402. $url = $this->_liveUrls['ShipConfirm'];
  1403. return $url;
  1404. } else {
  1405. $url = $this->_defaultUrls['ShipConfirm'];
  1406. return $url;
  1407. }
  1408. }
  1409. return $url;
  1410. }
  1411. /**
  1412. * Return container types of carrier
  1413. *
  1414. * @param \Magento\Framework\DataObject|null $params
  1415. * @return array|bool
  1416. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  1417. */
  1418. public function getContainerTypes(\Magento\Framework\DataObject $params = null)
  1419. {
  1420. if ($params === null) {
  1421. return $this->_getAllowedContainers($params);
  1422. }
  1423. $method = $params->getMethod();
  1424. $countryShipper = $params->getCountryShipper();
  1425. $countryRecipient = $params->getCountryRecipient();
  1426. if ($countryShipper == self::USA_COUNTRY_ID && $countryRecipient == self::CANADA_COUNTRY_ID ||
  1427. $countryShipper == self::CANADA_COUNTRY_ID && $countryRecipient == self::USA_COUNTRY_ID ||
  1428. $countryShipper == self::MEXICO_COUNTRY_ID && $countryRecipient == self::USA_COUNTRY_ID && $method == '11'
  1429. ) {
  1430. $containerTypes = [];
  1431. if ($method == '07' || $method == '08' || $method == '65') {
  1432. // Worldwide Expedited
  1433. if ($method != '08') {
  1434. $containerTypes = [
  1435. '01' => __('UPS Letter Envelope'),
  1436. '24' => __('UPS Worldwide 25 kilo'),
  1437. '25' => __('UPS Worldwide 10 kilo'),
  1438. ];
  1439. }
  1440. $containerTypes = $containerTypes + [
  1441. '03' => __('UPS Tube'),
  1442. '04' => __('PAK'),
  1443. '2a' => __('Small Express Box'),
  1444. '2b' => __('Medium Express Box'),
  1445. '2c' => __('Large Express Box'),
  1446. ];
  1447. }
  1448. return ['00' => __('Customer Packaging')] + $containerTypes;
  1449. } elseif ($countryShipper == self::USA_COUNTRY_ID &&
  1450. $countryRecipient == self::PUERTORICO_COUNTRY_ID &&
  1451. ($method == '03' ||
  1452. $method == '02' ||
  1453. $method == '01')
  1454. ) {
  1455. // Container types should be the same as for domestic
  1456. $params->setCountryRecipient(self::USA_COUNTRY_ID);
  1457. $containerTypes = $this->_getAllowedContainers($params);
  1458. $params->setCountryRecipient($countryRecipient);
  1459. return $containerTypes;
  1460. }
  1461. return $this->_getAllowedContainers($params);
  1462. }
  1463. /**
  1464. * Return all container types of carrier
  1465. *
  1466. * @return array|bool
  1467. */
  1468. public function getContainerTypesAll()
  1469. {
  1470. $codes = $this->configHelper->getCode('container');
  1471. $descriptions = $this->configHelper->getCode('container_description');
  1472. $result = [];
  1473. foreach ($codes as $key => &$code) {
  1474. $result[$code] = $descriptions[$key];
  1475. }
  1476. return $result;
  1477. }
  1478. /**
  1479. * Return structured data of containers witch related with shipping methods
  1480. *
  1481. * @return array|bool
  1482. */
  1483. public function getContainerTypesFilter()
  1484. {
  1485. return $this->configHelper->getCode('containers_filter');
  1486. }
  1487. /**
  1488. * Return delivery confirmation types of carrier
  1489. *
  1490. * @param \Magento\Framework\DataObject|null $params
  1491. * @return array|bool
  1492. */
  1493. public function getDeliveryConfirmationTypes(\Magento\Framework\DataObject $params = null)
  1494. {
  1495. $countryRecipient = $params != null ? $params->getCountryRecipient() : null;
  1496. $deliveryConfirmationTypes = [];
  1497. switch ($this->_getDeliveryConfirmationLevel($countryRecipient)) {
  1498. case self::DELIVERY_CONFIRMATION_PACKAGE:
  1499. $deliveryConfirmationTypes = [
  1500. 1 => __('Delivery Confirmation'),
  1501. 2 => __('Signature Required'),
  1502. 3 => __('Adult Signature Required'),
  1503. ];
  1504. break;
  1505. case self::DELIVERY_CONFIRMATION_SHIPMENT:
  1506. $deliveryConfirmationTypes = [1 => __('Signature Required'), 2 => __('Adult Signature Required')];
  1507. break;
  1508. default:
  1509. break;
  1510. }
  1511. array_unshift($deliveryConfirmationTypes, __('Not Required'));
  1512. return $deliveryConfirmationTypes;
  1513. }
  1514. /**
  1515. * Get Container Types, that could be customized for UPS carrier
  1516. *
  1517. * @return array
  1518. */
  1519. public function getCustomizableContainerTypes()
  1520. {
  1521. $result = [];
  1522. $containerTypes = $this->configHelper->getCode('container');
  1523. foreach (parent::getCustomizableContainerTypes() as $containerType) {
  1524. $result[$containerType] = $containerTypes[$containerType];
  1525. }
  1526. return $result;
  1527. }
  1528. /**
  1529. * Get delivery confirmation level based on origin/destination
  1530. *
  1531. * Return null if delivery confirmation is not acceptable
  1532. *
  1533. * @param string|null $countyDestination
  1534. * @return int|null
  1535. */
  1536. protected function _getDeliveryConfirmationLevel($countyDestination = null)
  1537. {
  1538. if ($countyDestination === null) {
  1539. return null;
  1540. }
  1541. if ($countyDestination == self::USA_COUNTRY_ID) {
  1542. return self::DELIVERY_CONFIRMATION_PACKAGE;
  1543. }
  1544. return self::DELIVERY_CONFIRMATION_SHIPMENT;
  1545. }
  1546. }