first commit

This commit is contained in:
Zeeshaun
2026-03-11 11:18:25 -05:00
commit f3cb730f3b
472 changed files with 53651 additions and 0 deletions
@@ -0,0 +1,10 @@
namespace USPS.Configuration
{
public class UspsSettings
{
public string Username { get; set; }
public string Password { get; set; }
public string EndpointUrl { get; set; }
}
}
@@ -0,0 +1,56 @@
using Landmark.OLA.Models.Addresses;
using System.Text;
using System.Web;
using System.Xml;
using USPS.Configuration;
namespace USPS.ApiCalls
{
public class AddressValidationRequestBuilder : IRequestBuildable
{
private readonly UspsSettings _settings;
private readonly Address _address;
public AddressValidationRequestBuilder(UspsSettings settings, Address address)
{
_settings = settings;
_address = address;
}
public virtual string BuildAddressXML()
{
var sb = new StringBuilder();
var sw = new StringWriter(sb);
var xml = new XmlTextWriter(sw);
xml.WriteStartElement("AddressValidateRequest");
xml.WriteAttributeString("USERID", _settings.Username);
xml.WriteStartElement("Address");
// USPS considers unit numbers (ex: APT 204) to be Address1. Our app's models would consider these to be Address2. DO NOT CHANGE
xml.WriteElementString("Address1", _address.Address2);
xml.WriteElementString("Address2", _address.Address1);
xml.WriteElementString("City", _address.City);
xml.WriteElementString("State", _address.State.ToString());
xml.WriteElementString("Zip5", _address.PostalCode);
xml.WriteElementString("Zip4", "");
xml.WriteEndElement();
xml.WriteEndElement();
xml.Close();
var query = HttpUtility.ParseQueryString(string.Empty);
query["API"] = "Verify";
query["XML"] = sb.ToString();
return query.ToString();
}
public string BuildGET()
{
var xml = BuildAddressXML();
var message = _settings.EndpointUrl + xml;
return message;
}
}
}
@@ -0,0 +1,8 @@
namespace USPS.ApiCalls
{
public interface IRequestBuildable
{
string BuildAddressXML();
}
}
@@ -0,0 +1,68 @@
using System.Xml.Linq;
using Landmark.OLA.Models.Addresses;
using Landmark.OLA.Models.Addresses.Enums;
namespace USPS.Parsers
{
public class AddressValidationResponseParser : IResponseParser<Address>
{
public bool CheckForError(XElement xdoc)
{
var elementAddress = xdoc.Element("Address");
if (elementAddress == null)
{
return true;
}
var error = elementAddress.Element("Error");
return (error != null);
}
public Address Parse(XElement xdoc)
{
if( xdoc == null )
{
throw new ArgumentNullException("Address xml is null");
}
var elementAddress = xdoc.Element("Address");
if (elementAddress == null || elementAddress.Element("Error") != null)
{
throw new FormatException("Attempt to parse address that is in error.");
}
try
{
var compareAddress = new Address
{
// USPS considers unit numbers (ex: APT 204) to be Address1. Our app's models would consider these to be Address2. DO NOT CHANGE
Address2 = (string)elementAddress.Element("Address1"),
Address1 = (string)elementAddress.Element("Address2"),
City = (string)elementAddress.Element("City"),
State = MakeStateOptionEnum((string)elementAddress.Element("State")),
PostalCode = (string)elementAddress.Element("Zip5"),
PostalCodeExt = (string)elementAddress.Element("Zip4"),
Country = CountryOption.UnitedStates
};
return compareAddress;
}
catch
{
throw new FormatException("USPS XML is in an unexpected layout.");
}
}
public static StateOption MakeStateOptionEnum(string state)
{
if (Enum.TryParse(state, out StateOption parsedState))
{
return parsedState;
}
else
{
throw new FormatException("USPS XML contains an unexpected 'State'");
}
}
}
}
@@ -0,0 +1,10 @@
using System.Xml.Linq;
namespace USPS.Parsers
{
public interface IResponseParser<T>
{
T Parse(XElement xElement);
bool CheckForError(XElement xElement);
}
}
@@ -0,0 +1,9 @@
using System.Xml.Linq;
namespace USPS.Communication
{
public interface IUspsService
{
Task<XElement> SendMessage(string request);
}
}
@@ -0,0 +1,46 @@
using System.Xml.Linq;
using Microsoft.Extensions.Options;
using Serilog;
using USPS.Configuration;
namespace USPS.Communication
{
public class UspsService : IUspsService
{
private readonly UspsSettings _settings;
public UspsService(
IOptions<UspsSettings> settings)
{
_settings = settings.Value;
}
public async Task<XElement> SendMessage(string request)
{
try
{
var hc = new HttpClient();
var result = await hc.GetAsync(request);
if (result.IsSuccessStatusCode)
{
var resultString = await result.Content.ReadAsStringAsync();
var xdoc = XDocument.Parse(resultString);
XElement xel = XElement.Parse(xdoc.ToString());
return xel;
}
else
{
throw new Exception("Bad status code from USPS");
}
}
catch (Exception e)
{
Log.Debug("{exception}", e.Message);
throw;
}
}
}
}
@@ -0,0 +1,62 @@
using System.Xml.Linq;
using Microsoft.Extensions.Options;
using Serilog;
using USPS.Parsers;
using USPS.ApiCalls;
using USPS.Configuration;
using Landmark.OLA.Models;
using Landmark.OLA.Models.Vendor;
using Landmark.OLA.Models.Addresses;
namespace USPS.Communication
{
public class UspsVendorClient : IUspsVendorClient
{
private readonly IUspsService _uspsService;
private readonly IResponseParser<Address> _addressParser;
private readonly UspsSettings _settings;
public UspsVendorClient(IUspsService uspsService,
IResponseParser<Address> addressParser,
IOptions<UspsSettings> settings)
{
_uspsService = uspsService;
_addressParser = addressParser;
_settings = settings.Value;
}
public async Task<Address> CompareAddress(Address address)
{
ModelValidator.Validate(address);
AddressValidationRequestBuilder api = new(_settings, address);
string createXml = api.BuildGET();
XElement response = await _uspsService.SendMessage(createXml);
try
{
if (_addressParser.CheckForError(response) )
{
throw new ArgumentException("USPS rejected address");
}
return _addressParser.Parse(response);
}
catch (ArgumentException)
{
// Do not log; this is thrown when a bad address is submitted and it just spams the logs.
throw;
}
catch (Exception)
{
Log.Fatal("Error parsing response.");
throw;
}
}
}
}
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
<ImplicitUsings>enable</ImplicitUsings>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
<PackageReference Include="CoreWCF.Primitives" Version="1.8.0" />
<PackageReference Include="CoreWCF.ConfigurationManager" Version="1.8.0" />
<PackageReference Include="CoreWCF.Http" Version="1.8.0" />
<PackageReference Include="CoreWCF.WebHttp" Version="1.8.0" />
<PackageReference Include="CoreWCF.NetTcp" Version="1.8.0" />
<PackageReference Include="Serilog" Version="4.2.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Landmark.OLA.Models\Landmark.OLA.Models.csproj" />
</ItemGroup>
</Project>