Upgrade USPS tracking API to v3
This commit is contained in:
parent
de93bfbeeb
commit
32aa0e6465
@ -46,6 +46,8 @@ $SETTINGS = [
|
||||
// MaxMind GeoIP2 database
|
||||
"geoip_database" => __DIR__ . "/resources/net.geoip/GeoLite2-City.mmdb",
|
||||
|
||||
"usps_client_id" => "",
|
||||
"usps_client_secret" => "",
|
||||
"usps_user_id" => "",
|
||||
"usps_source_id" => "",
|
||||
"ups_access_key" => "",
|
||||
|
@ -24,100 +24,107 @@ class Tracking_USPS {
|
||||
$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) . "\"");
|
||||
$resp = USPSAPIs::getAPIRequest("tracking/v3/tracking/$code?expand=DETAIL");
|
||||
$resp = str_replace("<SUP>reg;</SUP>", "®", $resp);
|
||||
$resp = str_replace("<SUP>®</SUP>", "®", $resp);
|
||||
$json = json_decode($resp, true);
|
||||
|
||||
if (!empty($json["error"])) {
|
||||
if (!empty($json["error"]["errors"]) && $json["error"]["errors"][0]["code"] == "150001") {
|
||||
// Tracking number not found
|
||||
throw new TrackingException(str_replace("12: ", "", $json["error"]["errors"][0]["title"]));
|
||||
}
|
||||
if (!empty($json["error"]["message"])) {
|
||||
throw new TrackingException("The USPS tracking system is having problems: \"" . trim($json["error"]["message"]) . "\"");
|
||||
}
|
||||
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>®</SUP>", "®", (string) $xml->TrackInfo->Error->Description));
|
||||
}
|
||||
$trackinfo = $json;
|
||||
} catch (TrackingException $ex) {
|
||||
throw $ex;
|
||||
} catch (Exception $ex) {
|
||||
throw new TrackingException("There was a server problem. This code cannot be tracked right now.");
|
||||
throw new TrackingException("There was a server error. This code cannot be tracked right now. Try again later.");
|
||||
}
|
||||
|
||||
$info = new TrackingInfo();
|
||||
|
||||
try {
|
||||
$info->setCode($trackinfo->attributes()["ID"]);
|
||||
$info->setCode($trackinfo["trackingNumber"]);
|
||||
} 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->setService(new Service((string) $trackinfo["mailClass"], (string) $trackinfo["mailClass"]));
|
||||
$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 status
|
||||
if (count($trackinfo["trackingEvents"]) > 0) {
|
||||
$index = 0;
|
||||
$evt = $trackinfo["trackingEvents"][$index];
|
||||
$current_status = new TrackingEntry(
|
||||
TrackingStatus::USPSEventCodeToStatus($evt["eventCode"]),
|
||||
($evt["eventType"] ?? "Unknown") . (TrackingStatus::USPSEventCodeToStatus($evt["eventCode"]) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . $evt["eventCode"] : ""),
|
||||
date("Y-m-d\TH:i:s", strtotime($evt["eventTimestamp"])),
|
||||
null,
|
||||
TrackingStatus::isUSPSEventCodeContainerScan($evt["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 ?? "";
|
||||
$current_location = new Location();
|
||||
$current_location->city = (string) $evt["eventCity"] ?? "";
|
||||
$current_location->state = (string) $evt["eventState"] ?? "";
|
||||
$current_location->zip = (string) $evt["eventZIP"] ?? "";
|
||||
$current_location->country = (string) $evt["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];
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$current_status = new TrackingEntry(
|
||||
TrackingStatus::TRACKING_STATUS_PRE_TRANSIT,
|
||||
($trackinfo["statusSummary"] ?? "Unknown"),
|
||||
date("Y-m-d\TH:i:s", strtotime("now")),
|
||||
null,
|
||||
false
|
||||
);
|
||||
$current_location = new Location();
|
||||
}
|
||||
|
||||
$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 ?? "";
|
||||
$from->city = (string) $trackinfo["originCity"] ?? "";
|
||||
$from->state = (string) $trackinfo["originState"] ?? "";
|
||||
$from->zip = (string) $trackinfo["originZIP"] ?? "";
|
||||
$from->country = (string) (isset($trackinfo["originCountry"]) ? $trackinfo["originCountry"] : "");
|
||||
|
||||
$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 ?? "";
|
||||
$to->city = (string) $trackinfo["destinationCity"] ?? "";
|
||||
$to->state = (string) $trackinfo["destinationState"] ?? "";
|
||||
$to->zip = (string) $trackinfo["destinationZIP"] ?? "";
|
||||
$to->country = (string) (isset($trackinfo["destinationCountry"]) ? $trackinfo["destinationCountry"] : "");
|
||||
|
||||
$info->setTo($to);
|
||||
|
||||
for ($i = 0; $i < count($trackinfo->TrackDetail); $i++) {
|
||||
$history = $trackinfo->TrackDetail[$i];
|
||||
for ($i = 0; $i < count($trackinfo["trackingEvents"]); $i++) {
|
||||
$history = $trackinfo["trackingEvents"][$i];
|
||||
$location = new Location();
|
||||
$location->city = (string) $history->EventCity ?? "";
|
||||
$location->state = (string) $history->EventState ?? "";
|
||||
$location->zip = (string) $history->EventZIPCode ?? "";
|
||||
$location->country = (string) $history->EventCountry ?? "";
|
||||
$location->city = (string) $history["eventCity"] ?? "";
|
||||
$location->state = (string) $history["eventState"] ?? "";
|
||||
$location->zip = (string) $history["eventZIP"] ?? "";
|
||||
$location->country = (string) $history["eventCountry"] ?? "";
|
||||
/*
|
||||
* Fill in state from list above when it's missing from the API response
|
||||
*/
|
||||
@ -126,25 +133,14 @@ class Tracking_USPS {
|
||||
$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,
|
||||
TrackingStatus::USPSEventCodeToStatus((string) $history["eventCode"]),
|
||||
$history["eventType"] . (TrackingStatus::USPSEventCodeToStatus((string) $history["eventCode"]) == TrackingStatus::TRACKING_STATUS_UNKNOWN ? " " . (string) $history["eventCode"] : ""),
|
||||
$history["eventTimestamp"],
|
||||
$location,
|
||||
TrackingStatus::isUSPSEventCodeContainerScan((string) $history->EventCode)));
|
||||
TrackingStatus::isUSPSEventCodeContainerScan((string) $history["eventCode"])));
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
}
|
||||
|
150
lib/Tracking_USPS.lib.v2.php
Normal file
150
lib/Tracking_USPS.lib.v2.php
Normal file
@ -0,0 +1,150 @@
|
||||
<?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>®</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;
|
||||
}
|
||||
|
||||
}
|
51
lib/USPSAPIs.lib.php
Normal file
51
lib/USPSAPIs.lib.php
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license
|
||||
* Click nbfs://nbhost/SystemFileSystem/Templates/Scripting/EmptyPHP.php to edit this template
|
||||
*/
|
||||
|
||||
class USPSAPIs {
|
||||
|
||||
public static function getBearerToken(bool $force = false): string {
|
||||
global $memcache;
|
||||
$clientid = env("usps_client_id");
|
||||
$clientsecret = env("usps_client_secret");
|
||||
|
||||
if (!$force && $memcache->get("logistics.tracking.usps_bearer_token") != false) {
|
||||
return $memcache->get("logistics.tracking.usps_bearer_token");
|
||||
}
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, 'https://api.usps.com/oauth2/v3/token');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, [
|
||||
'Content-Type: application/json',
|
||||
]);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\"client_id\": \"$clientid\", \"client_secret\": \"$clientsecret\", \"grant_type\": \"client_credentials\", \"scope\": \"addresses international-prices pickup tracking scan-forms locations prices\"}");
|
||||
$response = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
$data = json_decode($response, true);
|
||||
$memcache->set("logistics.tracking.usps_bearer_token", $data["access_token"], ($data["expires_in"] * 1) - 120);
|
||||
return $data["access_token"];
|
||||
}
|
||||
|
||||
public static function getAPIRequest($endpoint) {
|
||||
$headers = [
|
||||
"Authorization: Bearer " . USPSAPIs::getBearerToken(true),
|
||||
'Content-Type: application/json'
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 45);
|
||||
curl_setopt($ch, CURLOPT_URL, "https://api.usps.com/" . $endpoint);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
||||
$response = curl_exec($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user