diff --git a/apiconfig.php b/apiconfig.php
index d01ebd2..acbd338 100644
--- a/apiconfig.php
+++ b/apiconfig.php
@@ -62,6 +62,17 @@ $APIS = [
"carrier (optional)" => ""
]
],
+ "logistics/uspsaddress" => [
+ "load" => "logistics.uspsaddress.php",
+ "vars" => [
+ "number" => "",
+ "street" => "",
+ "unit (optional)" => "",
+ "city (optional)" => "",
+ "state (optional)" => "",
+ "zip (optional)" => ""
+ ]
+ ],
"net/contactspam" => [
"load" => "net.contactspam.php",
"vars" => [
diff --git a/endpoints/logistics.uspsaddress.php b/endpoints/logistics.uspsaddress.php
new file mode 100644
index 0000000..63e62bc
--- /dev/null
+++ b/endpoints/logistics.uspsaddress.php
@@ -0,0 +1,93 @@
+get("logistics.uspsaddress." . $hash);
+if ($cacheresp !== false && empty($VARS["nocache"])) {
+ exitWithJson(json_decode($cacheresp, true));
+}
+
+$output = [
+ "status" => "OK",
+ "address" => [
+ "status" => "",
+ "message" => "",
+ "address" => "",
+ "zip" => "",
+ "plus4" => "",
+ "delivery_point" => "",
+ "route" => "",
+ "county" => "",
+ "dpv_confirmed" => false,
+ "cmra" => false,
+ "business" => false,
+ "centralized" => false,
+ "vacant" => false
+ ]
+];
+
+try {
+
+ if (empty($VARS["state"])) {
+ throw new Exception("A state is required.");
+ }
+ if (empty($VARS["zip"]) && empty($VARS["city"])) {
+ throw new Exception("A city and/or ZIP Code are required.");
+ }
+
+ $VARS["state"] = trim(strtoupper($VARS["state"]));
+ $statereg = "/^(AA|AE|AL|AK|AP|AS|AZ|AR|CA|CO|CT|DE|DC|FM|FL|GA|GU|HI|ID|IL|IN|IA|KS|KY|LA|ME|MH|MD|MA|MI|MN|MS|MO|MP|MT|NE|NV|NH|NJ|NM|NY|NC|ND|OH|OK|OR|PW|PA|PR|RI|SC|SD|TN|TX|UT|VT|VI|VA|WA|WV|WI|WY)$/";
+ if (!preg_match($statereg, $VARS["state"])) {
+ throw new Exception("Two-letter state name is required.");
+ }
+
+ $resp = USPSAPIs::getAPIRequest(
+ "addresses/v3/address?"
+ . "streetAddress=" . urlencode($VARS["number"] . " " . $VARS["street"])
+ . (!empty($VARS["unit"]) ? "&secondaryAddress=" . urlencode($VARS["unit"]) : "")
+ . (!empty($VARS["city"]) ? "&city=" . urlencode($VARS["city"]) : "&city=")
+ . (!empty($VARS["state"]) ? "&state=" . urlencode($VARS["state"]) : "&state=")
+ . (!empty($VARS["zip"]) ? "&ZIPCode=" . urlencode($VARS["zip"]) : "")
+ );
+
+ $json = json_decode($resp, true);
+
+ if (!empty($json["error"])) {
+ if (!empty($json["error"]["message"])) {
+ throw new Exception(trim($json["error"]["message"]));
+ }
+ throw new Exception("The USPS system is having problems. Try again later.");
+ }
+
+ $addr = $json["address"];
+ $addl = $json["additionalInfo"];
+
+ $output["address"]["address"] = $addr["streetAddress"] . (!empty($addr["secondaryAddress"]) ? " " . $addr["secondaryAddress"] : "");
+ $output["address"]["city"] = $addr["city"];
+ $output["address"]["state"] = $addr["state"];
+ $output["address"]["zip"] = $addr["ZIPCode"];
+ $output["address"]["plus4"] = $addr["ZIPPlus4"] ?? "";
+
+ $output["address"]["delivery_point"] = (empty($addl["deliveryPoint"]) ? "" : $addl["deliveryPoint"]);
+ $output["address"]["route"] = $addl["carrierRoute"];
+ $output["address"]["county"] = ""; // Not implemented in API but a value is expected by some clients
+ $output["address"]["dpv_confirmed"] = $addl["DPVConfirmation"] == "Y";
+ $output["address"]["cmra"] = ($addl["DPVCMRA"] ?? "") == "Y";
+ $output["address"]["business"] = $addl["business"] == "Y";
+ $output["address"]["centralized"] = $addl["centralDeliveryPoint"] == "Y";
+ $output["address"]["vacant"] = $addl["vacant"] == "Y";
+
+ $output["address"]["status"] = "OK";
+
+ $memcache->set("logistics.uspsaddress." . $hash, json_encode($output));
+} catch (Exception $ex) {
+ $output["address"]["status"] = "ERROR";
+ $output["address"]["message"] = $ex->getMessage();
+}
+
+exitWithJson($output);
diff --git a/lib/Tracking_USPS.lib.php b/lib/Tracking_USPS.lib.php
index a595321..454d1c4 100644
--- a/lib/Tracking_USPS.lib.php
+++ b/lib/Tracking_USPS.lib.php
@@ -28,6 +28,7 @@ class Tracking_USPS {
$resp = USPSAPIs::getAPIRequest("tracking/v3/tracking/$code?expand=DETAIL");
$resp = str_replace("reg;", "®", $resp);
$resp = str_replace("®", "®", $resp);
+ $resp = str_replace("", "™", $resp);
$json = json_decode($resp, true);
if (!empty($json["error"])) {
diff --git a/publicsite/index.html b/publicsite/index.html
index 1506874..a06eaad 100644
--- a/publicsite/index.html
+++ b/publicsite/index.html
@@ -131,6 +131,16 @@
code=XX123456789US
carrier=usps (optional, autodetected if omitted)
+