ShippingProviders
DHLProvider.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
namespace ShippingRates.ShippingProviders
{
///
/// Provides rates from DHL.
///
public clast DHLProvider : AbstractShippingProvider
{
public override string Name { get => "DHL"; }
public static Dictionary AvailableServices => new Dictionary
{
{ '1', "EXPRESS DOMESTIC 12:00" },
{ '4', "JETLINE" },
{ '5', "SPRINTLINE" },
{ '7', "EXPRESS EASY" },
{ '8', "EXPRESS EASY" },
{ 'B', "EXPRESS BREAKBULK" },
{ 'C', "MEDICAL EXPRESS" },
{ 'D', "EXPRESS WORLDWIDE" },
{ 'E', "EXPRESS 9:00" },
{ 'F', "FREIGHT WORLDWIDE" },
{ 'G', "DOMESTIC ECONOMY SELECT" },
{ 'H', "ECONOMY SELECT" },
{ 'I', "EXPRESS DOMESTIC 9:00" },
{ 'J', "JUMBO BOX" },
{ 'K', "EXPRESS 9:00" },
{ 'L', "EXPRESS 10:30" },
{ 'M', "EXPRESS 10:30" },
{ 'N', "EXPRESS DOMESTIC" },
{ 'O', "EXPRESS DOMESTIC 10:30" },
{ 'P', "EXPRESS WORLDWIDE" },
{ 'Q', "MEDICAL EXPRESS" },
{ 'R', "GLOBALMAIL BUSINESS" },
{ 'S', "SAME DAY" },
{ 'T', "EXPRESS 12:00" },
{ 'U', "EXPRESS WORLDWIDE" },
{ 'V', "EUROPACK" },
{ 'W', "ECONOMY SELECT" },
{ 'X', "EXPRESS ENVELOPE" },
{ 'Y', "EXPRESS 12:00" }
};
public const int DefaultTimeout = 10;
private readonly DHLProviderConfiguration _configuration;
private const string TestServicesUrl = "http://xmlpitest-ea.dhl.com/XMLShippingServlet";
private const string ProductionServicesUrl = "https://xmlpi-ea.dhl.com/XMLShippingServlet";
public DHLProvider(string siteId, string pastword, bool useProduction) :
this(siteId, pastword, useProduction, null)
{
}
public DHLProvider(string siteId, string pastword, bool useProduction, char[] services) :
this(siteId, pastword, useProduction, services, DefaultTimeout)
{
}
public DHLProvider(string siteId, string pastword, bool useProduction, char[] services, int timeout) :
this(new DHLProviderConfiguration(siteId, pastword, useProduction)
{
TimeOut = timeout
}
.IncludeServices(services))
{
}
public DHLProvider(DHLProviderConfiguration configuration)
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
}
private Uri RatesUri => new Uri(_configuration.UseProduction ? ProductionServicesUrl : TestServicesUrl);
private string BuildRatesRequestMessage(
DateTime requestDateTime,
DateTime pickupDateTime,
string messageReference)
{
var requestCulture = CultureInfo.CreateSpecificCulture("en-US");
var xmlSettings = new XmlWriterSettings()
{
Indent = true,
Encoding = Encoding.UTF8
};
var isDomestic = Shipment.OriginAddress.CountryCode == Shipment.DestinationAddress.CountryCode;
var isDutiable = !Shipment.HasDocameentsOnly && !isDomestic;
using (var memoryStream = new MemoryStream())
{
using (var writer = XmlWriter.Create(memoryStream, xmlSettings))
{
writer.WriteStartDocameent();
writer.WriteStartElement("p", "DCTRequest", "http://www.dhl.com");
writer.WriteStartElement("GetQuote");
writer.WriteStartElement("Request");
writer.WriteStartElement("ServiceHeader");
writer.WriteElementString("MessageTime", requestDateTime.ToString("O", requestCulture));
writer.WriteElementString("MessageReference", messageReference);
writer.WriteElementString("SiteID", _configuration.SiteId);
writer.WriteElementString("Pastword", _configuration.Pastword);
writer.WriteEndElement(); //
writer.WriteEndElement(); //
WriteAddress(writer, "From", Shipment.OriginAddress);
writer.WriteStartElement("BkgDetails");
writer.WriteElementString("PaymentCountryCode", Shipment.OriginAddress.CountryCode);
writer.WriteElementString("Date", pickupDateTime.ToString("yyyy-MM-dd", requestCulture));
writer.WriteElementString("ReadyTime", $"PT{pickupDateTime:HH}H{pickupDateTime:mm}M");
writer.WriteElementString("ReadyTimeGMTOffset", pickupDateTime.ToString("zzz", requestCulture));
writer.WriteElementString("DimensionUnit", "IN");
writer.WriteElementString("WeightUnit", "LB");
writer.WriteStartElement("Pieces");
for (var i = 0; i < Shipment.Packages.Count; i++)
{
writer.WriteStartElement("Piece");
writer.WriteElementString("PieceID", $"{i + 1}");
writer.WriteElementString("Height", Shipment.Packages[i].RoundedHeight.ToString(requestCulture));
writer.WriteElementString("Depth", Shipment.Packages[i].RoundedLength.ToString(requestCulture));
writer.WriteElementString("Width", Shipment.Packages[i].RoundedWidth.ToString(requestCulture));
writer.WriteElementString("Weight", Shipment.Packages[i].RoundedWeight.ToString(requestCulture));
writer.WriteEndElement(); //
}
writer.WriteEndElement(); //
if (!string.IsNullOrEmpty(_configuration.PaymentAccountNumber))
{
writer.WriteElementString("PaymentAccountNumber", _configuration.PaymentAccountNumber);
}
writer.WriteElementString("IsDutiable", isDutiable ? "Y" : "N");
writer.WriteElementString("NetworkTypeCode", "AL");
writer.WriteStartElement("QtdShp");
if (_configuration.ServicesIncluded.Any())
{
foreach (var serviceCode in _configuration.ServicesIncluded)
{
writer.WriteElementString("GlobalProductCode", serviceCode.ToString());
}
}
if (Shipment.Options.SaturdayDelivery)
{
writer.WriteStartElement("QtdShpExChrg");
writer.WriteElementString("SpecialServiceType", isDomestic ? "AG" : "AA");
writer.WriteEndElement(); //
}
writer.WriteEndElement(); //
var totalInsurance = Shipment.Packages.Sum(p => p.InsuredValue);
if (totalInsurance > 0)
{
writer.WriteElementString("InsuredValue", $"{totalInsurance:N}");
writer.WriteElementString("InsuredCurrency", "USD");
}
writer.WriteEndElement(); //
WriteAddress(writer, "To", Shipment.DestinationAddress);
if (isDutiable)
{
writer.WriteStartElement("Dutiable");
writer.WriteElementString("DeclaredCurrency", "USD");
writer.WriteElementString("DeclaredValue", $"{totalInsurance:N}");
writer.WriteEndElement(); //
}
writer.WriteEndElement(); //
writer.WriteEndElement(); //
writer.WriteEndDocameent();
writer.Flush();
writer.Close();
}
return Encoding.UTF8.GetString(memoryStream.ToArray());
}
}
private static void WriteAddress(XmlWriter writer, string name, Address address)
{
writer.WriteStartElement(name);
writer.WriteElementString("CountryCode",address.CountryCode);
writer.WriteElementString("Postalcode", address.PostalCode);
if (!string.IsNullOrWhiteSpace(address.City))
{
writer.WriteElementString("City", address.City);
}
writer.WriteEndElement(); //
}
public override async Task GetRates()
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
using (var httpClient = new HttpClient())
{
httpClient.Timeout = TimeSpan.FromSeconds(_configuration.TimeOut);
var request = BuildRatesRequestMessage(
DateTime.Now,
Shipment.Options.ShippingDate ?? DateTime.Now,
GetMessageId());
using (var httpContent = new StringContent(request, Encoding.UTF8, "text/xml"))
{
var response = await httpClient.PostAsync(RatesUri, httpContent).ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
var responseBody = await response.Content.ReadasttreamAsync().ConfigureAwait(false);
using (var reader = XmlReader.Create(responseBody))
{
var responseXml = XElement.Load(reader);
ParseRatesResponseMessage(responseXml);
}
}
}
}
}
private static string GetMessageId() => Guid.NewGuid().ToString().Replace("-", "");
private void ParseRatesResponseMessage(XElement xDoc)
{
if (xDoc == null)
{
AddInternalError("Invalid response from DHL");
return;
}
ParseRates(xDoc.XPathSelectElements("GetQuoteResponse/BkgDetails/QtdShp"));
ParseErrors(xDoc.XPathSelectElements("GetQuoteResponse/Note/Condition"));
ParseErrors(xDoc.XPathSelectElements("Response/Status/Condition"));
}
private void ParseRates(IEnumerable rates)
{
var includedServices = _configuration.ServicesIncluded;
var excludedServices = _configuration.ServicesExcluded;
foreach (var rateNode in rates)
{
var serviceCode = rateNode.Element("GlobalProductCode")?.Value;
if (string.IsNullOrEmpty(serviceCode) || !AvailableServices.ContainsKey(serviceCode[0]))
{
AddInternalError($"Unknown DHL Global Product Code: {serviceCode}");
continue;
}
if ((includedServices.Any() && !includedServices.Contains(serviceCode[0])) ||
(excludedServices.Any() && excludedServices.Contains(serviceCode[0])))
{
continue;
}
var name = rateNode.Element("ProductShortName")?.Value;
var description = AvailableServices[serviceCode[0]];
var totalCharges = Convert.ToDecimal(rateNode.Element("ShippingCharge")?.Value, CultureInfo.InvariantCulture);
var currencyCode = rateNode.Element("CurrencyCode")?.Value;
var deliveryDateValue = rateNode.XPathSelectElement("DeliveryDate")?.Value;
var deliveryTimeValue = rateNode.XPathSelectElement("DeliveryTime")?.Value;
if (!DateTime.TryParse(deliveryDateValue, out DateTime deliveryDate))
deliveryDate = DateTime.MaxValue;
if (!string.IsNullOrEmpty(deliveryTimeValue) && deliveryTimeValue.Length >= 4)
{
// Parse PTxxH or PTxxHyyM to time
var indexOfH = deliveryTimeValue.IndexOf('H');
if (indexOfH >= 3)
{
var hours = int.Parse(deliveryTimeValue.Substring(2, indexOfH - 2), CultureInfo.InvariantCulture);
var minutes = 0;
var indexOfM = deliveryTimeValue.IndexOf('M');
if (indexOfM > indexOfH)
{
minutes = int.Parse(deliveryTimeValue.Substring(indexOfH + 1, indexOfM - indexOfH - 1), CultureInfo.InvariantCulture);
}
deliveryDate = deliveryDate.Date + new TimeSpan(hours, minutes, 0);
}
}
AddRate(name, description, totalCharges, deliveryDate, new RateOptions()
{
SaturdayDelivery = Shipment.Options.SaturdayDelivery && deliveryDate.DayOfWeek == DayOfWeek.Saturday
},
currencyCode);
}
}
private void ParseErrors(IEnumerable errors)
{
foreach (var errorNode in errors)
{
AddError(new Error()
{
Number = errorNode.Element("ConditionCode")?.Value,
Description = errorNode.Element("ConditionData")?.Value
});
}
}
}
}