Skip to content

Commit 3fce7b2

Browse files
author
Dmytro Yushkin
committed
MAGETWO-57460: [Backport] - Exception occurs when tracking shipment with invalid FedEx tracking number - for 2.1.x
- Resolve merge conflict
2 parents 02b59e7 + ed0c0c4 commit 3fce7b2

File tree

6 files changed

+1857
-578
lines changed

6 files changed

+1857
-578
lines changed

app/code/Magento/Fedex/Model/Carrier.php

Lines changed: 223 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
/**
1818
* Fedex shipping implementation
1919
*
20-
* @author Magento Core Team <core@magentocommerce.com>
20+
* @author Magento Core Team <core@magentocommerce.com>
2121
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
2222
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2323
*/
@@ -126,6 +126,18 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C
126126
'Key', 'Password', 'MeterNumber'
127127
];
128128

129+
/**
130+
* Version of tracking service
131+
* @var int
132+
*/
133+
private static $trackServiceVersion = 10;
134+
135+
/**
136+
* List of TrackReply errors
137+
* @var array
138+
*/
139+
private static $trackingErrors = ['FAILURE', 'ERROR'];
140+
129141
/**
130142
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
131143
* @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory
@@ -193,7 +205,7 @@ public function __construct(
193205
$wsdlBasePath = $configReader->getModuleDir(Dir::MODULE_ETC_DIR, 'Magento_Fedex') . '/wsdl/';
194206
$this->_shipServiceWsdl = $wsdlBasePath . 'ShipService_v10.wsdl';
195207
$this->_rateServiceWsdl = $wsdlBasePath . 'RateService_v10.wsdl';
196-
$this->_trackServiceWsdl = $wsdlBasePath . 'TrackService_v5.wsdl';
208+
$this->_trackServiceWsdl = $wsdlBasePath . 'TrackService_v' . self::$trackServiceVersion . '.wsdl';
197209
}
198210

199211
/**
@@ -371,6 +383,9 @@ public function setRequest(RateRequest $request)
371383
*/
372384
public function getResult()
373385
{
386+
if (!$this->_result) {
387+
$this->_result = $this->_trackFactory->create();
388+
}
374389
return $this->_result;
375390
}
376391

@@ -1034,9 +1049,16 @@ protected function _getXMLTracking($tracking)
10341049
'AccountNumber' => $this->getConfigData('account'),
10351050
'MeterNumber' => $this->getConfigData('meter_number'),
10361051
],
1037-
'Version' => ['ServiceId' => 'trck', 'Major' => '5', 'Intermediate' => '0', 'Minor' => '0'],
1038-
'PackageIdentifier' => ['Type' => 'TRACKING_NUMBER_OR_DOORTAG', 'Value' => $tracking],
1039-
'IncludeDetailedScans' => 1,
1052+
'Version' => [
1053+
'ServiceId' => 'trck',
1054+
'Major' => self::$trackServiceVersion,
1055+
'Intermediate' => '0',
1056+
'Minor' => '0',
1057+
],
1058+
'SelectionDetails' => [
1059+
'PackageIdentifier' => ['Type' => 'TRACKING_NUMBER_OR_DOORTAG', 'Value' => $tracking],
1060+
],
1061+
'ProcessingOptions' => 'INCLUDE_DETAILED_SCANS'
10401062
];
10411063
$requestString = serialize($trackRequest);
10421064
$response = $this->_getCachedQuotes($requestString);
@@ -1063,114 +1085,48 @@ protected function _getXMLTracking($tracking)
10631085
/**
10641086
* Parse tracking response
10651087
*
1066-
* @param string[] $trackingValue
1088+
* @param string $trackingValue
10671089
* @param \stdClass $response
10681090
* @return void
1069-
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
1070-
* @SuppressWarnings(PHPMD.NPathComplexity)
1071-
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
10721091
*/
10731092
protected function _parseTrackingResponse($trackingValue, $response)
10741093
{
1075-
if (is_object($response)) {
1076-
if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
1077-
$errorTitle = (string)$response->Notifications->Message;
1078-
} elseif (isset($response->TrackDetails)) {
1079-
$trackInfo = $response->TrackDetails;
1080-
$resultArray['status'] = (string)$trackInfo->StatusDescription;
1081-
$resultArray['service'] = (string)$trackInfo->ServiceInfo;
1082-
$timestamp = isset(
1083-
$trackInfo->EstimatedDeliveryTimestamp
1084-
) ? $trackInfo->EstimatedDeliveryTimestamp : $trackInfo->ActualDeliveryTimestamp;
1085-
$timestamp = strtotime((string)$timestamp);
1086-
if ($timestamp) {
1087-
$resultArray['deliverydate'] = date('Y-m-d', $timestamp);
1088-
$resultArray['deliverytime'] = date('H:i:s', $timestamp);
1089-
}
1090-
1091-
$deliveryLocation = isset(
1092-
$trackInfo->EstimatedDeliveryAddress
1093-
) ? $trackInfo->EstimatedDeliveryAddress : $trackInfo->ActualDeliveryAddress;
1094-
$deliveryLocationArray = [];
1095-
if (isset($deliveryLocation->City)) {
1096-
$deliveryLocationArray[] = (string)$deliveryLocation->City;
1097-
}
1098-
if (isset($deliveryLocation->StateOrProvinceCode)) {
1099-
$deliveryLocationArray[] = (string)$deliveryLocation->StateOrProvinceCode;
1100-
}
1101-
if (isset($deliveryLocation->CountryCode)) {
1102-
$deliveryLocationArray[] = (string)$deliveryLocation->CountryCode;
1103-
}
1104-
if ($deliveryLocationArray) {
1105-
$resultArray['deliverylocation'] = implode(', ', $deliveryLocationArray);
1106-
}
1107-
1108-
$resultArray['signedby'] = (string)$trackInfo->DeliverySignatureName;
1109-
$resultArray['shippeddate'] = date('Y-m-d', (int)$trackInfo->ShipTimestamp);
1110-
if (isset($trackInfo->PackageWeight) && isset($trackInfo->Units)) {
1111-
$weight = (string)$trackInfo->PackageWeight;
1112-
$unit = (string)$trackInfo->Units;
1113-
$resultArray['weight'] = "{$weight} {$unit}";
1114-
}
1115-
1116-
$packageProgress = [];
1117-
if (isset($trackInfo->Events)) {
1118-
$events = $trackInfo->Events;
1119-
if (isset($events->Address)) {
1120-
$events = [$events];
1121-
}
1122-
foreach ($events as $event) {
1123-
$tempArray = [];
1124-
$tempArray['activity'] = (string)$event->EventDescription;
1125-
$timestamp = strtotime((string)$event->Timestamp);
1126-
if ($timestamp) {
1127-
$tempArray['deliverydate'] = date('Y-m-d', $timestamp);
1128-
$tempArray['deliverytime'] = date('H:i:s', $timestamp);
1129-
}
1130-
if (isset($event->Address)) {
1131-
$addressArray = [];
1132-
$address = $event->Address;
1133-
if (isset($address->City)) {
1134-
$addressArray[] = (string)$address->City;
1135-
}
1136-
if (isset($address->StateOrProvinceCode)) {
1137-
$addressArray[] = (string)$address->StateOrProvinceCode;
1138-
}
1139-
if (isset($address->CountryCode)) {
1140-
$addressArray[] = (string)$address->CountryCode;
1141-
}
1142-
if ($addressArray) {
1143-
$tempArray['deliverylocation'] = implode(', ', $addressArray);
1144-
}
1145-
}
1146-
$packageProgress[] = $tempArray;
1147-
}
1148-
}
1149-
1150-
$resultArray['progressdetail'] = $packageProgress;
1151-
}
1094+
if (!is_object($response) || empty($response->HighestSeverity)) {
1095+
$this->appendTrackingError($trackingValue, __('Invalid response from carrier'));
1096+
return;
1097+
} else if (in_array($response->HighestSeverity, self::$trackingErrors)) {
1098+
$this->appendTrackingError($trackingValue, (string) $response->Notifications->Message);
1099+
return;
1100+
} else if (empty($response->CompletedTrackDetails) || empty($response->CompletedTrackDetails->TrackDetails)) {
1101+
$this->appendTrackingError($trackingValue, __('No available tracking items'));
1102+
return;
11521103
}
11531104

1154-
if (!$this->_result) {
1155-
$this->_result = $this->_trackFactory->create();
1105+
$trackInfo = $response->CompletedTrackDetails->TrackDetails;
1106+
1107+
// Fedex can return tracking details as single object instead array
1108+
if (is_object($trackInfo)) {
1109+
$trackInfo = [$trackInfo];
11561110
}
11571111

1158-
if (isset($resultArray)) {
1112+
$result = $this->getResult();
1113+
$carrierTitle = $this->getConfigData('title');
1114+
$counter = 0;
1115+
foreach ($trackInfo as $item) {
11591116
$tracking = $this->_trackStatusFactory->create();
1160-
$tracking->setCarrier('fedex');
1161-
$tracking->setCarrierTitle($this->getConfigData('title'));
1117+
$tracking->setCarrier(self::CODE);
1118+
$tracking->setCarrierTitle($carrierTitle);
11621119
$tracking->setTracking($trackingValue);
1163-
$tracking->addData($resultArray);
1164-
$this->_result->append($tracking);
1165-
} else {
1166-
$error = $this->_trackErrorFactory->create();
1167-
$error->setCarrier('fedex');
1168-
$error->setCarrierTitle($this->getConfigData('title'));
1169-
$error->setTracking($trackingValue);
1170-
$error->setErrorMessage(
1171-
$errorTitle ? $errorTitle : __('For some reason we can\'t retrieve tracking info right now.')
1120+
$tracking->addData($this->processTrackingDetails($item));
1121+
$result->append($tracking);
1122+
$counter ++;
1123+
}
1124+
1125+
// no available tracking details
1126+
if (!$counter) {
1127+
$this->appendTrackingError(
1128+
$trackingValue, __('For some reason we can\'t retrieve tracking info right now.')
11721129
);
1173-
$this->_result->append($error);
11741130
}
11751131
}
11761132

@@ -1594,4 +1550,170 @@ protected function filterDebugData($data)
15941550
}
15951551
return $data;
15961552
}
1553+
1554+
/**
1555+
* Parse track details response from Fedex
1556+
* @param \stdClass $trackInfo
1557+
* @return array
1558+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
1559+
+ @SuppressWarnings(PHPMD.NPathComplexity)
1560+
*/
1561+
private function processTrackingDetails(\stdClass $trackInfo)
1562+
{
1563+
$result = [
1564+
'shippeddate' => null,
1565+
'deliverydate' => null,
1566+
'deliverytime' => null,
1567+
'deliverylocation' => null,
1568+
'weight' => null,
1569+
'progressdetail' => [],
1570+
];
1571+
1572+
if (!empty($trackInfo->ShipTimestamp) &&
1573+
($datetime = \DateTime::createFromFormat(\DateTime::ISO8601, $trackInfo->ShipTimestamp)) !== false
1574+
) {
1575+
$result['shippeddate'] = $datetime->format('Y-m-d');
1576+
}
1577+
1578+
$result['signedby'] = !empty($trackInfo->DeliverySignatureName) ?
1579+
(string) $trackInfo->DeliverySignatureName :
1580+
null;
1581+
1582+
$result['status'] = (!empty($trackInfo->StatusDetail) && !empty($trackInfo->StatusDetail->Description)) ?
1583+
(string) $trackInfo->StatusDetail->Description :
1584+
null;
1585+
$result['service'] = (!empty($trackInfo->Service) && !empty($trackInfo->Service->Description)) ?
1586+
(string) $trackInfo->Service->Description :
1587+
null;
1588+
1589+
$datetime = $this->getDeliveryDateTime($trackInfo);
1590+
if ($datetime) {
1591+
$result['deliverydate'] = $datetime->format('Y-m-d');
1592+
$result['deliverytime'] = $datetime->format('H:i:s');
1593+
}
1594+
1595+
$address = null;
1596+
if (!empty($trackInfo->EstimatedDeliveryAddress)) {
1597+
$address = $trackInfo->EstimatedDeliveryAddress;
1598+
} elseif (!empty($trackInfo->ActualDeliveryAddress)) {
1599+
$address = $trackInfo->ActualDeliveryAddress;
1600+
}
1601+
1602+
if (!empty($address)) {
1603+
$result['deliverylocation'] = $this->getDeliveryAddress($address);
1604+
}
1605+
1606+
if (!empty($trackInfo->PackageWeight)) {
1607+
$result['weight'] = sprintf(
1608+
'%s %s',
1609+
(string) $trackInfo->PackageWeight->Value,
1610+
(string) $trackInfo->PackageWeight->Units
1611+
);
1612+
}
1613+
1614+
if (!empty($trackInfo->Events)) {
1615+
$events = $trackInfo->Events;
1616+
if (is_object($events)) {
1617+
$events = [$trackInfo->Events];
1618+
}
1619+
$result['progressdetail'] = $this->processTrackDetailsEvents($events);
1620+
}
1621+
1622+
return $result;
1623+
}
1624+
1625+
/**
1626+
* Parse delivery datetime from tracking details
1627+
* @param \stdClass $trackInfo
1628+
* @return \Datetime|null
1629+
*/
1630+
private function getDeliveryDateTime(\stdClass $trackInfo)
1631+
{
1632+
$timestamp = null;
1633+
if (!empty($trackInfo->EstimatedDeliveryTimestamp)) {
1634+
$timestamp = $trackInfo->EstimatedDeliveryTimestamp;
1635+
} elseif (!empty($trackInfo->ActualDeliveryTimestamp)) {
1636+
$timestamp = $trackInfo->ActualDeliveryTimestamp;
1637+
}
1638+
1639+
return $timestamp ? \DateTime::createFromFormat(\DateTime::ISO8601, $timestamp) : null;
1640+
}
1641+
1642+
/**
1643+
* Get delivery address details in string representation
1644+
* Return City, State, Country Code
1645+
*
1646+
* @param \stdClass $address
1647+
* @return \Magento\Framework\Phrase|string
1648+
*/
1649+
private function getDeliveryAddress(\stdClass $address)
1650+
{
1651+
$details = [];
1652+
1653+
if (!empty($address->City)) {
1654+
$details[] = (string) $address->City;
1655+
}
1656+
1657+
if (!empty($address->StateOrProvinceCode)) {
1658+
$details[] = (string) $address->StateOrProvinceCode;
1659+
}
1660+
1661+
if (!empty($address->CountryCode)) {
1662+
$details[] = (string) $address->CountryCode;
1663+
}
1664+
1665+
return implode(', ', $details);
1666+
}
1667+
1668+
/**
1669+
* Parse tracking details events from response
1670+
* Return list of items in such format:
1671+
* ['activity', 'deliverydate', 'deliverytime', 'deliverylocation']
1672+
*
1673+
* @param array $events
1674+
* @return array
1675+
*/
1676+
private function processTrackDetailsEvents(array $events)
1677+
{
1678+
$result = [];
1679+
/** @var \stdClass $event */
1680+
foreach ($events as $event) {
1681+
$item = [
1682+
'activity' => (string) $event->EventDescription,
1683+
'deliverydate' => null,
1684+
'deliverytime' => null,
1685+
'deliverylocation' => null
1686+
];
1687+
1688+
if (!empty($event->Timestamp)) {
1689+
$datetime = \DateTime::createFromFormat(\DateTime::ISO8601, $event->Timestamp);
1690+
$item['deliverydate'] = $datetime->format('Y-m-d');
1691+
$item['deliverytime'] = $datetime->format('H:i:s');
1692+
}
1693+
1694+
if (!empty($event->Address)) {
1695+
$item['deliverylocation'] = $this->getDeliveryAddress($event->Address);
1696+
}
1697+
1698+
$result[] = $item;
1699+
}
1700+
1701+
return $result;
1702+
}
1703+
1704+
/**
1705+
* Append error message to rate result instance
1706+
* @param string $trackingValue
1707+
* @param string $errorMessage
1708+
*/
1709+
private function appendTrackingError($trackingValue, $errorMessage)
1710+
{
1711+
$error = $this->_trackErrorFactory->create();
1712+
$error->setCarrier('fedex');
1713+
$error->setCarrierTitle($this->getConfigData('title'));
1714+
$error->setTracking($trackingValue);
1715+
$error->setErrorMessage($errorMessage);
1716+
$result = $this->getResult();
1717+
$result->append($error);
1718+
}
15971719
}

0 commit comments

Comments
 (0)