You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

151 lines
7.1 KiB
PHP

<?php
class Tracking_USPS {
/**
* Sometimes the API returns a city but no state or ZIP. This is a lookup table of city => state
* so the API can return a more complete response.
*/
public const STATELESS_CITIES = [
"LOS ANGELES" => "CA",
"SAN FRANCISCO" => "CA",
"NEW YORK" => "NY",
"CHICAGO" => "IL",
"MIAMI" => "FL"
];
/**
*
* @param string $code
* @return \TrackingInfo
* @throws TrackingException
*/
public static function track(string $code, string $carrier = ""): TrackingInfo {
$barcode = new TrackingBarcode($code);
try {
$track = new \SimpleXMLElement("<TrackFieldRequest></TrackFieldRequest>");
$track->addAttribute('USERID', env("usps_user_id"));
$track->addChild('Revision', '1');
$track->addChild('ClientIp', $_SERVER['REMOTE_ADDR']);
$track->addChild('SourceId', env("usps_source_id", "selfhosted-opensource-default-value.netsyms.net"));
$pack = $track->addChild('TrackID');
$pack->addAttribute('ID', $barcode->getSanitized());
$url = 'https://secure.shippingapis.com/ShippingApi.dll?API=TrackV2&XML=' . $track->asXML();
$xml = simplexml_load_file($url);
if ($xml->getName() == "Error") {
if (!empty($xml->Description)) {
throw new TrackingException("The USPS tracking system is having problems: \"" . trim($xml->Description) . "\"");
}
throw new TrackingException("The USPS tracking system is having problems. Try again later.");
}
if (!empty($xml->TrackInfo)) {
$trackinfo = $xml->TrackInfo;
}
if (!empty($xml->TrackInfo->Error)) {
throw new TrackingException(str_replace("<SUP>&reg;</SUP>", "®", (string) $xml->TrackInfo->Error->Description));
}
} catch (TrackingException $ex) {
throw $ex;
} catch (Exception $ex) {
throw new TrackingException("There was a server problem. This code cannot be tracked right now.");
}
$info = new TrackingInfo();
try {
$info->setCode($trackinfo->attributes()["ID"]);
} catch (Exception $ex) {
throw new TrackingException("The USPS tracking system returned an invalid response. Try again later.");
}
$info->setCarrier("usps");
$info->setService(new Service((string) $trackinfo->ClassofMailCode, (string) $trackinfo->Class));
$info->setCarrierAttributionText(CarrierAssets::getAttribution(Carriers::getCarrierCode($info->getCarrier())));
$info->setCarrierLogo(CarrierAssets::getLogo(Carriers::getCarrierCode($info->getCarrier())));
$current_status = new TrackingEntry(
TrackingStatus::USPSEventCodeToStatus($trackinfo->TrackSummary->EventCode),
($trackinfo->StatusSummary ?? "Unknown") . (TrackingStatus::USPSEventCodeToStatus($trackinfo->TrackSummary->EventCode) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . $trackinfo->TrackSummary->EventCode : ""),
$trackinfo->TrackSummary->EventDate . " " . $trackinfo->TrackSummary->EventTime,
null,
TrackingStatus::isUSPSEventCodeContainerScan($trackinfo->TrackSummary->EventCode)
);
$current_location = new Location();
$current_location->city = (string) $trackinfo->TrackSummary->EventCity ?? "";
$current_location->state = (string) $trackinfo->TrackSummary->EventState ?? "";
$current_location->zip = (string) $trackinfo->TrackSummary->EventZIPCode ?? "";
$current_location->country = (string) $trackinfo->TrackSummary->EventCountry ?? "";
/*
* Fill in state from list above when it's missing from the API response
*/
if ($current_location->state == "" && $current_location->zip == "") {
if (array_key_exists(strtoupper($current_location->city), self::STATELESS_CITIES)) {
$current_location->state = self::STATELESS_CITIES[$current_location->city];
}
}
$current_status->setLocation($current_location);
$info->setCurrentStatus($current_status);
// USPS doesn't put the latest entry in the history
$info->appendHistoryEntry($current_status);
$from = new Location();
$from->city = (string) $trackinfo->OriginCity ?? "";
$from->state = (string) $trackinfo->OriginState ?? "";
$from->zip = (string) $trackinfo->OriginZip ?? "";
$from->country = (string) $trackinfo->OriginCountryCode ?? "";
$info->setFrom($from);
$to = new Location();
$to->city = (string) $trackinfo->DestinationCity ?? "";
$to->state = (string) $trackinfo->DestinationState ?? "";
$to->zip = (string) $trackinfo->DestinationZip ?? "";
$to->country = (string) $trackinfo->DestinationCountryCode ?? "";
$info->setTo($to);
for ($i = 0; $i < count($trackinfo->TrackDetail); $i++) {
$history = $trackinfo->TrackDetail[$i];
$location = new Location();
$location->city = (string) $history->EventCity ?? "";
$location->state = (string) $history->EventState ?? "";
$location->zip = (string) $history->EventZIPCode ?? "";
$location->country = (string) $history->EventCountry ?? "";
/*
* Fill in state from list above when it's missing from the API response
*/
if ($location->state == "" && $location->zip == "") {
if (array_key_exists(strtoupper($location->city), self::STATELESS_CITIES)) {
$location->state = self::STATELESS_CITIES[$location->city];
}
}
if ((empty($history->EventDate) || empty($history->EventTime)) && $i < count($trackinfo->TrackDetail) - 1) {
// If there's no date/time for some reason (yes this happens sometimes apparently),
// just make it 60 seconds after the previous event.
// This way it'll be in the correct order if the events are displayed
// after being sorted by date/time.
// Because events are ordered latest first, we get $i+1 to get the previous event.
$datetime = date("Y-m-d H:i:s", strtotime($trackinfo->TrackDetail[$i + 1]->EventDate . " " . $trackinfo->TrackDetail[$i + 1]->EventTime) + 60);
} else {
$datetime = $history->EventDate . " " . $history->EventTime;
}
$info->appendHistoryEntry(new TrackingEntry(
TrackingStatus::USPSEventCodeToStatus((string) $history->EventCode),
((string) $history->Event) . (TrackingStatus::USPSEventCodeToStatus((string) $history->EventCode) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . (string) $history->EventCode : ""),
$datetime,
$location,
TrackingStatus::isUSPSEventCodeContainerScan((string) $history->EventCode)));
}
return $info;
}
}