Browse Source

Add package tracking API

master
Skylar Ittner 2 months ago
parent
commit
31a6243408
  1. 7
      apiconfig.php
  2. 4
      composer.json
  3. 114
      composer.lock
  4. 18
      endpoints/logistics.tracking.php
  5. 9
      env.sample.php
  6. 187
      lib/Carriers.lib.php
  7. 29
      lib/Location.lib.php
  8. 11
      lib/Service.lib.php
  9. 37
      lib/Tracking.lib.php
  10. 58
      lib/TrackingBarcode.lib.php
  11. 47
      lib/TrackingEntry.lib.php
  12. 5
      lib/TrackingException.lib.php
  13. 191
      lib/TrackingInfo.lib.php
  14. 250
      lib/TrackingStatus.lib.php
  15. 33
      lib/Tracking_FedEx_PDF417.lib.php
  16. 51
      lib/Tracking_HelenaExpress.lib.php
  17. 86
      lib/Tracking_Shippo.lib.php
  18. 149
      lib/Tracking_UPS.lib.php
  19. 57
      lib/Tracking_UPS_MailInnovations_DataMatrix.lib.php
  20. 105
      lib/Tracking_USPS.lib.php
  21. 16
      publicsite/index.html

7
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" => [

4
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"
}
}

114
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",

18
endpoints/logistics.tracking.php

@ -0,0 +1,18 @@
<?php
$trackinginfo;
try {
if (!empty($VARS["carrier"])) {
$trackinginfo = Tracking::track($VARS["code"], $VARS["carrier"]);
} else {
$trackinginfo = Tracking::track($VARS["code"]);
}
} catch (TrackingException $ex) {
sendJsonResp($ex->getMessage(), "ERROR");
}
if (is_null($trackinginfo)) {
sendJsonResp("Could not find any results for that tracking code.", "ERROR");
}
exitWithJson($trackinginfo->toObject());

9
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" => "",
];

187
lib/Carriers.lib.php

@ -0,0 +1,187 @@
<?php
class Carriers {
const CARRIER_REGEXES = [
[
"carrier" => "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 "";
}
}

29
lib/Location.lib.php

@ -0,0 +1,29 @@
<?php
class Location {
public $number = "";
public $streetname = "";
/**
*
* @var string Number and street name.
*/
public $street = "";
public $unit = "";
public $city = "";
public $state = "";
public $zip = "";
public $country = "";
public $latitude = false;
public $longitude = false;
public function hasAddress(): bool {
return ($number . $streetname . $street . $city . $state . $zip . $country != "");
}
public function hasCoordinates(): bool {
return ($latitude !== false && $longitude !== false);
}
}

11
lib/Service.lib.php

@ -0,0 +1,11 @@
<?php
class Service {
public $id = "";
public $name = "";
public function __construct($id, $name) {
$this->id = $id;
$this->name = $name;
}
}

37
lib/Tracking.lib.php

@ -0,0 +1,37 @@
<?php
class Tracking {
/**
*
* @global type $SETTINGS
* @param string $code
* @return \TrackingInfo
* @throws TrackingException
*/
public static function track(string $code, string $carrier = ""): TrackingInfo {
$barcode = new TrackingBarcode($code);
if (!empty($carrier)) {
$carriercode = Carriers::getCarrierCode($carrier);
} else {
$carriercode = $barcode->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());
}
}
}

58
lib/TrackingBarcode.lib.php

@ -0,0 +1,58 @@
<?php
class TrackingBarcode {
private $code = "";
public function __construct(string $code) {
$this->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;
}
}

47
lib/TrackingEntry.lib.php

@ -0,0 +1,47 @@
<?php
class TrackingEntry {
private $status = TrackingStatus::TRACKING_STATUS_UNKNOWN;
private $details = "";
private $datetime = "";
private $location = null;
private $iscontainerscan = false;
public function __construct(int $status, string $details, string $datetime, $location = null, bool $iscontainerscan = false) {
$this->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;
}
}

5
lib/TrackingException.lib.php

@ -0,0 +1,5 @@
<?php
class TrackingException extends Exception {
}

191
lib/TrackingInfo.lib.php

@ -0,0 +1,191 @@
<?php
class TrackingInfo {
private $code = null;
private $carrier = null;
private $service = null;
private $history = [];
private $from = null;
private $to = null;
private $current_status = null;
public function __construct($code = null, $carrier = null, $to = null, $from = null, $current_status = null, $history = null) {
$this->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;
}
}

250
lib/TrackingStatus.lib.php

@ -0,0 +1,250 @@
<?php
class TrackingStatus {
public const TRACKING_STATUS_UNKNOWN = 0;
public const TRACKING_STATUS_PRE_TRANSIT = 1;
public const TRACKING_STATUS_TRANSIT = 2;
public const TRACKING_STATUS_DELIVERED = 3;
public const TRACKING_STATUS_RETURNED = 4;
public const TRACKING_STATUS_FAILURE = 5;
/**
* Convert a status string API response to a constant integer.
* @param string $status
* @return int
*/
public static function stringToStatus(string $status): int {
$status = strtoupper($status);
switch ($status) {
case "PRE_TRANSIT":
return TrackingStatus::TRACKING_STATUS_PRE_TRANSIT;
case "TRANSIT":
case "IN_TRANSIT":
case "ACCEPTED":
return TrackingStatus::TRACKING_STATUS_TRANSIT;
case "DELIVERED":
return TrackingStatus::TRACKING_STATUS_DELIVERED;
case "RETURNED":
return TrackingStatus::TRACKING_STATUS_RETURNED;
case "FAILURE":
case "DELIVERY ATTEMPT":
case "ALERT":
return TrackingStatus::TRACKING_STATUS_FAILURE;
default:
return TrackingStatus::TRACKING_STATUS_UNKNOWN;
}
}
/**
* Convert a status constant to a string.
* @param int $status
* @return string
*/
public static function statusToString(int $status): string {
switch ($status) {
case TrackingStatus::TRACKING_STATUS_PRE_TRANSIT:
return "PRE_TRANSIT";
case TrackingStatus::TRACKING_STATUS_TRANSIT:
return "TRANSIT";
case TrackingStatus::TRACKING_STATUS_DELIVERED:
return "DELIVERED";
case TrackingStatus::TRACKING_STATUS_RETURNED:
return "RETURNED";
case TrackingStatus::TRACKING_STATUS_FAILURE:
return "FAILURE";
default:
return "UNKNOWN";
}
}
/**
* Returns false if a two-char USPS API scan event is probably a physical scan of the actual item
* @param string $eventcode
* @return boolean
*/
public static function isUSPSEventCodeContainerScan(string $eventcode) {
switch ($eventcode) {
case "A1":
case "AE":
case "DE":
case "E1":
case "L1":
case "MA":
case "MR":
case "NT":
case "OA":
case "OD":
case "OF":
case "OX":
case "PC":
case "RB":
case "SF":
case "T1":
case "TM":
case "TX":
case "U1":
case "UA":
case "VF":
case "VR":
case "WX":
case "GX":
case "80":
case "89":
return true;
default:
return false;
}
}
public static function USPSEventCodeToStatus(string $eventcode) {
switch ($eventcode) {
case "GC":
case "GX":
case "MA":
case "89":
return TrackingStatus::TRACKING_STATUS_PRE_TRANSIT;
case "03":
case "06":
case "07":
case "08":
case "10":
case "12":
case "PA":
case "14":
case "15":
case "16":
case "17":
case "19":
case "30": // no access
case "34":
case "35":
case "36":
case "38":
case "39":
case "40":
case "42":
case "45":
case "52":
case "58":
case "59":
case "70":
case "71":
case "72":
case "73":
case "74":
case "80":
case "81":
case "82":
case "83":
case "A1":
case "AD":
case "AE":
case "AR":
case "AX":
case "B1":
case "B5":
case "BB":
case "DD":
case "DE":
case "DX":
case "E1":
case "EF":
case "L1":
case "MR":
case "NT": // generic filler messages to fill tracking gaps
case "NP":
case "C0":
case "U1":
case "OA":
case "OD":
case "OF":
case "PC":
case "RB":
case "RC":
case "SF":
case "T1":
case "TM":
case "UA":
case "VF":
case "VR":
case "WX":
return TrackingStatus::TRACKING_STATUS_TRANSIT;
case "01":
case "13":
case "17":
case "41":
case "43":
case "60":
case "61":
case "62":
case "63":
case "84":
case "85":
case "86":
case "87":
case "LD": // letter estimated to be delivered today
return TrackingStatus::TRACKING_STATUS_DELIVERED;
case "04":
case "05":
case "09":
case "21":
case "22":
case "23":
case "24":
case "25":
case "26":
case "27":
case "28":
case "29":
return TrackingStatus::TRACKING_STATUS_RETURNED;
case "02":
case "53":
case "54":
case "55":
case "56":
case "57":
case "64":
case "04":
case "05":
case "11":
case "16":
case "31":
case "32":
case "33":
case "44":
case "51":
case "LX": // LOOP bin
case "OX":
case "TX":
return TrackingStatus::TRACKING_STATUS_FAILURE;
default:
return TrackingStatus::TRACKING_STATUS_UNKNOWN;
}
}
public static function UPSEventTypeToStatus(string $eventcode) {
switch ($eventcode) {
case "M":
case "MV":
return TrackingStatus::TRACKING_STATUS_PRE_TRANSIT;
case "I":
case "P":
case "O":
case "W":
case "DO":
case "DD":
return TrackingStatus::TRACKING_STATUS_TRANSIT;
case "D":
return TrackingStatus::TRACKING_STATUS_DELIVERED;
case "RS":
return TrackingStatus::TRACKING_STATUS_RETURNED;
case "X":
return TrackingStatus::TRACKING_STATUS_FAILURE;
case "NA":
default:
return TrackingStatus::TRACKING_STATUS_UNKNOWN;
}
}
}

33
lib/Tracking_FedEx_PDF417.lib.php

@ -0,0 +1,33 @@
<?php
class Tracking_FedEx_PDF417 {
/**
*
* @global type $SETTINGS
* @param string $code
* @return \TrackingInfo
* @throws TrackingException
*/
public static function track(string $code): TrackingInfo {
$codeparts = preg_split("/(\\x1D|\\x1E)/", $code);
$realcode = $codeparts[5];
$info = Tracking::track($realcode, "fedex");
// Set detailed destination address from code data
$to = $info->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;
}
}

51
lib/Tracking_HelenaExpress.lib.php

@ -0,0 +1,51 @@
<?php
class Tracking_HelenaExpress {
/**
*
* @global type $SETTINGS
* @param string $code
* @return \TrackingInfo
* @throws TrackingException
*/
public static function track(string $code): TrackingInfo {
global $SETTINGS;
$barcode = new TrackingBarcode($code);
try {
$status = json_decode(file_get_contents("https://helena.express/tracker/api.php?code=" . $barcode->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;
}
}

86
lib/Tracking_Shippo.lib.php

@ -0,0 +1,86 @@
<?php
class Tracking_Shippo {
/**
*
* @param string $code
* @return \TrackingInfo
* @throws TrackingException
*/
public static function track(string $code, string $carrier = ""): TrackingInfo {
$barcode = new TrackingBarcode($code);
Shippo::setApiKey(env("shippo_key"));
try {
$status = Shippo_Track::get_status([
"id" => $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;
}
}

149
lib/Tracking_UPS.lib.php

@ -0,0 +1,149 @@
<?php
class Tracking_UPS {
/**
*
* @param string $code
* @return \TrackingInfo
* @throws TrackingException
*/
public static function track(string $code, string $carrier = ""): TrackingInfo {
$barcode = new TrackingBarcode($code);
try {
$trackrequest = [
"Security" => [
"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;
}
}

57
lib/Tracking_UPS_MailInnovations_DataMatrix.lib.php

@ -0,0 +1,57 @@
<?php
class Tracking_UPS_MailInnovations_DataMatrix {
/**
*
* @global type $SETTINGS
* @param string $code
* @return \TrackingInfo
* @throws TrackingException
*/
public static function track(string $code): TrackingInfo {
$codeparts = explode("\t", $code);
$info = new TrackingInfo();
if (empty($codeparts[13])) {
$from = new Location();
$from->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;
}
}

105
lib/Tracking_USPS.lib.php

@ -0,0 +1,105 @@
<?php
class Tracking_USPS {
/**
*
* @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((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;
}
}

16
publicsite/index.html

@ -102,6 +102,22 @@
</div>
</div>
<div class="col-12 col-md-6">
<div class="card border-primary">
<div class="card-body">
<h4 class="card-title d-flex">/logistics: Addresses, Shipping, and Tracking</h4>
<ul class="list-group">
<li class="list-group-item">
<b>tracking</b>
<br><i>Get tracking status of a package</i>
<br>code=XX123456789US
<br>carrier=usps (optional, autodetected if omitted)
</li>
</ul>
</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="card border-primary">
<div class="card-body">

Loading…
Cancel
Save