diff --git a/apiconfig.php b/apiconfig.php index e385158..14d6615 100644 --- a/apiconfig.php +++ b/apiconfig.php @@ -41,6 +41,13 @@ $APIS = [ "nocache (optional)" => "" ] ], + "logistics/tracking" => [ + "load" => "logistics.tracking.php", + "vars" => [ + "code" => "", + "carrier (optional)" => "" + ] + ], "net/contactspam" => [ "load" => "net.contactspam.php", "vars" => [ diff --git a/composer.json b/composer.json index 27348a9..c18fa64 100644 --- a/composer.json +++ b/composer.json @@ -2,6 +2,8 @@ "require": { "catfan/medoo": "^2.0", "io-developer/php-whois": "^4.0", - "geoip2/geoip2": "^2.11" + "geoip2/geoip2": "^2.11", + "shippo/shippo-php": "^1.4", + "easypost/easypost-php": "^3.5" } } diff --git a/composer.lock b/composer.lock index 8bd0eb4..f58ba4d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "38ec51243dee8ea8089b161e1f20d30f", + "content-hash": "e99006318711be4bc8b61a3e909a24da", "packages": [ { "name": "catfan/medoo", @@ -154,6 +154,60 @@ ], "time": "2021-01-12T12:10:35+00:00" }, + { + "name": "easypost/easypost-php", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/EasyPost/easypost-php.git", + "reference": "e225cfeb7ee2459a32b3efaa710358ac3bb116c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/EasyPost/easypost-php/zipball/e225cfeb7ee2459a32b3efaa710358ac3bb116c3", + "reference": "e225cfeb7ee2459a32b3efaa710358ac3bb116c3", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.16", + "overtrue/phplint": "^1.2", + "phpunit/phpunit": "^8" + }, + "type": "library", + "autoload": { + "psr-0": { + "EasyPost": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "EasyPost Developers", + "email": "oss@easypost.com", + "homepage": "https://www.easypost.com" + } + ], + "description": "EasyPost Shipping API Client Library for PHP", + "homepage": "https://github.com/EasyPost/easypost-php", + "keywords": [ + "api", + "easypost", + "shipping" + ], + "support": { + "issues": "https://github.com/EasyPost/easypost-php/issues", + "source": "https://github.com/EasyPost/easypost-php/tree/v3.5.0" + }, + "time": "2021-04-06T02:57:46+00:00" + }, { "name": "geoip2/geoip2", "version": "v2.11.0", @@ -388,6 +442,64 @@ }, "time": "2020-11-02T17:00:53+00:00" }, + { + "name": "shippo/shippo-php", + "version": "v1.4.4", + "source": { + "type": "git", + "url": "https://github.com/goshippo/shippo-php-client.git", + "reference": "a2edfa7766f322ef9b719710a5b90df089a4008e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/goshippo/shippo-php-client/zipball/a2edfa7766f322ef9b719710a5b90df089a4008e", + "reference": "a2edfa7766f322ef9b719710a5b90df089a4008e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "php": ">=5.2" + }, + "require-dev": { + "phpunit/phpunit": "5.5.*" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/Shippo/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Shippo & Contributors", + "homepage": "https://goshippo.com/" + } + ], + "description": "A PHP library for connecting with multiple carriers (FedEx, UPS, USPS) using Shippo.", + "homepage": "https://goshippo.com/", + "keywords": [ + "FedEx", + "Uber", + "address", + "dhl", + "shipping", + "shyp", + "tracking", + "ups", + "usps" + ], + "support": { + "issues": "https://github.com/goshippo/shippo-php-client/issues", + "source": "https://github.com/goshippo/shippo-php-client/tree/v1.4.4" + }, + "time": "2020-09-14T13:26:17+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.22.1", diff --git a/endpoints/logistics.tracking.php b/endpoints/logistics.tracking.php new file mode 100644 index 0000000..cd32c23 --- /dev/null +++ b/endpoints/logistics.tracking.php @@ -0,0 +1,18 @@ +getMessage(), "ERROR"); +} + +if (is_null($trackinginfo)) { + sendJsonResp("Could not find any results for that tracking code.", "ERROR"); +} +exitWithJson($trackinginfo->toObject()); \ No newline at end of file diff --git a/env.sample.php b/env.sample.php index c1f8d2c..03488e2 100644 --- a/env.sample.php +++ b/env.sample.php @@ -42,5 +42,12 @@ $SETTINGS = [ "mapquest_key" => "", // MaxMind GeoIP2 database - "geoip_database" => __DIR__ . "/resources/net.geoip/GeoLite2-City.mmdb" + "geoip_database" => __DIR__ . "/resources/net.geoip/GeoLite2-City.mmdb", + + "usps_user_id" => "", + "usps_source_id" => "", + "ups_access_key" => "", + "ups_user_account" => "", + "ups_password" => "", + "shippo_key" => "", ]; \ No newline at end of file diff --git a/lib/Carriers.lib.php b/lib/Carriers.lib.php new file mode 100644 index 0000000..4af3e35 --- /dev/null +++ b/lib/Carriers.lib.php @@ -0,0 +1,187 @@ + "ups_mi_datamatrix", + "pattern" => "/^([^\\t]*\\t[^\\t]*){13}$/" + ], + [ + "carrier" => "fedex_pdf417", + "pattern" => "/.*\\x1D.*\\x1D.*\\x1D.*/" // look for the GS (group seperator) multiple times + ], + [ + "carrier" => "helena_express", + "pattern" => "/^[HLN|HEX][0-9]+$/" + ], + [ + "carrier" => "helena_express", + "pattern" => "/^406[0-9]{6}$/" + ], + [ + "carrier" => "usps", + "pattern" => "/^[0-9]{20,40}$/" + ], + [ + "carrier" => "usps", + "pattern" => "/^82[0-9]{8}$/" + ], + [ + "carrier" => "usps", + "pattern" => "/^E\D{1}\d{9}\D{2}$|^9\d{15,21}$/" + ], + [ + "carrier" => "usps", + "pattern" => "/^91[0-9]+$/" + ], + [ + "carrier" => "usps", + "pattern" => "/^[A-Z]{2}[0-9]{9}[A-Z]{2}$/" + ], + [ + "carrier" => "usps", + "pattern" => "/^420[0-9]{5}[0-9]+$/" + ], + [ + "carrier" => "ups", + "pattern" => "/\b(1Z ?[0-9A-Z]{3} ?[0-9A-Z]{3} ?[0-9A-Z]{2} ?[0-9A-Z]{4} ?[0-9A-Z]{3} ?[0-9A-Z]|[\dT]\d\d\d ?\d\d\d\d ?\d\d\d)\b/" + ], + [ + "carrier" => "ups", + "pattern" => "/^[KJ]{1}[0-9]{10}$/" + ], + [ + "carrier" => "fedex", + "pattern" => "/(\b96\d{20}\b)|(\b\d{15}\b)|(\b\d{12}\b)/" + ], + [ + "carrier" => "fedex", + "pattern" => "/\b((98\d\d\d\d\d?\d\d\d\d|98\d\d) ?\d\d\d\d ?\d\d\d\d( ?\d\d\d)?)\b/" + ], + [ + "carrier" => "fedex", + "pattern" => "/^[0-9]{15}$/" + ], + [ + "carrier" => "dhl_express", + "pattern" => "/^[0-9]{10}$/" + ], + [ + "carrier" => "dhl_express", + "pattern" => "/^J?JD[0-9]{13,18}$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^[LR][A-Z][0-9]{9}DE$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^CNAOG[0-9]{10}$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^SGKEN[0-9]{10}$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^SGAGS[0-9]{6}$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^[0-9]{16}$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^[0-9]{22}$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^GM[0-9]{16,18}$/" + ], + [ + "carrier" => "dhl_ecommerce", + "pattern" => "/^A[A-Z0-9]000[0-9]{15}$/" + ], + [ + "carrier" => "lasership", + "pattern" => "/^LX[0-9]+$/" + ], + [ + "carrier" => "lasership", + "pattern" => "/^1LS[0-9]+$/" + ], + ]; + + const CARRIERS = [ + "usps" => [ + "code" => "usps", + "name" => "USPS" + ], + "ups" => [ + "code" => "ups", + "name" => "UPS" + ], + "ups_mi_datamatrix" => [ + "code" => "usps", + "name" => "USPS" + ], + "fedex" => [ + "code" => "fedex", + "name" => "FedEx" + ], + "fedex_pdf417" => [ + "code" => "fedex", + "name" => "FedEx" + ], + "dhl_express" => [ + "code" => "dhl_express", + "name" => "DHL Express" + ], + "dhl_ecommerce" => [ + "code" => "dhl_ecommerce", + "name" => "DHL eCommerce" + ], + "lasership" => [ + "code" => "lasership", + "name" => "LaserShip" + ], + "helena_express" => [ + "code" => "helena_express", + "name" => "Helena Express" + ] + ]; + + public static function getCarrierName(string $carrier): string { + // scan by internal ID + foreach (Carriers::CARRIERS as $id => $info) { + if ($id == $carrier) { + return $info["name"]; + } + } + // scan by code + foreach (Carriers::CARRIERS as $id => $info) { + if (strtolower($info["code"]) == strtolower($carrier)) { + return $info["name"]; + } + } + return ""; + } + + public static function getCarrierCode(string $carrier): string { + // scan by internal ID + foreach (Carriers::CARRIERS as $id => $info) { + if ($id == $carrier) { + return $info["code"]; + } + } + // scan by name + foreach (Carriers::CARRIERS as $id => $info) { + if (strtolower($info["name"]) == strtolower($carrier)) { + return $info["code"]; + } + } + return ""; + } + +} diff --git a/lib/Location.lib.php b/lib/Location.lib.php new file mode 100644 index 0000000..225c5a6 --- /dev/null +++ b/lib/Location.lib.php @@ -0,0 +1,29 @@ +id = $id; + $this->name = $name; + } +} \ No newline at end of file diff --git a/lib/Tracking.lib.php b/lib/Tracking.lib.php new file mode 100644 index 0000000..d7b1887 --- /dev/null +++ b/lib/Tracking.lib.php @@ -0,0 +1,37 @@ +getCarrier(); + } + + switch ($carriercode) { + case "ups_mi_datamatrix": + return Tracking_UPS_MailInnovations_DataMatrix::track($code); + case "fedex_pdf417": + return Tracking_FedEx_PDF417::track($code); + case "helena_express": + return Tracking_HelenaExpress::track($barcode->getSanitized()); + case "usps": + return Tracking_USPS::track($barcode->getSanitized()); + case "ups": + return Tracking_UPS::track($barcode->getSanitized()); + default: + return Tracking_Shippo::track($barcode->getSanitized()); + } + } + +} diff --git a/lib/TrackingBarcode.lib.php b/lib/TrackingBarcode.lib.php new file mode 100644 index 0000000..09c25b3 --- /dev/null +++ b/lib/TrackingBarcode.lib.php @@ -0,0 +1,58 @@ +code = $code; + } + + /** + * Trim and clean up the code so it's all caps and only A-Z and 0-9. + * Does not change the actual barcode represented by this object. + * @param string $code + * @return string the sanitized code + */ + public function getSanitized(): string { + $code = strtoupper($this->code); + $code = trim($code); + $code = preg_replace("/[^0-9A-Z]/", "", $code); + return $code; + } + + /** + * Guess the carrier for the tracking code. + * @return string An ID string representing the carrier. Some codes that + * require extra processing before use will return a non-standard ID, use + * Carriers::getCarrierName()/getCarrierCode() to resolve a standard ID code. + */ + public function getCarrier(): string { + $carrier = ""; + foreach (Carriers::CARRIER_REGEXES as $p) { + if (preg_match($p["pattern"], strtoupper($this->code))) { + $carrier = $p["carrier"]; + break; + } + } + if ($carrier == "") { + // check again but strip out anything extra this time + // makes it work with codes containing nonprintable stuff like the GS (group separator, %1D) char + foreach (Carriers::CARRIER_REGEXES as $p) { + if (preg_match($p["pattern"], $this->getSanitized())) { + $carrier = $p["carrier"]; + break; + } + } + if ($carrier == "") { + throw new TrackingException("Unrecognized tracking code."); + } + } + return $carrier; + } + + public function getCode(): string { + return $this->code; + } + +} diff --git a/lib/TrackingEntry.lib.php b/lib/TrackingEntry.lib.php new file mode 100644 index 0000000..430610b --- /dev/null +++ b/lib/TrackingEntry.lib.php @@ -0,0 +1,47 @@ +status = $status; + $this->details = $details; + $this->datetime = date("Y-m-d\TH:i:s", strtotime($datetime)); + $this->location = $location; + $this->iscontainerscan = $iscontainerscan; + } + + public function toString(): string { + return $this->details; + } + + public function getTimestamp(): int { + return strtotime($this->datetime); + } + + public function getDateTime(string $format = "Y-m-d\TH:i:s"): string { + return date($format, strtotime($this->datetime)); + } + + public function getLocation() { + return $this->location; + } + + public function setLocation(Location $location) { + $this->location = $location; + } + + public function getStatus() { + return $this->status; + } + + public function isContainerScan() { + return $this->iscontainerscan; + } + +} \ No newline at end of file diff --git a/lib/TrackingException.lib.php b/lib/TrackingException.lib.php new file mode 100644 index 0000000..a8265e6 --- /dev/null +++ b/lib/TrackingException.lib.php @@ -0,0 +1,5 @@ +code = $code; + $this->carrier = $carrier; + $this->to = $to; + $this->from = $from; + $this->current_status = $current_status; + $this->history = $history; + } + + public function setCode(string $code) { + $this->code = $code; + } + + public function getCode(): string { + return $this->code; + } + + public function setCarrier(string $carrierid) { + $this->carrier = $carrierid; + } + + public function getCarrier() { + return $this->carrier; + } + + public function setService(Service $service) { + $this->service = $service; + } + + public function getService() { + return $this->service; + } + + public function setTo(Location $to) { + $this->to = $to; + } + + public function getTo() { + return $this->to; + } + + public function setFrom(Location $from) { + $this->from = $from; + } + + public function getFrom() { + return $this->from; + } + + public function setCurrentStatus(TrackingEntry $status) { + $this->current_status = $status; + } + + public function getCurrentStatus() { + return $this->current_status; + } + + public function getHistory() { + return $this->history; + } + + public function appendHistoryEntry(TrackingEntry $history) { + $this->history[] = $history; + } + + public function toObject() { + $output = [ + "status" => "OK", + "code" => $this->getCode(), + "carrier" => [ + "code" => Carriers::getCarrierCode($this->getCarrier()), + "name" => Carriers::getCarrierName($this->getCarrier()) + ], + "service" => [ + "id" => $this->service->id, + "name" => $this->service->name + ], + "addresses" => [ + "from" => [ + "street" => "", + "city" => "Unknown", + "state" => "", + "zip" => "", + "country" => "" + ], + "to" => [ + "street" => "", + "city" => "Unknown", + "state" => "", + "zip" => "", + "country" => "" + ] + ], + "current" => [ + "status" => "UNKNOWN", + "details" => "No tracking information found.", + "date" => time(), + "location" => [ + "street" => "", + "city" => "", + "state" => "", + "zip" => "", + "country" => "" + ] + ], + "history" => [ + ] + ]; + + if (!is_null($this->getCurrentStatus())) { + $output["current"] = [ + "status" => TrackingStatus::statusToString($this->getCurrentStatus()->getStatus()), + "details" => $this->getCurrentStatus()->toString(), + "datetime" => $this->getCurrentStatus()->getDateTime(), + "timestamp" => $this->getCurrentStatus()->getTimestamp(), + "containerscan" => $this->getCurrentStatus()->isContainerScan(), + "location" => [ + "street" => "", + "city" => "Unknown", + "state" => "", + "zip" => "", + "country" => "" + ] + ]; + + if (!empty($this->getCurrentStatus()->getLocation())) { + $output["current"]["location"] = [ + "street" => $this->getCurrentStatus()->getLocation()->street, + "city" => $this->getCurrentStatus()->getLocation()->city, + "state" => $this->getCurrentStatus()->getLocation()->state, + "zip" => $this->getCurrentStatus()->getLocation()->zip, + "country" => $this->getCurrentStatus()->getLocation()->country + ]; + } + } + + if (!is_null($this->getFrom())) { + $output["addresses"]["from"] = [ + "street" => $this->getFrom()->street, + "city" => $this->getFrom()->city, + "state" => $this->getFrom()->state, + "zip" => $this->getFrom()->zip, + "country" => $this->getFrom()->country + ]; + } + + if (!is_null($this->getTo())) { + $output["addresses"]["to"] = [ + "street" => $this->getTo()->street, + "city" => $this->getTo()->city, + "state" => $this->getTo()->state, + "zip" => $this->getTo()->zip, + "country" => $this->getTo()->country + ]; + } + + if (!is_null($this->getHistory())) { + foreach ($this->getHistory() as $history) { + $output["history"][] = [ + "status" => TrackingStatus::statusToString($history->getStatus()), + "details" => $history->toString(), + "datetime" => $history->getDateTime(), + "timestamp" => $history->getTimestamp(), + "containerscan" => $history->isContainerScan(), + "location" => [ + "street" => $history->getLocation()->street, + "city" => $history->getLocation()->city, + "state" => $history->getLocation()->state, + "zip" => $history->getLocation()->zip, + "country" => $history->getLocation()->country + ], + ]; + } + } + + return $output; + } + +} diff --git a/lib/TrackingStatus.lib.php b/lib/TrackingStatus.lib.php new file mode 100644 index 0000000..094a827 --- /dev/null +++ b/lib/TrackingStatus.lib.php @@ -0,0 +1,250 @@ +getTo(); + if (is_null($to)) { + $to = new Location(); + } + $to->street = $codeparts[13]; + $to->city = $codeparts[14]; + $to->state = $codeparts[15]; + $to->country = (new League\ISO3166\ISO3166)->numeric($codeparts[3])['alpha2']; + $info->setTo($to); + + return $info; + } + +} diff --git a/lib/Tracking_HelenaExpress.lib.php b/lib/Tracking_HelenaExpress.lib.php new file mode 100644 index 0000000..ec605d4 --- /dev/null +++ b/lib/Tracking_HelenaExpress.lib.php @@ -0,0 +1,51 @@ +getCode())); + } catch (Exception $ex) { + throw new TrackingException("There was a server problem. This code cannot be tracked right now."); + } + + if (empty($status->events) && empty($status->info->carrier)) { + throw new TrackingException("Unknown tracking code (best guess: " . Carriers::getCarrierName($barcode->getCarrier()) . ")."); + } + + $info = new TrackingInfo(); + + $info->setCode($barcode->getCode()); + $info->setCarrier($status->info->carrier); + + if (!empty($status->events)) { + $current_status = new TrackingEntry( + TrackingStatus::stringToStatus($status->events[0]->eventtype), + $status->events[0]->text, + $status->events[0]->timestring + ); + + $info->setCurrentStatus($current_status); + } + + + foreach ($status->events as $event) { + $info->appendHistoryEntry(new TrackingEntry(TrackingStatus::stringToStatus($event->eventtype), $event->text, $event->timestring)); + } + + return $info; + } + +} diff --git a/lib/Tracking_Shippo.lib.php b/lib/Tracking_Shippo.lib.php new file mode 100644 index 0000000..6809fd0 --- /dev/null +++ b/lib/Tracking_Shippo.lib.php @@ -0,0 +1,86 @@ + $code, + "carrier" => empty($carrier) ? Carriers::getCarrierCode($barcode->getCarrier()) : $carrier + ]); + } catch (Exception $ex) { + throw new TrackingException("There was a server problem. This code cannot be tracked right now."); + } + + if (empty($status->tracking_status) && empty($status->address_to) && empty($status->address_from)) { + throw new TrackingException("Unknown tracking code (best guess: " . Carriers::getCarrierName($barcode->getCarrier()) . ")."); + } + + $info = new TrackingInfo(); + + $info->setCode($barcode->getCode()); + $info->setCarrier($barcode->getCarrier()); + $info->setService(new Service($status->servicelevel->token, $status->servicelevel->name)); + + if (!empty($status->tracking_status)) { + $current_status = new TrackingEntry( + TrackingStatus::stringToStatus($status->tracking_status->status), + $status->tracking_status->status_details, + $status->tracking_status->status_date + ); + + if (!empty($status->tracking_status->location)) { + $current_location = new Location(); + $current_location->city = $status->tracking_status->location->city; + $current_location->state = $status->tracking_status->location->state; + $current_location->zip = $status->tracking_status->location->zip; + $current_location->country = $status->tracking_status->location->country; + $current_status->setLocation($current_location); + } + + $info->setCurrentStatus($current_status); + } + + if (!empty($status->address_from)) { + $from = new Location(); + $from->city = empty($status->address_from->city) ? "" : $status->address_from->city; + $from->state = empty($status->address_from->state) ? "" : $status->address_from->state; + $from->zip = empty($status->address_from->zip) ? "" : $status->address_from->zip; + $from->country = empty($status->address_from->country) ? "" : $status->address_from->country; + + $info->setFrom($from); + } + + if (!empty($status->address_to)) { + $to = new Location(); + $to->city = empty($status->address_to->city) ? "" : $status->address_to->city; + $to->state = empty($status->address_to->state) ? "" : $status->address_to->state; + $to->zip = empty($status->address_to->zip) ? "" : $status->address_to->zip; + $to->country = empty($status->address_to->country) ? "" : $status->address_to->country; + + $info->setTo($to); + } + + foreach ($status->tracking_history as $history) { + $location = new Location(); + $location->city = $history->location["city"]; + $location->state = $history->location["state"]; + $location->zip = $history->location["zip"]; + $location->country = $history->location["country"]; + $info->appendHistoryEntry(new TrackingEntry(TrackingStatus::stringToStatus($history->status), $history->status_details, $history->status_date, $location)); + } + + return $info; + } + +} diff --git a/lib/Tracking_UPS.lib.php b/lib/Tracking_UPS.lib.php new file mode 100644 index 0000000..d028fdf --- /dev/null +++ b/lib/Tracking_UPS.lib.php @@ -0,0 +1,149 @@ + [ + "UsernameToken" => [ + "Username" => env("ups_user_account"), + "Password" => env("ups_password") + ], + "UPSServiceAccessToken" => [ + "AccessLicenseNumber" => env("ups_access_key") + ] + ], + "TrackRequest" => [ + "Request" => [ + "RequestAction" => "Track", + "RequestOption" => "activity" + ], + "InquiryNumber" => $code + ] + ]; + $headers = [ + 'Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept', + 'Access-Control-Allow-Methods: POST', + 'Access-Control-Allow-Origin: *', + '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://wwwcie.ups.com/rest/Track"); // TEST + curl_setopt($ch, CURLOPT_URL, "https://onlinetools.ups.com/rest/Track"); // PROD + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($trackrequest)); + $response = curl_exec($ch); + + // CHECK TO SEE IF WE GOT AN ERROR + // IF SO, FORMAT IT LIKE THIS ::28::Operation timed out afterseconds + if ((curl_errno($ch)) && (curl_errno($ch) != 0)) { + throw new TrackingException(curl_error($ch)); + } + + $result = json_decode($response, true); + + if (!empty($result["response"])) { + // Should have trackResponse instead... + if (!empty($result["response"]["errors"][0]["message"])) { + throw new TrackingException($result["response"]["errors"][0]["message"]); + } + throw new TrackingException("The UPS tracking system is having problems. Try again later."); + } + if (!empty($result["Fault"])) { + // Should have trackResponse instead... + if (!empty($result["Fault"]["detail"]["Errors"]["ErrorDetail"]["PrimaryErrorCode"]["Description"])) { + throw new TrackingException($result["Fault"]["detail"]["Errors"]["ErrorDetail"]["PrimaryErrorCode"]["Description"]); + } + throw new TrackingException("The UPS tracking system is having problems. Try again later."); + } + + if (!empty($result["TrackResponse"]["Shipment"]) && !empty($result["TrackResponse"]["Shipment"])) { + $trackinfo = $result["TrackResponse"]["Shipment"]; + } else { + throw new TrackingException("No tracking details found."); + } + //exitWithJson($trackinfo); + } 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(); + + $info->setCode($trackinfo["Package"]["TrackingNumber"]); + + $info->setCarrier("ups"); + $info->setService(new Service($trackinfo["Service"]["Code"], $trackinfo["Service"]["Description"])); + + if (count($trackinfo["Package"]["Activity"]) > 0) { + $current = $trackinfo["Package"]["Activity"][0]; + $current_status = new TrackingEntry( + TrackingStatus::UPSEventTypeToStatus($current["Status"]["Type"]), + $current["Status"]["Description"] ?? "Unknown", + DateTime::createFromFormat('Ymd His', "$current[Date] $current[Time]")->format("c") + ); + + $current_location = new Location(); + $current_location->city = $current["ActivityLocation"]["Address"]["City"] ?? ""; + $current_location->state = $current["ActivityLocation"]["Address"]["StateProvinceCode"] ?? ""; + $current_location->zip = $current["ActivityLocation"]["Address"]["PostalCode"] ?? ""; + $current_location->country = $current["ActivityLocation"]["Address"]["CountryCode"] ?? ""; + $current_status->setLocation($current_location); + + $info->setCurrentStatus($current_status); + } + + foreach ($trackinfo["ShipmentAddress"] as $address) { + switch ($address["Type"]["Code"]) { + case "02": // ShipTo Address + $to = new Location(); + $to->city = $address["Address"]["City"] ?? ""; + $to->state = $address["Address"]["StateProvinceCode"] ?? ""; + $to->zip = $address["Address"]["PostalCode"] ?? ""; + $to->country = $address["Address"]["CountryCode"] ?? ""; + $info->setTo($to); + break; + } + } +// +// $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); + + foreach ($trackinfo["Package"]["Activity"] as $history) { + $location = new Location(); + $location->city = $history["ActivityLocation"]["Address"]["City"] ?? ""; + $location->state = $history["ActivityLocation"]["Address"]["StateProvinceCode"] ?? ""; + $location->zip = $history["ActivityLocation"]["Address"]["PostalCode"] ?? ""; + $location->country = $history["ActivityLocation"]["Address"]["CountryCode"] ?? ""; + $info->appendHistoryEntry(new TrackingEntry( + TrackingStatus::UPSEventTypeToStatus($history["Status"]["Type"]), + $history["Status"]["Description"] ?? "Unknown", + DateTime::createFromFormat('Ymd His', "$history[Date] $history[Time]")->format("c"), + $location + )); + } + + return $info; + } + +} diff --git a/lib/Tracking_UPS_MailInnovations_DataMatrix.lib.php b/lib/Tracking_UPS_MailInnovations_DataMatrix.lib.php new file mode 100644 index 0000000..184ea63 --- /dev/null +++ b/lib/Tracking_UPS_MailInnovations_DataMatrix.lib.php @@ -0,0 +1,57 @@ +city = "Unknown"; + + $to = new Location(); + $to->street = implode(" ", [$codeparts[4], $codeparts[5]]); + $to->city = $codeparts[6]; + $to->state = $codeparts[7]; + $to->zip = $codeparts[8]; + $to->country = (new League\ISO3166\ISO3166)->numeric($codeparts[9])['alpha2']; + + $info->setCode($to->zip); + $info->setTo($to); + $info->setFrom($from); + $info->setCarrier("usps"); + $info->setService(new Service("", "UPS Mail Innovations")); + $info->setCurrentStatus(new TrackingEntry(TrackingStatus::TRACKING_STATUS_UNKNOWN, "Item is an untracked flat or international mailpiece.", time(), new Location())); + } else { + $realcode = $codeparts[13]; + + $info = Tracking::track($realcode); + + // Set detailed destination address from code data + $to = $info->getTo(); + if (is_null($to)) { + $to = new Location(); + } + $to->street = implode(" ", [$codeparts[4], $codeparts[5]]); + $to->city = $codeparts[6]; + $to->state = $codeparts[7]; + $to->zip = $codeparts[8]; + $to->country = (new League\ISO3166\ISO3166)->numeric($codeparts[9])['alpha2']; + $info->setTo($to); + + $info->setService(new Service("", "UPS Mail Innovations")); + } + + return $info; + } + +} diff --git a/lib/Tracking_USPS.lib.php b/lib/Tracking_USPS.lib.php new file mode 100644 index 0000000..c4b8712 --- /dev/null +++ b/lib/Tracking_USPS.lib.php @@ -0,0 +1,105 @@ +"); + $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((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)); + + $current_status = new TrackingEntry( + TrackingStatus::USPSEventCodeToStatus($trackinfo->TrackSummary->EventCode), + $trackinfo->StatusSummary ?? "Unknown", + $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 ?? ""; + $current_status->setLocation($current_location); + + $info->setCurrentStatus($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); + + foreach ($trackinfo->TrackDetail as $history) { + $location = new Location(); + $location->city = (string) $history->EventCity ?? ""; + $location->state = (string) $history->EventState ?? ""; + $location->zip = (string) $history->EventZIPCode ?? ""; + $location->country = (string) $history->EventCountry ?? ""; + $info->appendHistoryEntry(new TrackingEntry( + TrackingStatus::USPSEventCodeToStatus((string) $history->EventCode), + ((string) $history->Event), + $history->EventDate . " " . $history->EventTime, + $location, + TrackingStatus::isUSPSEventCodeContainerScan((string) $history->EventCode))); + } + + return $info; + } + +} diff --git a/publicsite/index.html b/publicsite/index.html index 2169c9b..90d3e13 100644 --- a/publicsite/index.html +++ b/publicsite/index.html @@ -102,6 +102,22 @@ +
+
+
+

/logistics: Addresses, Shipping, and Tracking

+
    +
  • + tracking +
    Get tracking status of a package +
    code=XX123456789US +
    carrier=usps (optional, autodetected if omitted) +
  • +
+
+
+
+