Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
Carrier.php
Go to the documentation of this file.
1 <?php
7 namespace Magento\Fedex\Model;
8 
18 
26 {
32  const CODE = 'fedex';
33 
39  const RATE_REQUEST_GENERAL = 'general';
40 
46  const RATE_REQUEST_SMARTPOST = 'SMART_POST';
47 
53  protected $_code = self::CODE;
54 
60  protected $_ratesOrder = [
61  'RATED_ACCOUNT_PACKAGE',
62  'PAYOR_ACCOUNT_PACKAGE',
63  'RATED_ACCOUNT_SHIPMENT',
64  'PAYOR_ACCOUNT_SHIPMENT',
65  'RATED_LIST_PACKAGE',
66  'PAYOR_LIST_PACKAGE',
67  'RATED_LIST_SHIPMENT',
68  'PAYOR_LIST_SHIPMENT',
69  ];
70 
76  protected $_request = null;
77 
83  protected $_result = null;
84 
90  protected $_rateServiceWsdl;
91 
97  protected $_shipServiceWsdl = null;
98 
104  protected $_trackServiceWsdl = null;
105 
111  protected $_customizableContainerTypes = ['YOUR_PACKAGING'];
112 
116  protected $_storeManager;
117 
122 
127  'Key', 'Password', 'MeterNumber',
128  ];
129 
134  private static $trackServiceVersion = 10;
135 
140  private static $trackingErrors = ['FAILURE', 'ERROR'];
141 
145  private $serializer;
146 
150  private $soapClientFactory;
151 
176  public function __construct(
177  \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
178  \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory,
179  \Psr\Log\LoggerInterface $logger,
181  \Magento\Shipping\Model\Simplexml\ElementFactory $xmlElFactory,
182  \Magento\Shipping\Model\Rate\ResultFactory $rateFactory,
183  \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory,
184  \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory,
185  \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory,
186  \Magento\Shipping\Model\Tracking\Result\StatusFactory $trackStatusFactory,
187  \Magento\Directory\Model\RegionFactory $regionFactory,
188  \Magento\Directory\Model\CountryFactory $countryFactory,
189  \Magento\Directory\Model\CurrencyFactory $currencyFactory,
190  \Magento\Directory\Helper\Data $directoryData,
191  \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
192  \Magento\Store\Model\StoreManagerInterface $storeManager,
193  \Magento\Framework\Module\Dir\Reader $configReader,
194  \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory,
195  array $data = [],
196  Json $serializer = null,
197  ClientFactory $soapClientFactory = null
198  ) {
199  $this->_storeManager = $storeManager;
200  $this->_productCollectionFactory = $productCollectionFactory;
201  parent::__construct(
202  $scopeConfig,
203  $rateErrorFactory,
204  $logger,
205  $xmlSecurity,
206  $xmlElFactory,
207  $rateFactory,
208  $rateMethodFactory,
209  $trackFactory,
210  $trackErrorFactory,
211  $trackStatusFactory,
212  $regionFactory,
213  $countryFactory,
214  $currencyFactory,
215  $directoryData,
217  $data
218  );
219  $wsdlBasePath = $configReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_Fedex') . '/wsdl/';
220  $this->_shipServiceWsdl = $wsdlBasePath . 'ShipService_v10.wsdl';
221  $this->_rateServiceWsdl = $wsdlBasePath . 'RateService_v10.wsdl';
222  $this->_trackServiceWsdl = $wsdlBasePath . 'TrackService_v' . self::$trackServiceVersion . '.wsdl';
223  $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
224  $this->soapClientFactory = $soapClientFactory ?: ObjectManager::getInstance()->get(ClientFactory::class);
225  }
226 
234  protected function _createSoapClient($wsdl, $trace = false)
235  {
236  $client = $this->soapClientFactory->create($wsdl, ['trace' => $trace]);
237  $client->__setLocation(
238  $this->getConfigFlag(
239  'sandbox_mode'
240  ) ? $this->getConfigData('sandbox_webservices_url') : $this->getConfigData('production_webservices_url')
241  );
242 
243  return $client;
244  }
245 
251  protected function _createRateSoapClient()
252  {
253  return $this->_createSoapClient($this->_rateServiceWsdl);
254  }
255 
261  protected function _createShipSoapClient()
262  {
263  return $this->_createSoapClient($this->_shipServiceWsdl, 1);
264  }
265 
271  protected function _createTrackSoapClient()
272  {
273  return $this->_createSoapClient($this->_trackServiceWsdl, 1);
274  }
275 
283  {
284  if (!$this->canCollectRates()) {
285  return $this->getErrorMessage();
286  }
287 
288  $this->setRequest($request);
289  $this->_getQuotes();
290  $this->_updateFreeMethodQuote($request);
291 
292  return $this->getResult();
293  }
294 
303  public function setRequest(RateRequest $request)
304  {
305  $this->_request = $request;
306 
307  $r = new \Magento\Framework\DataObject();
308 
309  if ($request->getLimitMethod()) {
310  $r->setService($request->getLimitMethod());
311  }
312 
313  if ($request->getFedexAccount()) {
314  $account = $request->getFedexAccount();
315  } else {
316  $account = $this->getConfigData('account');
317  }
318  $r->setAccount($account);
319 
320  if ($request->getFedexDropoff()) {
321  $dropoff = $request->getFedexDropoff();
322  } else {
323  $dropoff = $this->getConfigData('dropoff');
324  }
325  $r->setDropoffType($dropoff);
326 
327  if ($request->getFedexPackaging()) {
328  $packaging = $request->getFedexPackaging();
329  } else {
330  $packaging = $this->getConfigData('packaging');
331  }
332  $r->setPackaging($packaging);
333 
334  if ($request->getOrigCountry()) {
335  $origCountry = $request->getOrigCountry();
336  } else {
337  $origCountry = $this->_scopeConfig->getValue(
338  \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_COUNTRY_ID,
339  \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
340  $request->getStoreId()
341  );
342  }
343  $r->setOrigCountry($this->_countryFactory->create()->load($origCountry)->getData('iso2_code'));
344 
345  if ($request->getOrigPostcode()) {
346  $r->setOrigPostal($request->getOrigPostcode());
347  } else {
348  $r->setOrigPostal(
349  $this->_scopeConfig->getValue(
350  \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_ZIP,
351  \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
352  $request->getStoreId()
353  )
354  );
355  }
356 
357  if ($request->getDestCountryId()) {
358  $destCountry = $request->getDestCountryId();
359  } else {
360  $destCountry = self::USA_COUNTRY_ID;
361  }
362  $r->setDestCountry($this->_countryFactory->create()->load($destCountry)->getData('iso2_code'));
363 
364  if ($request->getDestPostcode()) {
365  $r->setDestPostal($request->getDestPostcode());
366  } else {
367  }
368 
369  if ($request->getDestCity()) {
370  $r->setDestCity($request->getDestCity());
371  }
372 
373  $weight = $this->getTotalNumOfBoxes($request->getPackageWeight());
374  $r->setWeight($weight);
375  if ($request->getFreeMethodWeight() != $request->getPackageWeight()) {
376  $r->setFreeMethodWeight($request->getFreeMethodWeight());
377  }
378 
379  $r->setValue($request->getPackagePhysicalValue());
380  $r->setValueWithDiscount($request->getPackageValueWithDiscount());
381 
382  $r->setMeterNumber($this->getConfigData('meter_number'));
383  $r->setKey($this->getConfigData('key'));
384  $r->setPassword($this->getConfigData('password'));
385 
386  $r->setIsReturn($request->getIsReturn());
387 
388  $r->setBaseSubtotalInclTax($request->getBaseSubtotalInclTax());
389 
390  $this->setRawRequest($r);
391 
392  return $this;
393  }
394 
400  public function getResult()
401  {
402  if (!$this->_result) {
403  $this->_result = $this->_trackFactory->create();
404  }
405  return $this->_result;
406  }
407 
413  public function getVersionInfo()
414  {
415  return ['ServiceId' => 'crs', 'Major' => '10', 'Intermediate' => '0', 'Minor' => '0'];
416  }
417 
424  protected function _formRateRequest($purpose)
425  {
426  $r = $this->_rawRequest;
427  $ratesRequest = [
428  'WebAuthenticationDetail' => [
429  'UserCredential' => ['Key' => $r->getKey(), 'Password' => $r->getPassword()],
430  ],
431  'ClientDetail' => ['AccountNumber' => $r->getAccount(), 'MeterNumber' => $r->getMeterNumber()],
432  'Version' => $this->getVersionInfo(),
433  'RequestedShipment' => [
434  'DropoffType' => $r->getDropoffType(),
435  'ShipTimestamp' => date('c'),
436  'PackagingType' => $r->getPackaging(),
437  'TotalInsuredValue' => ['Amount' => $r->getValue(), 'Currency' => $this->getCurrencyCode()],
438  'Shipper' => [
439  'Address' => ['PostalCode' => $r->getOrigPostal(), 'CountryCode' => $r->getOrigCountry()],
440  ],
441  'Recipient' => [
442  'Address' => [
443  'PostalCode' => $r->getDestPostal(),
444  'CountryCode' => $r->getDestCountry(),
445  'Residential' => (bool)$this->getConfigData('residence_delivery'),
446  ],
447  ],
448  'ShippingChargesPayment' => [
449  'PaymentType' => 'SENDER',
450  'Payor' => ['AccountNumber' => $r->getAccount(), 'CountryCode' => $r->getOrigCountry()],
451  ],
452  'CustomsClearanceDetail' => [
453  'CustomsValue' => ['Amount' => $r->getValue(), 'Currency' => $this->getCurrencyCode()],
454  ],
455  'RateRequestTypes' => 'LIST',
456  'PackageCount' => '1',
457  'PackageDetail' => 'INDIVIDUAL_PACKAGES',
458  'RequestedPackageLineItems' => [
459  '0' => [
460  'Weight' => [
461  'Value' => (double)$r->getWeight(),
462  'Units' => $this->getConfigData('unit_of_measure'),
463  ],
464  'GroupPackageCount' => 1,
465  ],
466  ],
467  ],
468  ];
469 
470  if ($r->getDestCity()) {
471  $ratesRequest['RequestedShipment']['Recipient']['Address']['City'] = $r->getDestCity();
472  }
473 
474  if ($purpose == self::RATE_REQUEST_GENERAL) {
475  $ratesRequest['RequestedShipment']['RequestedPackageLineItems'][0]['InsuredValue'] = [
476  'Amount' => $r->getValue(),
477  'Currency' => $this->getCurrencyCode(),
478  ];
479  } else {
480  if ($purpose == self::RATE_REQUEST_SMARTPOST) {
481  $ratesRequest['RequestedShipment']['ServiceType'] = self::RATE_REQUEST_SMARTPOST;
482  $ratesRequest['RequestedShipment']['SmartPostDetail'] = [
483  'Indicia' => (double)$r->getWeight() >= 1 ? 'PARCEL_SELECT' : 'PRESORTED_STANDARD',
484  'HubId' => $this->getConfigData('smartpost_hubid'),
485  ];
486  }
487  }
488 
489  return $ratesRequest;
490  }
491 
498  protected function _doRatesRequest($purpose)
499  {
500  $ratesRequest = $this->_formRateRequest($purpose);
501  $ratesRequestNoShipTimestamp = $ratesRequest;
502  unset($ratesRequestNoShipTimestamp['RequestedShipment']['ShipTimestamp']);
503  $requestString = $this->serializer->serialize($ratesRequestNoShipTimestamp);
504  $response = $this->_getCachedQuotes($requestString);
505  $debugData = ['request' => $this->filterDebugData($ratesRequest)];
506  if ($response === null) {
507  try {
508  $client = $this->_createRateSoapClient();
509  $response = $client->getRates($ratesRequest);
510  $this->_setCachedQuotes($requestString, $response);
511  $debugData['result'] = $response;
512  } catch (\Exception $e) {
513  $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
514  $this->_logger->critical($e);
515  }
516  } else {
517  $debugData['result'] = $response;
518  }
519  $this->_debug($debugData);
520 
521  return $response;
522  }
523 
529  protected function _getQuotes()
530  {
531  $this->_result = $this->_rateFactory->create();
532  // make separate request for Smart Post method
533  $allowedMethods = explode(',', $this->getConfigData('allowed_methods'));
534  if (in_array(self::RATE_REQUEST_SMARTPOST, $allowedMethods)) {
535  $response = $this->_doRatesRequest(self::RATE_REQUEST_SMARTPOST);
536  $preparedSmartpost = $this->_prepareRateResponse($response);
537  if (!$preparedSmartpost->getError()) {
538  $this->_result->append($preparedSmartpost);
539  }
540  }
541  // make general request for all methods
542  $response = $this->_doRatesRequest(self::RATE_REQUEST_GENERAL);
543  $preparedGeneral = $this->_prepareRateResponse($response);
544  if (!$preparedGeneral->getError()
545  || $this->_result->getError() && $preparedGeneral->getError()
546  || empty($this->_result->getAllRates())
547  ) {
548  $this->_result->append($preparedGeneral);
549  }
550 
551  return $this->_result;
552  }
553 
561  protected function _prepareRateResponse($response)
562  {
563  $costArr = [];
564  $priceArr = [];
565  $errorTitle = 'For some reason we can\'t retrieve tracking info right now.';
566 
567  if (is_object($response)) {
568  if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
569  if (is_array($response->Notifications)) {
570  $notification = array_pop($response->Notifications);
571  $errorTitle = (string)$notification->Message;
572  } else {
573  $errorTitle = (string)$response->Notifications->Message;
574  }
575  } elseif (isset($response->RateReplyDetails)) {
576  $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
577 
578  if (is_array($response->RateReplyDetails)) {
579  foreach ($response->RateReplyDetails as $rate) {
580  $serviceName = (string)$rate->ServiceType;
581  if (in_array($serviceName, $allowedMethods)) {
582  $amount = $this->_getRateAmountOriginBased($rate);
583  $costArr[$serviceName] = $amount;
584  $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
585  }
586  }
587  asort($priceArr);
588  } else {
589  $rate = $response->RateReplyDetails;
590  $serviceName = (string)$rate->ServiceType;
591  if (in_array($serviceName, $allowedMethods)) {
593  $costArr[$serviceName] = $amount;
594  $priceArr[$serviceName] = $this->getMethodPrice($amount, $serviceName);
595  }
596  }
597  }
598  }
599 
600  $result = $this->_rateFactory->create();
601  if (empty($priceArr)) {
602  $error = $this->_rateErrorFactory->create();
603  $error->setCarrier($this->_code);
604  $error->setCarrierTitle($this->getConfigData('title'));
605  $error->setErrorMessage($errorTitle);
606  $error->setErrorMessage($this->getConfigData('specificerrmsg'));
607  $result->append($error);
608  } else {
609  foreach ($priceArr as $method => $price) {
610  $rate = $this->_rateMethodFactory->create();
611  $rate->setCarrier($this->_code);
612  $rate->setCarrierTitle($this->getConfigData('title'));
613  $rate->setMethod($method);
614  $rate->setMethodTitle($this->getCode('method', $method));
615  $rate->setCost($costArr[$method]);
616  $rate->setPrice($price);
617  $result->append($rate);
618  }
619  }
620 
621  return $result;
622  }
623 
630  protected function _getRateAmountOriginBased($rate)
631  {
632  $amount = null;
633  $rateTypeAmounts = [];
634 
635  if (is_object($rate)) {
636  // The "RATED..." rates are expressed in the currency of the origin country
637  foreach ($rate->RatedShipmentDetails as $ratedShipmentDetail) {
638  $netAmount = (string)$ratedShipmentDetail->ShipmentRateDetail->TotalNetCharge->Amount;
639  $rateType = (string)$ratedShipmentDetail->ShipmentRateDetail->RateType;
640  $rateTypeAmounts[$rateType] = $netAmount;
641  }
642 
643  foreach ($this->_ratesOrder as $rateType) {
644  if (!empty($rateTypeAmounts[$rateType])) {
645  $amount = $rateTypeAmounts[$rateType];
646  break;
647  }
648  }
649 
650  if ($amount === null) {
651  $amount = (string)$rate->RatedShipmentDetails[0]->ShipmentRateDetail->TotalNetCharge->Amount;
652  }
653  }
654 
655  return $amount;
656  }
657 
664  protected function _setFreeMethodRequest($freeMethod)
665  {
666  $r = $this->_rawRequest;
667  $weight = $this->getTotalNumOfBoxes($r->getFreeMethodWeight());
668  $r->setWeight($weight);
669  $r->setService($freeMethod);
670  }
671 
677  protected function _getXmlQuotes()
678  {
679  $r = $this->_rawRequest;
680  $xml = $this->_xmlElFactory->create(
681  ['data' => '<?xml version = "1.0" encoding = "UTF-8"?><FDXRateAvailableServicesRequest/>']
682  );
683 
684  $xml->addAttribute('xmlns:api', 'http://www.fedex.com/fsmapi');
685  $xml->addAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
686  $xml->addAttribute('xsi:noNamespaceSchemaLocation', 'FDXRateAvailableServicesRequest.xsd');
687 
688  $requestHeader = $xml->addChild('RequestHeader');
689  $requestHeader->addChild('AccountNumber', $r->getAccount());
690  $requestHeader->addChild('MeterNumber', '0');
691 
692  $xml->addChild('ShipDate', date('Y-m-d'));
693  $xml->addChild('DropoffType', $r->getDropoffType());
694  if ($r->hasService()) {
695  $xml->addChild('Service', $r->getService());
696  }
697  $xml->addChild('Packaging', $r->getPackaging());
698  $xml->addChild('WeightUnits', 'LBS');
699  $xml->addChild('Weight', $r->getWeight());
700 
701  $originAddress = $xml->addChild('OriginAddress');
702  $originAddress->addChild('PostalCode', $r->getOrigPostal());
703  $originAddress->addChild('CountryCode', $r->getOrigCountry());
704 
705  $destinationAddress = $xml->addChild('DestinationAddress');
706  $destinationAddress->addChild('PostalCode', $r->getDestPostal());
707  $destinationAddress->addChild('CountryCode', $r->getDestCountry());
708 
709  $payment = $xml->addChild('Payment');
710  $payment->addChild('PayorType', 'SENDER');
711 
712  $declaredValue = $xml->addChild('DeclaredValue');
713  $declaredValue->addChild('Value', $r->getValue());
714  $declaredValue->addChild('CurrencyCode', $this->getCurrencyCode());
715 
716  if ($this->getConfigData('residence_delivery')) {
717  $specialServices = $xml->addChild('SpecialServices');
718  $specialServices->addChild('ResidentialDelivery', 'true');
719  }
720 
721  $xml->addChild('PackageCount', '1');
722 
723  $request = $xml->asXML();
724 
725  $responseBody = $this->_getCachedQuotes($request);
726  if ($responseBody === null) {
727  $debugData = ['request' => $this->filterDebugData($request)];
728  try {
729  $url = $this->getConfigData('gateway_url');
730  if (!$url) {
731  $url = $this->_defaultGatewayUrl;
732  }
733  $ch = curl_init();
734  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
735  curl_setopt($ch, CURLOPT_URL, $url);
736  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
737  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
738  curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
739  $responseBody = curl_exec($ch);
740  curl_close($ch);
741 
742  $debugData['result'] = $this->filterDebugData($responseBody);
743  $this->_setCachedQuotes($request, $responseBody);
744  } catch (\Exception $e) {
745  $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
746  $responseBody = '';
747  }
748  $this->_debug($debugData);
749  }
750 
751  return $this->_parseXmlResponse($responseBody);
752  }
753 
761  protected function _parseXmlResponse($response)
762  {
763  $costArr = [];
764  $priceArr = [];
765 
766  if (strlen(trim($response)) > 0) {
767  $xml = $this->parseXml($response, \Magento\Shipping\Model\Simplexml\Element::class);
768  if (is_object($xml)) {
769  if (is_object($xml->Error) && is_object($xml->Error->Message)) {
770  $errorTitle = (string)$xml->Error->Message;
771  } elseif (is_object($xml->SoftError) && is_object($xml->SoftError->Message)) {
772  $errorTitle = (string)$xml->SoftError->Message;
773  } else {
774  $errorTitle = 'Sorry, something went wrong. Please try again or contact us and we\'ll try to help.';
775  }
776 
777  $allowedMethods = explode(",", $this->getConfigData('allowed_methods'));
778 
779  foreach ($xml->Entry as $entry) {
780  if (in_array((string)$entry->Service, $allowedMethods)) {
781  $costArr[(string)$entry->Service] = (string)$entry
782  ->EstimatedCharges
783  ->DiscountedCharges
784  ->NetCharge;
785  $priceArr[(string)$entry->Service] = $this->getMethodPrice(
786  (string)$entry->EstimatedCharges->DiscountedCharges->NetCharge,
787  (string)$entry->Service
788  );
789  }
790  }
791 
792  asort($priceArr);
793  } else {
794  $errorTitle = 'Response is in the wrong format.';
795  }
796  } else {
797  $errorTitle = 'For some reason we can\'t retrieve tracking info right now.';
798  }
799 
800  $result = $this->_rateFactory->create();
801  if (empty($priceArr)) {
802  $error = $this->_rateErrorFactory->create();
803  $error->setCarrier('fedex');
804  $error->setCarrierTitle($this->getConfigData('title'));
805  $error->setErrorMessage($this->getConfigData('specificerrmsg'));
806  $result->append($error);
807  } else {
808  foreach ($priceArr as $method => $price) {
809  $rate = $this->_rateMethodFactory->create();
810  $rate->setCarrier('fedex');
811  $rate->setCarrierTitle($this->getConfigData('title'));
812  $rate->setMethod($method);
813  $rate->setMethodTitle($this->getCode('method', $method));
814  $rate->setCost($costArr[$method]);
815  $rate->setPrice($price);
816  $result->append($rate);
817  }
818  }
819 
820  return $result;
821  }
822 
831  public function getCode($type, $code = '')
832  {
833  $codes = [
834  'method' => [
835  'EUROPE_FIRST_INTERNATIONAL_PRIORITY' => __('Europe First Priority'),
836  'FEDEX_1_DAY_FREIGHT' => __('1 Day Freight'),
837  'FEDEX_2_DAY_FREIGHT' => __('2 Day Freight'),
838  'FEDEX_2_DAY' => __('2 Day'),
839  'FEDEX_2_DAY_AM' => __('2 Day AM'),
840  'FEDEX_3_DAY_FREIGHT' => __('3 Day Freight'),
841  'FEDEX_EXPRESS_SAVER' => __('Express Saver'),
842  'FEDEX_GROUND' => __('Ground'),
843  'FIRST_OVERNIGHT' => __('First Overnight'),
844  'GROUND_HOME_DELIVERY' => __('Home Delivery'),
845  'INTERNATIONAL_ECONOMY' => __('International Economy'),
846  'INTERNATIONAL_ECONOMY_FREIGHT' => __('Intl Economy Freight'),
847  'INTERNATIONAL_FIRST' => __('International First'),
848  'INTERNATIONAL_GROUND' => __('International Ground'),
849  'INTERNATIONAL_PRIORITY' => __('International Priority'),
850  'INTERNATIONAL_PRIORITY_FREIGHT' => __('Intl Priority Freight'),
851  'PRIORITY_OVERNIGHT' => __('Priority Overnight'),
852  'SMART_POST' => __('Smart Post'),
853  'STANDARD_OVERNIGHT' => __('Standard Overnight'),
854  'FEDEX_FREIGHT' => __('Freight'),
855  'FEDEX_NATIONAL_FREIGHT' => __('National Freight'),
856  ],
857  'dropoff' => [
858  'REGULAR_PICKUP' => __('Regular Pickup'),
859  'REQUEST_COURIER' => __('Request Courier'),
860  'DROP_BOX' => __('Drop Box'),
861  'BUSINESS_SERVICE_CENTER' => __('Business Service Center'),
862  'STATION' => __('Station'),
863  ],
864  'packaging' => [
865  'FEDEX_ENVELOPE' => __('FedEx Envelope'),
866  'FEDEX_PAK' => __('FedEx Pak'),
867  'FEDEX_BOX' => __('FedEx Box'),
868  'FEDEX_TUBE' => __('FedEx Tube'),
869  'FEDEX_10KG_BOX' => __('FedEx 10kg Box'),
870  'FEDEX_25KG_BOX' => __('FedEx 25kg Box'),
871  'YOUR_PACKAGING' => __('Your Packaging'),
872  ],
873  'containers_filter' => [
874  [
875  'containers' => ['FEDEX_ENVELOPE', 'FEDEX_PAK'],
876  'filters' => [
877  'within_us' => [
878  'method' => [
879  'FEDEX_EXPRESS_SAVER',
880  'FEDEX_2_DAY',
881  'FEDEX_2_DAY_AM',
882  'STANDARD_OVERNIGHT',
883  'PRIORITY_OVERNIGHT',
884  'FIRST_OVERNIGHT',
885  ],
886  ],
887  'from_us' => [
888  'method' => ['INTERNATIONAL_FIRST', 'INTERNATIONAL_ECONOMY', 'INTERNATIONAL_PRIORITY'],
889  ],
890  ],
891  ],
892  [
893  'containers' => ['FEDEX_BOX', 'FEDEX_TUBE'],
894  'filters' => [
895  'within_us' => [
896  'method' => [
897  'FEDEX_2_DAY',
898  'FEDEX_2_DAY_AM',
899  'STANDARD_OVERNIGHT',
900  'PRIORITY_OVERNIGHT',
901  'FIRST_OVERNIGHT',
902  'FEDEX_FREIGHT',
903  'FEDEX_1_DAY_FREIGHT',
904  'FEDEX_2_DAY_FREIGHT',
905  'FEDEX_3_DAY_FREIGHT',
906  'FEDEX_NATIONAL_FREIGHT',
907  ],
908  ],
909  'from_us' => [
910  'method' => ['INTERNATIONAL_FIRST', 'INTERNATIONAL_ECONOMY', 'INTERNATIONAL_PRIORITY'],
911  ],
912  ],
913  ],
914  [
915  'containers' => ['FEDEX_10KG_BOX', 'FEDEX_25KG_BOX'],
916  'filters' => [
917  'within_us' => [],
918  'from_us' => ['method' => ['INTERNATIONAL_PRIORITY']],
919  ],
920  ],
921  [
922  'containers' => ['YOUR_PACKAGING'],
923  'filters' => [
924  'within_us' => [
925  'method' => [
926  'FEDEX_GROUND',
927  'GROUND_HOME_DELIVERY',
928  'SMART_POST',
929  'FEDEX_EXPRESS_SAVER',
930  'FEDEX_2_DAY',
931  'FEDEX_2_DAY_AM',
932  'STANDARD_OVERNIGHT',
933  'PRIORITY_OVERNIGHT',
934  'FIRST_OVERNIGHT',
935  'FEDEX_FREIGHT',
936  'FEDEX_1_DAY_FREIGHT',
937  'FEDEX_2_DAY_FREIGHT',
938  'FEDEX_3_DAY_FREIGHT',
939  'FEDEX_NATIONAL_FREIGHT',
940  ],
941  ],
942  'from_us' => [
943  'method' => [
944  'INTERNATIONAL_FIRST',
945  'INTERNATIONAL_ECONOMY',
946  'INTERNATIONAL_PRIORITY',
947  'INTERNATIONAL_GROUND',
948  'FEDEX_FREIGHT',
949  'FEDEX_1_DAY_FREIGHT',
950  'FEDEX_2_DAY_FREIGHT',
951  'FEDEX_3_DAY_FREIGHT',
952  'FEDEX_NATIONAL_FREIGHT',
953  'INTERNATIONAL_ECONOMY_FREIGHT',
954  'INTERNATIONAL_PRIORITY_FREIGHT',
955  ],
956  ],
957  ],
958  ],
959  ],
960  'delivery_confirmation_types' => [
961  'NO_SIGNATURE_REQUIRED' => __('Not Required'),
962  'ADULT' => __('Adult'),
963  'DIRECT' => __('Direct'),
964  'INDIRECT' => __('Indirect'),
965  ],
966  'unit_of_measure' => [
967  'LB' => __('Pounds'),
968  'KG' => __('Kilograms'),
969  ],
970  ];
971 
972  if (!isset($codes[$type])) {
973  return false;
974  } elseif ('' === $code) {
975  return $codes[$type];
976  }
977 
978  if (!isset($codes[$type][$code])) {
979  return false;
980  } else {
981  return $codes[$type][$code];
982  }
983  }
984 
990  public function getCurrencyCode()
991  {
992  $codes = [
993  'DOP' => 'RDD',
994  'XCD' => 'ECD',
995  'ARS' => 'ARN',
996  'SGD' => 'SID',
997  'KRW' => 'WON',
998  'JMD' => 'JAD',
999  'CHF' => 'SFR',
1000  'JPY' => 'JYE',
1001  'KWD' => 'KUD',
1002  'GBP' => 'UKL',
1003  'AED' => 'DHS',
1004  'MXN' => 'NMP',
1005  'UYU' => 'UYP',
1006  'CLP' => 'CHP',
1007  'TWD' => 'NTD',
1008  ];
1009  $currencyCode = $this->_storeManager->getStore()->getBaseCurrencyCode();
1010 
1011  return isset($codes[$currencyCode]) ? $codes[$currencyCode] : $currencyCode;
1012  }
1013 
1020  public function getTracking($trackings)
1021  {
1022  $this->setTrackingReqeust();
1023 
1024  if (!is_array($trackings)) {
1025  $trackings = [$trackings];
1026  }
1027 
1028  foreach ($trackings as $tracking) {
1029  $this->_getXMLTracking($tracking);
1030  }
1031 
1032  return $this->_result;
1033  }
1034 
1040  protected function setTrackingReqeust()
1041  {
1042  $r = new \Magento\Framework\DataObject();
1043 
1044  $account = $this->getConfigData('account');
1045  $r->setAccount($account);
1046 
1047  $this->_rawTrackingRequest = $r;
1048  }
1049 
1056  protected function _getXMLTracking($tracking)
1057  {
1058  $trackRequest = [
1059  'WebAuthenticationDetail' => [
1060  'UserCredential' => [
1061  'Key' => $this->getConfigData('key'),
1062  'Password' => $this->getConfigData('password'),
1063  ],
1064  ],
1065  'ClientDetail' => [
1066  'AccountNumber' => $this->getConfigData('account'),
1067  'MeterNumber' => $this->getConfigData('meter_number'),
1068  ],
1069  'Version' => [
1070  'ServiceId' => 'trck',
1071  'Major' => self::$trackServiceVersion,
1072  'Intermediate' => '0',
1073  'Minor' => '0',
1074  ],
1075  'SelectionDetails' => [
1076  'PackageIdentifier' => ['Type' => 'TRACKING_NUMBER_OR_DOORTAG', 'Value' => $tracking],
1077  ],
1078  'ProcessingOptions' => 'INCLUDE_DETAILED_SCANS'
1079  ];
1080  $requestString = $this->serializer->serialize($trackRequest);
1081  $response = $this->_getCachedQuotes($requestString);
1082  $debugData = ['request' => $this->filterDebugData($trackRequest)];
1083  if ($response === null) {
1084  try {
1085  $client = $this->_createTrackSoapClient();
1086  $response = $client->track($trackRequest);
1087  $this->_setCachedQuotes($requestString, $response);
1088  $debugData['result'] = $response;
1089  } catch (\Exception $e) {
1090  $debugData['result'] = ['error' => $e->getMessage(), 'code' => $e->getCode()];
1091  $this->_logger->critical($e);
1092  }
1093  } else {
1094  $debugData['result'] = $response;
1095  }
1096  $this->_debug($debugData);
1097 
1098  $this->_parseTrackingResponse($tracking, $response);
1099  }
1100 
1108  protected function _parseTrackingResponse($trackingValue, $response)
1109  {
1110  if (!is_object($response) || empty($response->HighestSeverity)) {
1111  $this->appendTrackingError($trackingValue, __('Invalid response from carrier'));
1112  return;
1113  } elseif (in_array($response->HighestSeverity, self::$trackingErrors)) {
1114  $this->appendTrackingError($trackingValue, (string) $response->Notifications->Message);
1115  return;
1116  } elseif (empty($response->CompletedTrackDetails) || empty($response->CompletedTrackDetails->TrackDetails)) {
1117  $this->appendTrackingError($trackingValue, __('No available tracking items'));
1118  return;
1119  }
1120 
1121  $trackInfo = $response->CompletedTrackDetails->TrackDetails;
1122 
1123  // Fedex can return tracking details as single object instead array
1124  if (is_object($trackInfo)) {
1125  $trackInfo = [$trackInfo];
1126  }
1127 
1128  $result = $this->getResult();
1129  $carrierTitle = $this->getConfigData('title');
1130  $counter = 0;
1131  foreach ($trackInfo as $item) {
1132  $tracking = $this->_trackStatusFactory->create();
1133  $tracking->setCarrier(self::CODE);
1134  $tracking->setCarrierTitle($carrierTitle);
1135  $tracking->setTracking($trackingValue);
1136  $tracking->addData($this->processTrackingDetails($item));
1137  $result->append($tracking);
1138  $counter ++;
1139  }
1140 
1141  // no available tracking details
1142  if (!$counter) {
1143  $this->appendTrackingError(
1144  $trackingValue,
1145  __('For some reason we can\'t retrieve tracking info right now.')
1146  );
1147  }
1148  }
1149 
1155  public function getResponse()
1156  {
1157  $statuses = '';
1158  if ($this->_result instanceof \Magento\Shipping\Model\Tracking\Result) {
1159  if ($trackings = $this->_result->getAllTrackings()) {
1160  foreach ($trackings as $tracking) {
1161  if ($data = $tracking->getAllData()) {
1162  if (!empty($data['status'])) {
1163  $statuses .= __($data['status']) . "\n<br/>";
1164  } else {
1165  $statuses .= __('Empty response') . "\n<br/>";
1166  }
1167  }
1168  }
1169  }
1170  }
1171  if (empty($statuses)) {
1172  $statuses = __('Empty response');
1173  }
1174 
1175  return $statuses;
1176  }
1177 
1183  public function getAllowedMethods()
1184  {
1185  $allowed = explode(',', $this->getConfigData('allowed_methods'));
1186  $arr = [];
1187  foreach ($allowed as $k) {
1188  $arr[$k] = $this->getCode('method', $k);
1189  }
1190 
1191  return $arr;
1192  }
1193 
1199  protected function _getAuthDetails()
1200  {
1201  return [
1202  'WebAuthenticationDetail' => [
1203  'UserCredential' => [
1204  'Key' => $this->getConfigData('key'),
1205  'Password' => $this->getConfigData('password'),
1206  ],
1207  ],
1208  'ClientDetail' => [
1209  'AccountNumber' => $this->getConfigData('account'),
1210  'MeterNumber' => $this->getConfigData('meter_number'),
1211  ],
1212  'TransactionDetail' => [
1213  'CustomerTransactionId' => '*** Express Domestic Shipping Request v9 using PHP ***',
1214  ],
1215  'Version' => ['ServiceId' => 'ship', 'Major' => '10', 'Intermediate' => '0', 'Minor' => '0'],
1216  ];
1217  }
1218 
1228  protected function _formShipmentRequest(\Magento\Framework\DataObject $request)
1229  {
1230  if ($request->getReferenceData()) {
1231  $referenceData = $request->getReferenceData() . $request->getPackageId();
1232  } else {
1233  $referenceData = 'Order #' .
1234  $request->getOrderShipment()->getOrder()->getIncrementId() .
1235  ' P' .
1236  $request->getPackageId();
1237  }
1238  $packageParams = $request->getPackageParams();
1239  $customsValue = $packageParams->getCustomsValue();
1240  $height = $packageParams->getHeight();
1241  $width = $packageParams->getWidth();
1242  $length = $packageParams->getLength();
1243  $weightUnits = $packageParams->getWeightUnits() == \Zend_Measure_Weight::POUND ? 'LB' : 'KG';
1244  $unitPrice = 0;
1245  $itemsQty = 0;
1246  $itemsDesc = [];
1247  $countriesOfManufacture = [];
1248  $productIds = [];
1249  $packageItems = $request->getPackageItems();
1250  foreach ($packageItems as $itemShipment) {
1251  $item = new \Magento\Framework\DataObject();
1252  $item->setData($itemShipment);
1253 
1254  $unitPrice += $item->getPrice();
1255  $itemsQty += $item->getQty();
1256 
1257  $itemsDesc[] = $item->getName();
1258  $productIds[] = $item->getProductId();
1259  }
1260 
1261  // get countries of manufacture
1262  $productCollection = $this->_productCollectionFactory->create()->addStoreFilter(
1263  $request->getStoreId()
1264  )->addFieldToFilter(
1265  'entity_id',
1266  ['in' => $productIds]
1267  )->addAttributeToSelect(
1268  'country_of_manufacture'
1269  );
1270  foreach ($productCollection as $product) {
1271  $countriesOfManufacture[] = $product->getCountryOfManufacture();
1272  }
1273 
1274  $paymentType = $this->getPaymentType($request);
1275  $optionType = $request->getShippingMethod() == self::RATE_REQUEST_SMARTPOST
1276  ? 'SERVICE_DEFAULT' : $packageParams->getDeliveryConfirmation();
1277  $requestClient = [
1278  'RequestedShipment' => [
1279  'ShipTimestamp' => time(),
1280  'DropoffType' => $this->getConfigData('dropoff'),
1281  'PackagingType' => $request->getPackagingType(),
1282  'ServiceType' => $request->getShippingMethod(),
1283  'Shipper' => [
1284  'Contact' => [
1285  'PersonName' => $request->getShipperContactPersonName(),
1286  'CompanyName' => $request->getShipperContactCompanyName(),
1287  'PhoneNumber' => $request->getShipperContactPhoneNumber(),
1288  ],
1289  'Address' => [
1290  'StreetLines' => [$request->getShipperAddressStreet()],
1291  'City' => $request->getShipperAddressCity(),
1292  'StateOrProvinceCode' => $request->getShipperAddressStateOrProvinceCode(),
1293  'PostalCode' => $request->getShipperAddressPostalCode(),
1294  'CountryCode' => $request->getShipperAddressCountryCode(),
1295  ],
1296  ],
1297  'Recipient' => [
1298  'Contact' => [
1299  'PersonName' => $request->getRecipientContactPersonName(),
1300  'CompanyName' => $request->getRecipientContactCompanyName(),
1301  'PhoneNumber' => $request->getRecipientContactPhoneNumber(),
1302  ],
1303  'Address' => [
1304  'StreetLines' => [$request->getRecipientAddressStreet()],
1305  'City' => $request->getRecipientAddressCity(),
1306  'StateOrProvinceCode' => $request->getRecipientAddressStateOrProvinceCode(),
1307  'PostalCode' => $request->getRecipientAddressPostalCode(),
1308  'CountryCode' => $request->getRecipientAddressCountryCode(),
1309  'Residential' => (bool)$this->getConfigData('residence_delivery'),
1310  ],
1311  ],
1312  'ShippingChargesPayment' => [
1313  'PaymentType' => $paymentType,
1314  'Payor' => [
1315  'AccountNumber' => $this->getConfigData('account'),
1316  'CountryCode' => $this->_scopeConfig->getValue(
1317  \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_COUNTRY_ID,
1318  \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
1319  $request->getStoreId()
1320  ),
1321  ],
1322  ],
1323  'LabelSpecification' => [
1324  'LabelFormatType' => 'COMMON2D',
1325  'ImageType' => 'PNG',
1326  'LabelStockType' => 'PAPER_8.5X11_TOP_HALF_LABEL',
1327  ],
1328  'RateRequestTypes' => ['ACCOUNT'],
1329  'PackageCount' => 1,
1330  'RequestedPackageLineItems' => [
1331  'SequenceNumber' => '1',
1332  'Weight' => ['Units' => $weightUnits, 'Value' => $request->getPackageWeight()],
1333  'CustomerReferences' => [
1334  'CustomerReferenceType' => 'CUSTOMER_REFERENCE',
1335  'Value' => $referenceData,
1336  ],
1337  'SpecialServicesRequested' => [
1338  'SpecialServiceTypes' => 'SIGNATURE_OPTION',
1339  'SignatureOptionDetail' => ['OptionType' => $optionType],
1340  ],
1341  ],
1342  ],
1343  ];
1344 
1345  // for international shipping
1346  if ($request->getShipperAddressCountryCode() != $request->getRecipientAddressCountryCode()) {
1347  $requestClient['RequestedShipment']['CustomsClearanceDetail'] = [
1348  'CustomsValue' => ['Currency' => $request->getBaseCurrencyCode(), 'Amount' => $customsValue],
1349  'DutiesPayment' => [
1350  'PaymentType' => $paymentType,
1351  'Payor' => [
1352  'AccountNumber' => $this->getConfigData('account'),
1353  'CountryCode' => $this->_scopeConfig->getValue(
1354  \Magento\Sales\Model\Order\Shipment::XML_PATH_STORE_COUNTRY_ID,
1355  \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
1356  $request->getStoreId()
1357  ),
1358  ],
1359  ],
1360  'Commodities' => [
1361  'Weight' => ['Units' => $weightUnits, 'Value' => $request->getPackageWeight()],
1362  'NumberOfPieces' => 1,
1363  'CountryOfManufacture' => implode(',', array_unique($countriesOfManufacture)),
1364  'Description' => implode(', ', $itemsDesc),
1365  'Quantity' => ceil($itemsQty),
1366  'QuantityUnits' => 'pcs',
1367  'UnitPrice' => ['Currency' => $request->getBaseCurrencyCode(), 'Amount' => $unitPrice],
1368  'CustomsValue' => ['Currency' => $request->getBaseCurrencyCode(), 'Amount' => $customsValue],
1369  ],
1370  ];
1371  }
1372 
1373  if ($request->getMasterTrackingId()) {
1374  $requestClient['RequestedShipment']['MasterTrackingId'] = $request->getMasterTrackingId();
1375  }
1376 
1377  if ($request->getShippingMethod() == self::RATE_REQUEST_SMARTPOST) {
1378  $requestClient['RequestedShipment']['SmartPostDetail'] = [
1379  'Indicia' => (double)$request->getPackageWeight() >= 1 ? 'PARCEL_SELECT' : 'PRESORTED_STANDARD',
1380  'HubId' => $this->getConfigData('smartpost_hubid'),
1381  ];
1382  }
1383 
1384  // set dimensions
1385  if ($length || $width || $height) {
1386  $requestClient['RequestedShipment']['RequestedPackageLineItems']['Dimensions'] = [
1387  'Length' => $length,
1388  'Width' => $width,
1389  'Height' => $height,
1390  'Units' => $packageParams->getDimensionUnits() == \Zend_Measure_Length::INCH ? 'IN' : 'CM',
1391  ];
1392  }
1393 
1394  return $this->_getAuthDetails() + $requestClient;
1395  }
1396 
1403  protected function _doShipmentRequest(\Magento\Framework\DataObject $request)
1404  {
1405  $this->_prepareShipmentRequest($request);
1406  $result = new \Magento\Framework\DataObject();
1407  $client = $this->_createShipSoapClient();
1408  $requestClient = $this->_formShipmentRequest($request);
1409  $debugData['request'] = $this->filterDebugData($requestClient);
1410  $response = $client->processShipment($requestClient);
1411 
1412  if ($response->HighestSeverity != 'FAILURE' && $response->HighestSeverity != 'ERROR') {
1413  $shippingLabelContent = $response->CompletedShipmentDetail->CompletedPackageDetails->Label->Parts->Image;
1414  $trackingNumber = $this->getTrackingNumber(
1415  $response->CompletedShipmentDetail->CompletedPackageDetails->TrackingIds
1416  );
1417  $result->setShippingLabelContent($shippingLabelContent);
1418  $result->setTrackingNumber($trackingNumber);
1419  $debugData['result'] = $client->__getLastResponse();
1420  $this->_debug($debugData);
1421  } else {
1422  $debugData['result'] = ['error' => '', 'code' => '', 'xml' => $client->__getLastResponse()];
1423  if (is_array($response->Notifications)) {
1424  foreach ($response->Notifications as $notification) {
1425  $debugData['result']['code'] .= $notification->Code . '; ';
1426  $debugData['result']['error'] .= $notification->Message . '; ';
1427  }
1428  } else {
1429  $debugData['result']['code'] = $response->Notifications->Code . ' ';
1430  $debugData['result']['error'] = $response->Notifications->Message . ' ';
1431  }
1432  $this->_debug($debugData);
1433  $result->setErrors($debugData['result']['error']);
1434  }
1435  $result->setGatewayResponse($client->__getLastResponse());
1436 
1437  return $result;
1438  }
1439 
1444  private function getTrackingNumber($trackingIds)
1445  {
1446  return is_array($trackingIds) ? array_map(
1447  function ($val) {
1448  return $val->TrackingNumber;
1449  },
1450  $trackingIds
1451  ) : $trackingIds->TrackingNumber;
1452  }
1453 
1461  public function rollBack($data)
1462  {
1463  $requestData = $this->_getAuthDetails();
1464  $requestData['DeletionControl'] = 'DELETE_ONE_PACKAGE';
1465  foreach ($data as &$item) {
1466  $requestData['TrackingId'] = $item['tracking_number'];
1467  $client = $this->_createShipSoapClient();
1468  $client->deleteShipment($requestData);
1469  }
1470 
1471  return true;
1472  }
1473 
1481  public function getContainerTypes(\Magento\Framework\DataObject $params = null)
1482  {
1483  if ($params == null) {
1484  return $this->_getAllowedContainers($params);
1485  }
1486  $method = $params->getMethod();
1487  $countryShipper = $params->getCountryShipper();
1488  $countryRecipient = $params->getCountryRecipient();
1489 
1490  if (($countryShipper == self::USA_COUNTRY_ID && $countryRecipient == self::CANADA_COUNTRY_ID ||
1491  $countryShipper == self::CANADA_COUNTRY_ID &&
1492  $countryRecipient == self::USA_COUNTRY_ID) &&
1493  $method == 'FEDEX_GROUND'
1494  ) {
1495  return ['YOUR_PACKAGING' => __('Your Packaging')];
1496  } else {
1497  if ($method == 'INTERNATIONAL_ECONOMY' || $method == 'INTERNATIONAL_FIRST') {
1498  $allTypes = $this->getContainerTypesAll();
1499  $exclude = ['FEDEX_10KG_BOX' => '', 'FEDEX_25KG_BOX' => ''];
1500 
1501  return array_diff_key($allTypes, $exclude);
1502  } else {
1503  if ($method == 'EUROPE_FIRST_INTERNATIONAL_PRIORITY') {
1504  $allTypes = $this->getContainerTypesAll();
1505  $exclude = ['FEDEX_BOX' => '', 'FEDEX_TUBE' => ''];
1506 
1507  return array_diff_key($allTypes, $exclude);
1508  } else {
1509  if ($countryShipper == self::CANADA_COUNTRY_ID && $countryRecipient == self::CANADA_COUNTRY_ID) {
1510  // hack for Canada domestic. Apply the same filter rules as for US domestic
1511  $params->setCountryShipper(self::USA_COUNTRY_ID);
1512  $params->setCountryRecipient(self::USA_COUNTRY_ID);
1513  }
1514  }
1515  }
1516  }
1517 
1518  return $this->_getAllowedContainers($params);
1519  }
1520 
1526  public function getContainerTypesAll()
1527  {
1528  return $this->getCode('packaging');
1529  }
1530 
1536  public function getContainerTypesFilter()
1537  {
1538  return $this->getCode('containers_filter');
1539  }
1540 
1548  public function getDeliveryConfirmationTypes(\Magento\Framework\DataObject $params = null)
1549  {
1550  return $this->getCode('delivery_confirmation_types');
1551  }
1552 
1558  protected function filterDebugData($data)
1559  {
1560  foreach (array_keys($data) as $key) {
1561  if (is_array($data[$key])) {
1562  $data[$key] = $this->filterDebugData($data[$key]);
1563  } elseif (in_array($key, $this->_debugReplacePrivateDataKeys)) {
1564  $data[$key] = self::DEBUG_KEYS_MASK;
1565  }
1566  }
1567  return $data;
1568  }
1569 
1577  private function processTrackingDetails(\stdClass $trackInfo)
1578  {
1579  $result = [
1580  'shippeddate' => null,
1581  'deliverydate' => null,
1582  'deliverytime' => null,
1583  'deliverylocation' => null,
1584  'weight' => null,
1585  'progressdetail' => [],
1586  ];
1587 
1588  $datetime = $this->parseDate(!empty($trackInfo->ShipTimestamp) ? $trackInfo->ShipTimestamp : null);
1589  if ($datetime) {
1590  $result['shippeddate'] = gmdate('Y-m-d', $datetime->getTimestamp());
1591  }
1592 
1593  $result['signedby'] = !empty($trackInfo->DeliverySignatureName) ?
1594  (string) $trackInfo->DeliverySignatureName :
1595  null;
1596 
1597  $result['status'] = (!empty($trackInfo->StatusDetail) && !empty($trackInfo->StatusDetail->Description)) ?
1598  (string) $trackInfo->StatusDetail->Description :
1599  null;
1600  $result['service'] = (!empty($trackInfo->Service) && !empty($trackInfo->Service->Description)) ?
1601  (string) $trackInfo->Service->Description :
1602  null;
1603 
1604  $datetime = $this->getDeliveryDateTime($trackInfo);
1605  if ($datetime) {
1606  $result['deliverydate'] = gmdate('Y-m-d', $datetime->getTimestamp());
1607  $result['deliverytime'] = gmdate('H:i:s', $datetime->getTimestamp());
1608  }
1609 
1610  $address = null;
1611  if (!empty($trackInfo->EstimatedDeliveryAddress)) {
1612  $address = $trackInfo->EstimatedDeliveryAddress;
1613  } elseif (!empty($trackInfo->ActualDeliveryAddress)) {
1614  $address = $trackInfo->ActualDeliveryAddress;
1615  }
1616 
1617  if (!empty($address)) {
1618  $result['deliverylocation'] = $this->getDeliveryAddress($address);
1619  }
1620 
1621  if (!empty($trackInfo->PackageWeight)) {
1622  $result['weight'] = sprintf(
1623  '%s %s',
1624  (string) $trackInfo->PackageWeight->Value,
1625  (string) $trackInfo->PackageWeight->Units
1626  );
1627  }
1628 
1629  if (!empty($trackInfo->Events)) {
1630  $events = $trackInfo->Events;
1631  if (is_object($events)) {
1632  $events = [$trackInfo->Events];
1633  }
1634  $result['progressdetail'] = $this->processTrackDetailsEvents($events);
1635  }
1636 
1637  return $result;
1638  }
1639 
1645  private function getDeliveryDateTime(\stdClass $trackInfo)
1646  {
1647  $timestamp = null;
1648  if (!empty($trackInfo->EstimatedDeliveryTimestamp)) {
1649  $timestamp = $trackInfo->EstimatedDeliveryTimestamp;
1650  } elseif (!empty($trackInfo->ActualDeliveryTimestamp)) {
1651  $timestamp = $trackInfo->ActualDeliveryTimestamp;
1652  }
1653 
1654  return $timestamp ? $this->parseDate($timestamp) : null;
1655  }
1656 
1664  private function getDeliveryAddress(\stdClass $address)
1665  {
1666  $details = [];
1667 
1668  if (!empty($address->City)) {
1669  $details[] = (string) $address->City;
1670  }
1671 
1672  if (!empty($address->StateOrProvinceCode)) {
1673  $details[] = (string) $address->StateOrProvinceCode;
1674  }
1675 
1676  if (!empty($address->CountryCode)) {
1677  $details[] = (string) $address->CountryCode;
1678  }
1679 
1680  return implode(', ', $details);
1681  }
1682 
1691  private function processTrackDetailsEvents(array $events)
1692  {
1693  $result = [];
1695  foreach ($events as $event) {
1696  $item = [
1697  'activity' => (string) $event->EventDescription,
1698  'deliverydate' => null,
1699  'deliverytime' => null,
1700  'deliverylocation' => null
1701  ];
1702 
1703  $datetime = $this->parseDate(!empty($event->Timestamp) ? $event->Timestamp : null);
1704  if ($datetime) {
1705  $item['deliverydate'] = gmdate('Y-m-d', $datetime->getTimestamp());
1706  $item['deliverytime'] = gmdate('H:i:s', $datetime->getTimestamp());
1707  }
1708 
1709  if (!empty($event->Address)) {
1710  $item['deliverylocation'] = $this->getDeliveryAddress($event->Address);
1711  }
1712 
1713  $result[] = $item;
1714  }
1715 
1716  return $result;
1717  }
1718 
1724  private function appendTrackingError($trackingValue, $errorMessage)
1725  {
1726  $error = $this->_trackErrorFactory->create();
1727  $error->setCarrier('fedex');
1728  $error->setCarrierTitle($this->getConfigData('title'));
1729  $error->setTracking($trackingValue);
1730  $error->setErrorMessage($errorMessage);
1731  $result = $this->getResult();
1732  $result->append($error);
1733  }
1734 
1743  private function parseDate($timestamp)
1744  {
1745  if ($timestamp === null) {
1746  return false;
1747  }
1748  $formats = [\DateTime::ATOM, 'Y-m-d\TH:i:s'];
1749  foreach ($formats as $format) {
1750  // set UTC timezone for a case if timestamp does not contain any timezone
1751  $utcTimezone = new \DateTimeZone('UTC');
1752  $dateTime = \DateTime::createFromFormat($format, $timestamp, $utcTimezone);
1753  if ($dateTime !== false) {
1754  return $dateTime;
1755  }
1756  }
1757 
1758  return false;
1759  }
1760 
1768  private function getPaymentType(DataObject $request): string
1769  {
1770  return $request->getIsReturn() && $request->getShippingMethod() !== self::RATE_REQUEST_SMARTPOST
1771  ? 'RECIPIENT'
1772  : 'SENDER';
1773  }
1774 }
_prepareShipmentRequest(\Magento\Framework\DataObject $request)
$response
Definition: 404.php:11
_prepareRateResponse($response)
Definition: Carrier.php:561
_parseXmlResponse($response)
Definition: Carrier.php:761
if(!defined( 'PHP_VERSION_ID')||!(PHP_VERSION_ID===70002||PHP_VERSION_ID===70004||PHP_VERSION_ID >=70006))
Definition: bootstrap.php:14
__construct(\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory, \Psr\Log\LoggerInterface $logger, Security $xmlSecurity, \Magento\Shipping\Model\Simplexml\ElementFactory $xmlElFactory, \Magento\Shipping\Model\Rate\ResultFactory $rateFactory, \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $rateMethodFactory, \Magento\Shipping\Model\Tracking\ResultFactory $trackFactory, \Magento\Shipping\Model\Tracking\Result\ErrorFactory $trackErrorFactory, \Magento\Shipping\Model\Tracking\Result\StatusFactory $trackStatusFactory, \Magento\Directory\Model\RegionFactory $regionFactory, \Magento\Directory\Model\CountryFactory $countryFactory, \Magento\Directory\Model\CurrencyFactory $currencyFactory, \Magento\Directory\Helper\Data $directoryData, \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\Module\Dir\Reader $configReader, \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory, array $data=[], Json $serializer=null, ClientFactory $soapClientFactory=null)
Definition: Carrier.php:176
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
_createSoapClient($wsdl, $trace=false)
Definition: Carrier.php:234
$details
Definition: vault.phtml:10
$storeManager
__()
Definition: __.php:13
$price
getCode($type, $code='')
Definition: Carrier.php:831
collectRates(RateRequest $request)
Definition: Carrier.php:282
_doShipmentRequest(\Magento\Framework\DataObject $request)
Definition: Carrier.php:1403
$logger
$address
Definition: customer.php:38
$amount
Definition: order.php:14
$payment
Definition: order.php:17
$type
Definition: item.phtml:13
getDeliveryConfirmationTypes(\Magento\Framework\DataObject $params=null)
Definition: Carrier.php:1548
setRequest(RateRequest $request)
Definition: Carrier.php:303
$format
Definition: list.phtml:12
_parseTrackingResponse($trackingValue, $response)
Definition: Carrier.php:1108
parseXml($xmlContent, $customSimplexml='SimpleXMLElement')
$method
Definition: info.phtml:13
_getAllowedContainers(\Magento\Framework\DataObject $params=null)
_formShipmentRequest(\Magento\Framework\DataObject $request)
Definition: Carrier.php:1228
getContainerTypes(\Magento\Framework\DataObject $params=null)
Definition: Carrier.php:1481
_setFreeMethodRequest($freeMethod)
Definition: Carrier.php:664
$params[\Magento\Store\Model\StoreManager::PARAM_RUN_CODE]
Definition: website.php:18
$code
Definition: info.phtml:12
$dateTime