Skip to content

Commit ec1884b

Browse files
committed
MAGETWO-57460: [Backport] - Exception occurs when tracking shipment with invalid FedEx tracking number - for 2.1.x
- Refactored carrier tracking response handler - Updated tracking wsdl schema
1 parent 0ff8138 commit ec1884b

File tree

6 files changed

+1834
-550
lines changed

6 files changed

+1834
-550
lines changed

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

Lines changed: 222 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
/**
@@ -367,6 +379,9 @@ public function setRequest(RateRequest $request)
367379
*/
368380
public function getResult()
369381
{
382+
if (!$this->_result) {
383+
$this->_result = $this->_trackFactory->create();
384+
}
370385
return $this->_result;
371386
}
372387

@@ -1026,9 +1041,16 @@ protected function _getXMLTracking($tracking)
10261041
'AccountNumber' => $this->getConfigData('account'),
10271042
'MeterNumber' => $this->getConfigData('meter_number'),
10281043
],
1029-
'Version' => ['ServiceId' => 'trck', 'Major' => '5', 'Intermediate' => '0', 'Minor' => '0'],
1030-
'PackageIdentifier' => ['Type' => 'TRACKING_NUMBER_OR_DOORTAG', 'Value' => $tracking],
1031-
'IncludeDetailedScans' => 1,
1044+
'Version' => [
1045+
'ServiceId' => 'trck',
1046+
'Major' => self::$trackServiceVersion,
1047+
'Intermediate' => '0',
1048+
'Minor' => '0',
1049+
],
1050+
'SelectionDetails' => [
1051+
'PackageIdentifier' => ['Type' => 'TRACKING_NUMBER_OR_DOORTAG', 'Value' => $tracking],
1052+
],
1053+
'ProcessingOptions' => 'INCLUDE_DETAILED_SCANS'
10321054
];
10331055
$requestString = serialize($trackRequest);
10341056
$response = $this->_getCachedQuotes($requestString);
@@ -1055,114 +1077,48 @@ protected function _getXMLTracking($tracking)
10551077
/**
10561078
* Parse tracking response
10571079
*
1058-
* @param string[] $trackingValue
1080+
* @param string $trackingValue
10591081
* @param \stdClass $response
10601082
* @return void
1061-
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
1062-
* @SuppressWarnings(PHPMD.NPathComplexity)
1063-
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
10641083
*/
10651084
protected function _parseTrackingResponse($trackingValue, $response)
10661085
{
1067-
if (is_object($response)) {
1068-
if ($response->HighestSeverity == 'FAILURE' || $response->HighestSeverity == 'ERROR') {
1069-
$errorTitle = (string)$response->Notifications->Message;
1070-
} elseif (isset($response->TrackDetails)) {
1071-
$trackInfo = $response->TrackDetails;
1072-
$resultArray['status'] = (string)$trackInfo->StatusDescription;
1073-
$resultArray['service'] = (string)$trackInfo->ServiceInfo;
1074-
$timestamp = isset(
1075-
$trackInfo->EstimatedDeliveryTimestamp
1076-
) ? $trackInfo->EstimatedDeliveryTimestamp : $trackInfo->ActualDeliveryTimestamp;
1077-
$timestamp = strtotime((string)$timestamp);
1078-
if ($timestamp) {
1079-
$resultArray['deliverydate'] = date('Y-m-d', $timestamp);
1080-
$resultArray['deliverytime'] = date('H:i:s', $timestamp);
1081-
}
1082-
1083-
$deliveryLocation = isset(
1084-
$trackInfo->EstimatedDeliveryAddress
1085-
) ? $trackInfo->EstimatedDeliveryAddress : $trackInfo->ActualDeliveryAddress;
1086-
$deliveryLocationArray = [];
1087-
if (isset($deliveryLocation->City)) {
1088-
$deliveryLocationArray[] = (string)$deliveryLocation->City;
1089-
}
1090-
if (isset($deliveryLocation->StateOrProvinceCode)) {
1091-
$deliveryLocationArray[] = (string)$deliveryLocation->StateOrProvinceCode;
1092-
}
1093-
if (isset($deliveryLocation->CountryCode)) {
1094-
$deliveryLocationArray[] = (string)$deliveryLocation->CountryCode;
1095-
}
1096-
if ($deliveryLocationArray) {
1097-
$resultArray['deliverylocation'] = implode(', ', $deliveryLocationArray);
1098-
}
1099-
1100-
$resultArray['signedby'] = (string)$trackInfo->DeliverySignatureName;
1101-
$resultArray['shippeddate'] = date('Y-m-d', (int)$trackInfo->ShipTimestamp);
1102-
if (isset($trackInfo->PackageWeight) && isset($trackInfo->Units)) {
1103-
$weight = (string)$trackInfo->PackageWeight;
1104-
$unit = (string)$trackInfo->Units;
1105-
$resultArray['weight'] = "{$weight} {$unit}";
1106-
}
1107-
1108-
$packageProgress = [];
1109-
if (isset($trackInfo->Events)) {
1110-
$events = $trackInfo->Events;
1111-
if (isset($events->Address)) {
1112-
$events = [$events];
1113-
}
1114-
foreach ($events as $event) {
1115-
$tempArray = [];
1116-
$tempArray['activity'] = (string)$event->EventDescription;
1117-
$timestamp = strtotime((string)$event->Timestamp);
1118-
if ($timestamp) {
1119-
$tempArray['deliverydate'] = date('Y-m-d', $timestamp);
1120-
$tempArray['deliverytime'] = date('H:i:s', $timestamp);
1121-
}
1122-
if (isset($event->Address)) {
1123-
$addressArray = [];
1124-
$address = $event->Address;
1125-
if (isset($address->City)) {
1126-
$addressArray[] = (string)$address->City;
1127-
}
1128-
if (isset($address->StateOrProvinceCode)) {
1129-
$addressArray[] = (string)$address->StateOrProvinceCode;
1130-
}
1131-
if (isset($address->CountryCode)) {
1132-
$addressArray[] = (string)$address->CountryCode;
1133-
}
1134-
if ($addressArray) {
1135-
$tempArray['deliverylocation'] = implode(', ', $addressArray);
1136-
}
1137-
}
1138-
$packageProgress[] = $tempArray;
1139-
}
1140-
}
1141-
1142-
$resultArray['progressdetail'] = $packageProgress;
1143-
}
1086+
if (!is_object($response) || empty($response->HighestSeverity)) {
1087+
$this->appendTrackingError($trackingValue, __('Invalid response from carrier'));
1088+
return;
1089+
} else if (in_array($response->HighestSeverity, self::$trackingErrors)) {
1090+
$this->appendTrackingError($trackingValue, (string) $response->Notifications->Message);
1091+
return;
1092+
} else if (empty($response->CompletedTrackDetails) || empty($response->CompletedTrackDetails->TrackDetails)) {
1093+
$this->appendTrackingError($trackingValue, __('No available tracking items'));
1094+
return;
11441095
}
11451096

1146-
if (!$this->_result) {
1147-
$this->_result = $this->_trackFactory->create();
1097+
$trackInfo = $response->CompletedTrackDetails->TrackDetails;
1098+
1099+
// Fedex can return tracking details as single object instead array
1100+
if (is_object($trackInfo)) {
1101+
$trackInfo = [$trackInfo];
11481102
}
11491103

1150-
if (isset($resultArray)) {
1104+
$result = $this->getResult();
1105+
$carrierTitle = $this->getConfigData('title');
1106+
$counter = 0;
1107+
foreach ($trackInfo as $item) {
11511108
$tracking = $this->_trackStatusFactory->create();
1152-
$tracking->setCarrier('fedex');
1153-
$tracking->setCarrierTitle($this->getConfigData('title'));
1109+
$tracking->setCarrier(self::CODE);
1110+
$tracking->setCarrierTitle($carrierTitle);
11541111
$tracking->setTracking($trackingValue);
1155-
$tracking->addData($resultArray);
1156-
$this->_result->append($tracking);
1157-
} else {
1158-
$error = $this->_trackErrorFactory->create();
1159-
$error->setCarrier('fedex');
1160-
$error->setCarrierTitle($this->getConfigData('title'));
1161-
$error->setTracking($trackingValue);
1162-
$error->setErrorMessage(
1163-
$errorTitle ? $errorTitle : __('For some reason we can\'t retrieve tracking info right now.')
1112+
$tracking->addData($this->processTrackingDetails($item));
1113+
$result->append($tracking);
1114+
$counter ++;
1115+
}
1116+
1117+
// no available tracking details
1118+
if (!$counter) {
1119+
$this->appendTrackingError(
1120+
$trackingValue, __('For some reason we can\'t retrieve tracking info right now.')
11641121
);
1165-
$this->_result->append($error);
11661122
}
11671123
}
11681124

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

0 commit comments

Comments
 (0)