From 7b2e779e9588c9abce9cbbed2e05e3f6b9b116fd Mon Sep 17 00:00:00 2001 From: Alexey Busygin Date: Fri, 9 Aug 2024 17:19:14 +0500 Subject: [PATCH] #69 Support for kg/cm UPS shipments (#70) * UnitsSystem introduced * UPS provider to detect units system * PackageKgCm --- .../Units/PackageDimensionTests.cs | 45 +++++ ShippingRates.Tests/Units/PackageKgCmTests.cs | 26 +++ .../Units/PackageWeightTests.cs | 45 +++++ ShippingRates/Models/Package.cs | 80 ++++---- ShippingRates/Models/PackageDimension.cs | 41 +++++ ShippingRates/Models/PackageKgCm.cs | 42 +++++ ShippingRates/Models/PackageWeight.cs | 37 ++++ ShippingRates/Models/Shipment.cs | 5 +- .../Models/UPS/ShipmentToRequestAdapter.cs | 173 ------------------ ShippingRates/Models/UPS/UPSErrorResponse.cs | 10 +- ShippingRates/Models/UPS/UPSRatingRequest.cs | 129 +------------ ShippingRates/Models/UPS/UPSRatingResponse.cs | 8 +- ShippingRates/Models/UPS/UpsAddress.cs | 28 +++ ShippingRates/Models/UPS/UpsPackage.cs | 86 +++++++++ ShippingRates/Models/UPS/UpsShipment.cs | 78 ++++++++ ShippingRates/Models/UnitsSystem.cs | 14 ++ ShippingRates/Services/UPSOAuthService.cs | 2 +- ShippingRates/Services/UPSRatingService.cs | 58 +----- ShippingRates/Services/UpsBaseService.cs | 59 ++++++ .../ShippingProviders/DHLProvider.cs | 10 +- .../ShippingProviders/FedExBaseProvider.cs | 12 +- .../ShippingProviders/UPSProvider.cs | 112 +++++++++++- .../USPSInternationalProvider.cs | 8 +- .../ShippingProviders/USPSProvider.cs | 22 ++- 24 files changed, 708 insertions(+), 422 deletions(-) create mode 100644 ShippingRates.Tests/Units/PackageDimensionTests.cs create mode 100644 ShippingRates.Tests/Units/PackageKgCmTests.cs create mode 100644 ShippingRates.Tests/Units/PackageWeightTests.cs create mode 100644 ShippingRates/Models/PackageDimension.cs create mode 100644 ShippingRates/Models/PackageKgCm.cs create mode 100644 ShippingRates/Models/PackageWeight.cs delete mode 100644 ShippingRates/Models/UPS/ShipmentToRequestAdapter.cs create mode 100644 ShippingRates/Models/UPS/UpsAddress.cs create mode 100644 ShippingRates/Models/UPS/UpsPackage.cs create mode 100644 ShippingRates/Models/UPS/UpsShipment.cs create mode 100644 ShippingRates/Models/UnitsSystem.cs create mode 100644 ShippingRates/Services/UpsBaseService.cs diff --git a/ShippingRates.Tests/Units/PackageDimensionTests.cs b/ShippingRates.Tests/Units/PackageDimensionTests.cs new file mode 100644 index 0000000..a487e33 --- /dev/null +++ b/ShippingRates.Tests/Units/PackageDimensionTests.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using ShippingRates.Models; + +namespace ShippingRates.Tests.Units +{ + [TestFixture] + public class PackageWeightTests + { + [Test()] + public void LbsToKgs() + { + var weight1 = new PackageWeight(UnitsSystem.USCustomary, 5); + Assert.AreEqual(5, weight1.Get()); + Assert.AreEqual(5, weight1.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(2.26796185m, weight1.Get(UnitsSystem.Metric)); + Assert.AreEqual(5, weight1.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(3, weight1.GetRounded(UnitsSystem.Metric)); + + var weight2 = new PackageWeight(UnitsSystem.USCustomary, 0.3m); + Assert.AreEqual(0.3m, weight2.Get()); + Assert.AreEqual(0.3m, weight2.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(0.136077711m, weight2.Get(UnitsSystem.Metric)); + Assert.AreEqual(1, weight2.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(1, weight2.GetRounded(UnitsSystem.Metric)); + } + + [Test()] + public void KgsToLbs() + { + var weight1 = new PackageWeight(UnitsSystem.Metric, 5); + Assert.AreEqual(5, weight1.Get()); + Assert.AreEqual(11.0231m, weight1.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(5, weight1.Get(UnitsSystem.Metric)); + Assert.AreEqual(12, weight1.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(5, weight1.GetRounded(UnitsSystem.Metric)); + + var weight2 = new PackageWeight(UnitsSystem.Metric, 0.5m); + Assert.AreEqual(0.5m, weight2.Get()); + Assert.AreEqual(1.10231m, weight2.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(0.5m, weight2.Get(UnitsSystem.Metric)); + Assert.AreEqual(2, weight2.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(1, weight2.GetRounded(UnitsSystem.Metric)); + } + } +} diff --git a/ShippingRates.Tests/Units/PackageKgCmTests.cs b/ShippingRates.Tests/Units/PackageKgCmTests.cs new file mode 100644 index 0000000..3d5a798 --- /dev/null +++ b/ShippingRates.Tests/Units/PackageKgCmTests.cs @@ -0,0 +1,26 @@ +using NUnit.Framework; +using ShippingRates.Models; + +namespace ShippingRates.Tests.Units +{ + [TestFixture] + public class PackageKgCmTests + { + [Test()] + public void TestDimensions() + { + var packageLbsInches = new Package(10, 20, 30, 40, 50); + var packageKgCm = new PackageKgCm(10, 20, 30, 40, 50); + + Assert.AreNotEqual(packageKgCm.GetHeight(UnitsSystem.Metric), packageLbsInches.GetHeight(UnitsSystem.Metric)); + Assert.AreNotEqual(packageKgCm.GetLength(UnitsSystem.Metric), packageLbsInches.GetLength(UnitsSystem.Metric)); + Assert.AreNotEqual(packageKgCm.GetWidth(UnitsSystem.USCustomary), packageLbsInches.GetWidth(UnitsSystem.USCustomary)); + Assert.AreNotEqual(packageKgCm.GetHeight(UnitsSystem.USCustomary), packageLbsInches.GetHeight(UnitsSystem.USCustomary)); + + Assert.AreEqual(40, packageLbsInches.GetWeight(UnitsSystem.USCustomary)); + Assert.AreEqual(18.1436948m, packageLbsInches.GetWeight(UnitsSystem.Metric)); + Assert.AreEqual(88.1848m, packageKgCm.GetWeight(UnitsSystem.USCustomary)); + Assert.AreEqual(40, packageKgCm.GetWeight(UnitsSystem.Metric)); + } + } +} diff --git a/ShippingRates.Tests/Units/PackageWeightTests.cs b/ShippingRates.Tests/Units/PackageWeightTests.cs new file mode 100644 index 0000000..aeee801 --- /dev/null +++ b/ShippingRates.Tests/Units/PackageWeightTests.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; +using ShippingRates.Models; + +namespace ShippingRates.Tests.Units +{ + [TestFixture] + public class PackageDimensionTests + { + [Test()] + public void InchesToCm() + { + var dimension1 = new PackageDimension(UnitsSystem.USCustomary, 5); + Assert.AreEqual(5, dimension1.Get()); + Assert.AreEqual(5, dimension1.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(12.7m, dimension1.Get(UnitsSystem.Metric)); + Assert.AreEqual(5, dimension1.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(13, dimension1.GetRounded(UnitsSystem.Metric)); + + var dimension2 = new PackageDimension(UnitsSystem.USCustomary, 0.4m); + Assert.AreEqual(0.4m, dimension2.Get()); + Assert.AreEqual(0.4m, dimension2.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(1.016m, dimension2.Get(UnitsSystem.Metric)); + Assert.AreEqual(1, dimension2.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(2, dimension2.GetRounded(UnitsSystem.Metric)); + } + + [Test()] + public void CmToInches() + { + var dimension1 = new PackageDimension(UnitsSystem.Metric, 6); + Assert.AreEqual(6, dimension1.Get()); + Assert.AreEqual(2.362206m, dimension1.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(6, dimension1.Get(UnitsSystem.Metric)); + Assert.AreEqual(3, dimension1.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(6, dimension1.GetRounded(UnitsSystem.Metric)); + + var dimension2 = new PackageDimension(UnitsSystem.Metric, 2.6m); + Assert.AreEqual(2.6m, dimension2.Get()); + Assert.AreEqual(1.0236226m, dimension2.Get(UnitsSystem.USCustomary)); + Assert.AreEqual(2.6m, dimension2.Get(UnitsSystem.Metric)); + Assert.AreEqual(2, dimension2.GetRounded(UnitsSystem.USCustomary)); + Assert.AreEqual(3, dimension2.GetRounded(UnitsSystem.Metric)); + } + } +} diff --git a/ShippingRates/Models/Package.cs b/ShippingRates/Models/Package.cs index 32a162a..13d3c74 100644 --- a/ShippingRates/Models/Package.cs +++ b/ShippingRates/Models/Package.cs @@ -1,3 +1,4 @@ +using ShippingRates.Models; using System; namespace ShippingRates @@ -7,6 +8,21 @@ namespace ShippingRates /// public class Package { + readonly UnitsSystem _unitsSystem; + PackageWeight _weight; + PackageDimension _length; + PackageDimension _width; + PackageDimension _height; + + protected Package(UnitsSystem unitsSystem, decimal length, decimal width, decimal height, decimal weight) + { + _unitsSystem = unitsSystem; + Weight = weight; + Length = length; + Width = width; + Height = height; + } + /// /// Creates a new package object. /// @@ -17,7 +33,8 @@ public class Package /// The insured-value of the package, in dollars. /// A specific packaging from a shipping provider. E.g. "LG FLAT RATE BOX" for USPS /// If true, will attempt to send this to the appropriate rate provider. - public Package(int length, int width, int height, int weight, decimal insuredValue, string container = null, bool signatureRequiredOnDelivery = false) : this(length, width, height, (decimal) weight, insuredValue, container, signatureRequiredOnDelivery) + public Package(int length, int width, int height, int weight, decimal insuredValue, string container = null, bool signatureRequiredOnDelivery = false) + : this(length, width, height, (decimal) weight, insuredValue, container, signatureRequiredOnDelivery) { } @@ -32,61 +49,52 @@ public Package(int length, int width, int height, int weight, decimal insuredVal /// A specific packaging from a shipping provider. E.g. "LG FLAT RATE BOX" for USPS /// If true, will attempt to send this to the appropriate rate provider. public Package(decimal length, decimal width, decimal height, decimal weight, decimal insuredValue, string container = null, bool signatureRequiredOnDelivery = false) + : this(UnitsSystem.USCustomary, length, width, height, weight) + { - Length = length; - Width = width; - Height = height; - Weight = weight; InsuredValue = insuredValue; Container = container; SignatureRequiredOnDelivery = signatureRequiredOnDelivery; } - public decimal CalculatedGirth + public decimal GetCalculatedGirth(UnitsSystem unitsSystem) { - get - { - var result = (Width * 2) + (Height * 2); - return Math.Ceiling(result); - } + var result = (_width.Get(unitsSystem) * 2) + (_height.Get(unitsSystem) * 2); + return Math.Ceiling(result); } - public decimal Height { get; set; } + + public decimal Height { get => _height.Get(); set => _height = new PackageDimension(_unitsSystem, value); } + public decimal Length { get => _length.Get(); set => _length = new PackageDimension(_unitsSystem, value); } + public decimal Width { get => _width.Get(); set => _width = new PackageDimension(_unitsSystem, value); } + public decimal Weight { get => _weight.Get(); set => _weight = new PackageWeight(_unitsSystem, value); } + public decimal InsuredValue { get; set; } public bool IsOversize { get; set; } - public decimal Length { get; set; } - public decimal RoundedHeight - { - get { return Math.Ceiling(Height); } - } - public decimal RoundedLength - { - get { return Math.Ceiling(Length); } - } - public decimal RoundedWeight - { - get { return Math.Ceiling(Weight); } - } - public decimal RoundedWidth - { - get { return Math.Ceiling(Width); } - } - public decimal Weight { get; set; } - public decimal Width { get; set; } public string Container { get; set; } public bool SignatureRequiredOnDelivery { get; set; } + + public decimal GetHeight(UnitsSystem unitsSystem) => _height.Get(unitsSystem); + public decimal GetLength(UnitsSystem unitsSystem) => _length.Get(unitsSystem); + public decimal GetWidth(UnitsSystem unitsSystem) => _width.Get(unitsSystem); + public decimal GetWeight(UnitsSystem unitsSystem) => _weight.Get(unitsSystem); + + public decimal GetRoundedHeight(UnitsSystem unitsSystem) => _height.GetRounded(unitsSystem); + public decimal GetRoundedLength(UnitsSystem unitsSystem) => _length.GetRounded(unitsSystem); + public decimal GetRoundedWidth(UnitsSystem unitsSystem) => _width.GetRounded(unitsSystem); + public decimal GetRoundedWeight(UnitsSystem unitsSystem) => _weight.GetRounded(unitsSystem); + public PoundsAndOunces PoundsAndOunces { get { var poundsAndOunces = new PoundsAndOunces(); - if (Weight <= 0) + var weight = _weight.Get(UnitsSystem.USCustomary); + if (weight > 0) { - return poundsAndOunces; + poundsAndOunces.Pounds = (int)Math.Truncate(weight); + poundsAndOunces.Ounces = (weight - poundsAndOunces.Pounds) * 16; } - poundsAndOunces.Pounds = (int) Math.Truncate(Weight); - poundsAndOunces.Ounces = (Weight - poundsAndOunces.Pounds) * 16; - return poundsAndOunces; } } diff --git a/ShippingRates/Models/PackageDimension.cs b/ShippingRates/Models/PackageDimension.cs new file mode 100644 index 0000000..9c80439 --- /dev/null +++ b/ShippingRates/Models/PackageDimension.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ShippingRates.Models +{ + internal class PackageDimension + { + readonly UnitsSystem _unitsSystem; + readonly decimal _value; + + public PackageDimension(UnitsSystem unitsSystem, decimal value) + { + _unitsSystem = unitsSystem; + _value = value; + } + + public decimal Get() => _value; + + public decimal Get(UnitsSystem unitsSystem) + { + if (unitsSystem == _unitsSystem) + { + return _value; + } + else if (unitsSystem == UnitsSystem.Metric && _unitsSystem == UnitsSystem.USCustomary) + { + return _value * 2.54m; + } + else if (unitsSystem == UnitsSystem.USCustomary && _unitsSystem == UnitsSystem.Metric) + { + return _value * 0.393701m; + } + throw new Exception($"Unsupported size conversion from {_unitsSystem} to {unitsSystem}"); + } + + public decimal GetRounded(UnitsSystem unitsSystem) => Math.Ceiling(Get(unitsSystem)); + } +} diff --git a/ShippingRates/Models/PackageKgCm.cs b/ShippingRates/Models/PackageKgCm.cs new file mode 100644 index 0000000..7b526aa --- /dev/null +++ b/ShippingRates/Models/PackageKgCm.cs @@ -0,0 +1,42 @@ +namespace ShippingRates.Models +{ + /// + /// Package object with dimensions in kgs and cm + /// + public class PackageKgCm : Package + { + /// + /// Creates a new package object with dimensions in kgs and cm + /// + /// The length of the package, in cm. + /// The width of the package, in cm. + /// The height of the package, in cm. + /// The weight of the package, in kgs. + /// The insured-value of the package, in dollars. + /// A specific packaging from a shipping provider. E.g. "LG FLAT RATE BOX" for USPS + /// If true, will attempt to send this to the appropriate rate provider. + public PackageKgCm(int length, int width, int height, int weight, decimal insuredValue, string container = null, bool signatureRequiredOnDelivery = false) + : this(length, width, height, (decimal)weight, insuredValue, container, signatureRequiredOnDelivery) + { + } + + /// + /// Creates a new package object with dimensions in kgs and cm + /// + /// The length of the package, in cm. + /// The width of the package, in cm. + /// The height of the package, in cm. + /// The weight of the package, in kgs. + /// The insured-value of the package, in dollars. + /// A specific packaging from a shipping provider. E.g. "LG FLAT RATE BOX" for USPS + /// If true, will attempt to send this to the appropriate rate provider. + public PackageKgCm(decimal length, decimal width, decimal height, decimal weight, decimal insuredValue, string container = null, bool signatureRequiredOnDelivery = false) + : base(UnitsSystem.Metric, length, width, height, weight) + + { + InsuredValue = insuredValue; + Container = container; + SignatureRequiredOnDelivery = signatureRequiredOnDelivery; + } + } +} diff --git a/ShippingRates/Models/PackageWeight.cs b/ShippingRates/Models/PackageWeight.cs new file mode 100644 index 0000000..3eb3ecf --- /dev/null +++ b/ShippingRates/Models/PackageWeight.cs @@ -0,0 +1,37 @@ +using System; + +namespace ShippingRates.Models +{ + internal class PackageWeight + { + readonly UnitsSystem _unitsSystem; + readonly decimal _value; + + public PackageWeight(UnitsSystem unitsSystem, decimal value) + { + _unitsSystem = unitsSystem; + _value = value; + } + + public decimal Get() => _value; + + public decimal Get(UnitsSystem unitsSystem) + { + if (unitsSystem == _unitsSystem) + { + return _value; + } + else if (unitsSystem == UnitsSystem.Metric && _unitsSystem == UnitsSystem.USCustomary) + { + return _value * 0.45359237m; + } + else if (unitsSystem == UnitsSystem.USCustomary && _unitsSystem == UnitsSystem.Metric) + { + return _value * 2.20462m; + } + throw new Exception($"Unsupported weight conversion from {_unitsSystem} to {unitsSystem}"); + } + + public decimal GetRounded(UnitsSystem unitsSystem) => Math.Ceiling(Get(unitsSystem)); + } +} diff --git a/ShippingRates/Models/Shipment.cs b/ShippingRates/Models/Shipment.cs index fc88e81..71df61e 100644 --- a/ShippingRates/Models/Shipment.cs +++ b/ShippingRates/Models/Shipment.cs @@ -1,3 +1,4 @@ +using ShippingRates.Models; using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -31,7 +32,7 @@ public Shipment(Address originAddress, Address destinationAddress, List /// /// Total shipment weight /// - public decimal TotalPackageWeight => Packages.Sum(x => x.Weight); + public decimal GetTotalPackageWeight(UnitsSystem unitsSystem = UnitsSystem.USCustomary) => Packages.Sum(x => x.GetWeight(unitsSystem)); /// /// Documents only in the shipment /// @@ -46,7 +47,7 @@ public Shipment(Address originAddress, Address destinationAddress, List public List Errors { get; } = new List(); /// /// Internal library errors during interaction with service provider - /// (e.g. SoapException was trown) + /// (e.g. SoapException was thrown) /// public List InternalErrors { get; } = new List(); } diff --git a/ShippingRates/Models/UPS/ShipmentToRequestAdapter.cs b/ShippingRates/Models/UPS/ShipmentToRequestAdapter.cs deleted file mode 100644 index 3e951a7..0000000 --- a/ShippingRates/Models/UPS/ShipmentToRequestAdapter.cs +++ /dev/null @@ -1,173 +0,0 @@ -using ShippingRates.ShippingProviders; -using System; -using System.Linq; - -namespace ShippingRates.Models.UPS -{ - internal class ShipmentToRequestAdapter - { - public static UPSRatingRequest FromShipment(UPSProviderConfiguration configuration, ShippingRates.Shipment shipment) - { - var request = new UPSRatingRequest() - { - RateRequest = new RateRequest() - { - PickupType = new PickupType() - { - Code = "03" - }, - Shipment = new Shipment() - { - PaymentDetails = new PaymentDetails() - { - ShipmentCharge = new ShipmentCharge() - { - BillShipper = new BillShipper() - { - AccountNumber = configuration.AccountNumber - }, - Type = "01" - } - }, - Shipper = new Shipper() - { - ShipperNumber = configuration.AccountNumber, - Address = FromAddress(shipment.OriginAddress) - }, - ShipFrom = new ShipAddress() - { - Address = FromAddress(shipment.OriginAddress) - }, - ShipTo = new ShipAddress() - { - Address = FromAddress(shipment.DestinationAddress) - }, - NumOfPieces = shipment.Packages.Count, - Package = shipment.Packages.Select(p => FromPackage(p)).ToArray() - } - } - }; - if (!string.IsNullOrEmpty(configuration.ServiceDescription)) - { - request.RateRequest.Shipment.Service = new Service() - { - Code = GetServiceCode(configuration.ServiceDescription) - }; - } - if (shipment.DestinationAddress.IsResidential) - { - request.RateRequest.Shipment.ShipTo.Address.ResidentialAddressIndicator = "Y"; - } - if (shipment.HasDocumentsOnly) - { - request.RateRequest.Shipment.DocumentsOnlyIndicator = "Document"; - } - if (shipment.Options.SaturdayDelivery) - { - request.RateRequest.Shipment.ShipmentServiceOptions = new ShipmentServiceOptions() - { - SaturdayDeliveryIndicator = "Y" - }; - } - if (configuration.UseNegotiatedRates) - { - request.RateRequest.Shipment.ShipmentRatingOptions = new ShipmentRatingOptions() - { - NegotiatedRatesIndicator = "Y" - }; - } - if (shipment.OriginAddress.CountryCode == "US") // Valid if ship from US - { - var code = configuration.UseRetailRates - ? "04" - : (configuration.UseDailyRates ? "01" : "00"); - } - if (shipment.Options.ShippingDate != null) - { - request.RateRequest.Shipment.DeliveryTimeInformation = new DeliveryTimeInformation() - { - PackageBillType = shipment.HasDocumentsOnly ? "02" : "03", - Pickup = new Pickup() - { - Date = shipment.Options.ShippingDate.Value.ToString("yyyyMMdd"), - Time = "1000" - } - }; - } - - return request; - } - - static string GetServiceCode(string serviceDescription) - { - if (serviceDescription.Length == 2) - return serviceDescription; - - var serviceCode = UPSProvider.GetServiceCodes() - .FirstOrDefault(c => c.Value == serviceDescription).Key; - - if (string.IsNullOrEmpty(serviceCode)) - throw new ArgumentException($"Invalid UPS service description {serviceCode}"); - - return serviceCode; - } - - static Package FromPackage(ShippingRates.Package package) - { - var ratesPackage = new Package() - { - PackagingType = new PackagingType() - { - Code = "02" - }, - PackageWeight = new PackageWeight() - { - UnitOfMeasurement = new UnitOfMeasurement() - { - Code = "LBS" - }, - Weight = package.RoundedWeight.ToString() - }, - Dimensions = new Dimensions() - { - UnitOfMeasurement = new UnitOfMeasurement() - { - Code = "IN" - }, - Length = package.RoundedLength.ToString(), - Width = package.RoundedWidth.ToString(), - Height = package.RoundedHeight.ToString() - } - }; - - if (package.SignatureRequiredOnDelivery) - { - ratesPackage.PackageServiceOptions = new PackageServiceOptions() - { - DeliveryConfirmation = new DeliveryConfirmation() - { - DCISType = "2" - } - }; - } - - return ratesPackage; - } - - static Address FromAddress(ShippingRates.Address address) - { - var addressLines = new string[] { address.Line1, address.Line2, address.Line3 } - .Where(s => !string.IsNullOrEmpty(s)) - .ToArray(); - - return new Address() - { - AddressLine = addressLines, - City = address.City, - StateProvinceCode = address.State, - PostalCode = address.PostalCode, - CountryCode = address.CountryCode - }; - } - } -} diff --git a/ShippingRates/Models/UPS/UPSErrorResponse.cs b/ShippingRates/Models/UPS/UPSErrorResponse.cs index a9c30eb..b6b8af7 100644 --- a/ShippingRates/Models/UPS/UPSErrorResponse.cs +++ b/ShippingRates/Models/UPS/UPSErrorResponse.cs @@ -2,19 +2,19 @@ namespace ShippingRates.Models.UPS { - internal class UPSErrorResponse + internal class UpsErrorResponse { [JsonPropertyName("response")] - public UPSErrorResponseBody Response { get; set; } + public UpsErrorResponseBody Response { get; set; } } - class UPSErrorResponseBody + class UpsErrorResponseBody { [JsonPropertyName("errors")] - public UPSErrorItem[] Errors { get; set; } + public UpsErrorItem[] Errors { get; set; } } - class UPSErrorItem + class UpsErrorItem { [JsonPropertyName("code")] public string Code { get; set; } diff --git a/ShippingRates/Models/UPS/UPSRatingRequest.cs b/ShippingRates/Models/UPS/UPSRatingRequest.cs index 0f96232..0175743 100644 --- a/ShippingRates/Models/UPS/UPSRatingRequest.cs +++ b/ShippingRates/Models/UPS/UPSRatingRequest.cs @@ -1,8 +1,9 @@ using System; +using System.Linq; namespace ShippingRates.Models.UPS { - internal class UPSRatingRequest + internal class UpsRatingRequest { public RateRequest RateRequest { get; set; } } @@ -12,7 +13,7 @@ internal class RateRequest public TransactionReference TransactionReference { get; set; } public PickupType PickupType { get; set; } public CustomerClassification CustomerClassification { get; set; } - public Shipment Shipment { get; set; } + public UpsShipment Shipment { get; set; } } internal class TransactionReference @@ -29,128 +30,4 @@ internal class CustomerClassification { public string Code { get; set; } } - - internal class Shipment - { - public Shipper Shipper { get; set; } - public ShipAddress ShipFrom { get; set; } - public ShipAddress ShipTo { get; set; } - public PaymentDetails PaymentDetails { get; set; } - public Service Service { get; set; } - public int NumOfPieces { get; set; } - public string DocumentsOnlyIndicator { get; set; } - public Package[] Package { get; set; } - public ShipmentServiceOptions ShipmentServiceOptions { get; set; } - public ShipmentRatingOptions ShipmentRatingOptions { get; set; } - public DeliveryTimeInformation DeliveryTimeInformation { get; set; } - } - - internal class Shipper - { - public string Name { get; set; } - public string ShipperNumber { get; set; } - public Address Address { get; set; } - } - - internal class ShipAddress - { - public string Name { get; set; } - public Address Address { get; set; } - } - - internal class Address - { - public string[] AddressLine = Array.Empty(); - public string City { get; set; } - public string StateProvinceCode { get; set; } - public string PostalCode { get; set; } - public string CountryCode { get; set; } - public string ResidentialAddressIndicator { get; set; } - } - - internal class Service - { - public string Code { get; set; } - public string Description { get; set; } - } - - internal class PaymentDetails - { - public ShipmentCharge ShipmentCharge { get; set; } - } - - internal class ShipmentCharge - { - public string Type { get; set; } - public BillShipper BillShipper { get; set; } - } - - internal class BillShipper - { - public string AccountNumber { get; set; } - } - - internal class Package - { - public PackagingType PackagingType { get; set; } - public PackageWeight PackageWeight { get; set; } - public Dimensions Dimensions { get; set; } - public PackageServiceOptions PackageServiceOptions { get; set; } - } - - internal class Dimensions - { - public UnitOfMeasurement UnitOfMeasurement { get; set; } - public string Length { get; set; } - public string Width { get; set; } - public string Height { get; set; } - } - - internal class PackageServiceOptions - { - public DeliveryConfirmation DeliveryConfirmation { get; set; } - } - - internal class DeliveryConfirmation - { - public string DCISType { get; set; } - } - - internal class PackageWeight - { - public UnitOfMeasurement UnitOfMeasurement { get; set; } - public string Weight { get; set; } - } - - internal class UnitOfMeasurement - { - public string Code { get; set; } - } - - internal class PackagingType - { - public string Code { get; set; } - } - - internal class ShipmentServiceOptions - { - public string SaturdayDeliveryIndicator { get; set; } - } - - internal class ShipmentRatingOptions - { - public string NegotiatedRatesIndicator { get; set; } - } - - internal class DeliveryTimeInformation - { - public string PackageBillType { get; set; } - public Pickup Pickup { get; set; } - } - - internal class Pickup - { - public string Date { get; set; } - public string Time { get; set; } - } } diff --git a/ShippingRates/Models/UPS/UPSRatingResponse.cs b/ShippingRates/Models/UPS/UPSRatingResponse.cs index 64052a6..e060416 100644 --- a/ShippingRates/Models/UPS/UPSRatingResponse.cs +++ b/ShippingRates/Models/UPS/UPSRatingResponse.cs @@ -1,16 +1,16 @@ namespace ShippingRates.Models.UPS { - internal class UPSRatingResponse + internal class UpsRatingResponse { public RateResponse RateResponse { get; set; } } - internal class UPSSingleRatingResponse + internal class UpsSingleRatingResponse { public SingleRateResponse RateResponse { get; set; } - public UPSRatingResponse GetRatesResponse() + public UpsRatingResponse GetRatesResponse() { - return new UPSRatingResponse() + return new UpsRatingResponse() { RateResponse = new RateResponse() { diff --git a/ShippingRates/Models/UPS/UpsAddress.cs b/ShippingRates/Models/UPS/UpsAddress.cs new file mode 100644 index 0000000..2f3238c --- /dev/null +++ b/ShippingRates/Models/UPS/UpsAddress.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; + +namespace ShippingRates.Models.UPS +{ + internal class UpsAddress + { + public string[] AddressLine = Array.Empty(); + public string City { get; set; } + public string StateProvinceCode { get; set; } + public string PostalCode { get; set; } + public string CountryCode { get; set; } + public string ResidentialAddressIndicator { get; set; } + + public UpsAddress(Address address) + { + var addressLines = new string[] { address.Line1, address.Line2, address.Line3 } + .Where(s => !string.IsNullOrEmpty(s)) + .ToArray(); + + AddressLine = addressLines; + City = address.City; + StateProvinceCode = address.State; + PostalCode = address.PostalCode; + CountryCode = address.CountryCode; + } + } +} diff --git a/ShippingRates/Models/UPS/UpsPackage.cs b/ShippingRates/Models/UPS/UpsPackage.cs new file mode 100644 index 0000000..40fae25 --- /dev/null +++ b/ShippingRates/Models/UPS/UpsPackage.cs @@ -0,0 +1,86 @@ +namespace ShippingRates.Models.UPS +{ + internal class UpsPackage + { + public PackagingType PackagingType { get; set; } + public UpsPackageWeight PackageWeight { get; set; } + public UpsDimensions Dimensions { get; set; } + public PackageServiceOptions PackageServiceOptions { get; set; } + + public UpsPackage(Package package, UnitsSystem unitsSystem) + { + PackagingType = new PackagingType() + { + Code = "02" + }; + PackageWeight = new UpsPackageWeight(package, unitsSystem); + Dimensions = new UpsDimensions(package, unitsSystem); + + if (package.SignatureRequiredOnDelivery) + { + PackageServiceOptions = new PackageServiceOptions() + { + DeliveryConfirmation = new DeliveryConfirmation() + { + DCISType = "2" + } + }; + } + } + } + + internal class UpsDimensions + { + public UpsDimensions(Package package, UnitsSystem unitsSystem) + { + UnitOfMeasurement = new UnitOfMeasurement() + { + Code = unitsSystem == UnitsSystem.Metric ? "CM" : "IN" + }; + Length = package.GetRoundedLength(unitsSystem).ToString(); + Width = package.GetRoundedWidth(unitsSystem).ToString(); + Height = package.GetRoundedHeight(unitsSystem).ToString(); + } + + public UnitOfMeasurement UnitOfMeasurement { get; set; } + public string Length { get; set; } + public string Width { get; set; } + public string Height { get; set; } + } + + internal class PackageServiceOptions + { + public DeliveryConfirmation DeliveryConfirmation { get; set; } + } + + internal class DeliveryConfirmation + { + public string DCISType { get; set; } + } + + internal class UpsPackageWeight + { + public UpsPackageWeight(Package package, UnitsSystem unitsSystem) + { + UnitOfMeasurement = new UnitOfMeasurement() + { + Code = unitsSystem == UnitsSystem.Metric ? "KGS" : "LBS" + }; + Weight = package.GetRoundedWeight(unitsSystem).ToString(); + } + + public UnitOfMeasurement UnitOfMeasurement { get; set; } + public string Weight { get; set; } + } + + internal class UnitOfMeasurement + { + public string Code { get; set; } + } + + internal class PackagingType + { + public string Code { get; set; } + } + +} diff --git a/ShippingRates/Models/UPS/UpsShipment.cs b/ShippingRates/Models/UPS/UpsShipment.cs new file mode 100644 index 0000000..c04db93 --- /dev/null +++ b/ShippingRates/Models/UPS/UpsShipment.cs @@ -0,0 +1,78 @@ +namespace ShippingRates.Models.UPS +{ + internal class UpsShipment + { + public Shipper Shipper { get; set; } + public ShipAddress ShipFrom { get; set; } + public ShipAddress ShipTo { get; set; } + public PaymentDetails PaymentDetails { get; set; } + public Service Service { get; set; } + public int NumOfPieces { get; set; } + public string DocumentsOnlyIndicator { get; set; } + public UpsPackage[] Package { get; set; } + public ShipmentServiceOptions ShipmentServiceOptions { get; set; } + public ShipmentRatingOptions ShipmentRatingOptions { get; set; } + public DeliveryTimeInformation DeliveryTimeInformation { get; set; } + } + internal class Shipper + { + public string Name { get; set; } + public string ShipperNumber { get; set; } + public UpsAddress Address { get; set; } + } + + internal class ShipAddress + { + public string Name { get; set; } + public UpsAddress Address { get; set; } + + public ShipAddress(UpsAddress address) + { + Address = address; + } + } + + internal class Service + { + public string Code { get; set; } + public string Description { get; set; } + } + + internal class PaymentDetails + { + public ShipmentCharge ShipmentCharge { get; set; } + } + + internal class ShipmentCharge + { + public string Type { get; set; } + public BillShipper BillShipper { get; set; } + } + + internal class BillShipper + { + public string AccountNumber { get; set; } + } + + internal class ShipmentServiceOptions + { + public string SaturdayDeliveryIndicator { get; set; } + } + + internal class ShipmentRatingOptions + { + public string NegotiatedRatesIndicator { get; set; } + } + + internal class DeliveryTimeInformation + { + public string PackageBillType { get; set; } + public Pickup Pickup { get; set; } + } + + internal class Pickup + { + public string Date { get; set; } + public string Time { get; set; } + } +} diff --git a/ShippingRates/Models/UnitsSystem.cs b/ShippingRates/Models/UnitsSystem.cs new file mode 100644 index 0000000..8f82999 --- /dev/null +++ b/ShippingRates/Models/UnitsSystem.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ShippingRates.Models +{ + public enum UnitsSystem + { + Metric, + USCustomary + } +} diff --git a/ShippingRates/Services/UPSOAuthService.cs b/ShippingRates/Services/UPSOAuthService.cs index 99ae150..70a5b6a 100644 --- a/ShippingRates/Services/UPSOAuthService.cs +++ b/ShippingRates/Services/UPSOAuthService.cs @@ -47,7 +47,7 @@ public static async Task GetTokenAsync(UPSProviderConfiguration configur } else { - var errorResponse = JsonSerializer.Deserialize(response); + var errorResponse = JsonSerializer.Deserialize(response); if ((errorResponse?.Response?.Errors?.Length ?? 0) > 0) { foreach (var error in errorResponse.Response.Errors) diff --git a/ShippingRates/Services/UPSRatingService.cs b/ShippingRates/Services/UPSRatingService.cs index 2486bee..9091278 100644 --- a/ShippingRates/Services/UPSRatingService.cs +++ b/ShippingRates/Services/UPSRatingService.cs @@ -1,72 +1,32 @@ using ShippingRates.Models.UPS; using System; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; using System.Threading.Tasks; namespace ShippingRates.Services { - internal class UPSRatingService + internal class UpsRatingService : UpsBaseService { const string Version = "v2403"; - static string GetRequestUri(bool isRateRequest, bool isProduction) - => $"https://{(isProduction ? "onlinetools" : "wwwcie")}.ups.com/api/rating/{Version}/{(isRateRequest ? "Rate" : "Shop")}"; + static Uri GetRequestUri(bool isRateRequest, bool isProduction) + => new Uri($"https://{(isProduction ? "onlinetools" : "wwwcie")}.ups.com/api/rating/{Version}/{(isRateRequest ? "Rate" : "Shop")}"); - static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions() - { - DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull - }; - - public static async Task GetRatingAsync(HttpClient httpClient, string token, bool isProduction, UPSRatingRequest request, Action reportError) + public static async Task GetRatingAsync(HttpClient httpClient, string token, bool isProduction, UpsRatingRequest request, Action reportError) { request = request ?? throw new ArgumentNullException(nameof(request)); var isRateRequest = !string.IsNullOrEmpty(request.RateRequest?.Shipment?.Service?.Code); + var uri = GetRequestUri(isRateRequest, isProduction); - var requestMessage = new HttpRequestMessage(HttpMethod.Post, GetRequestUri(isRateRequest, isProduction)); - requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); - - var jsonRequest = JsonSerializer.Serialize(request, _jsonSerializerOptions); - requestMessage.Content = new StringContent(jsonRequest, Encoding.UTF8, "application/json"); - - var responseMessage = await httpClient.SendAsync(requestMessage); - var response = await responseMessage.Content.ReadAsStringAsync(); - - if (responseMessage.IsSuccessStatusCode) + if (isRateRequest) { - if (isRateRequest) - { - var singleRateResponse = JsonSerializer.Deserialize(response); - return singleRateResponse.GetRatesResponse(); - } - else - { - return JsonSerializer.Deserialize(response); - } + var singleRateResponse = await PostAsync(httpClient, token, uri, request, reportError); + return singleRateResponse?.GetRatesResponse(); } else { - var errorResponse = JsonSerializer.Deserialize(response); - if ((errorResponse?.Response?.Errors?.Length ?? 0) > 0) - { - foreach (var error in errorResponse.Response.Errors) - { - reportError(new Error() - { - Number = error.Code, - Description = error.Message - }); - } - } - else - { - reportError(new Error() { Description = $"Unknown error while fetching UPS ratings: {responseMessage.StatusCode} {response}" }); - } - - return null; + return await PostAsync(httpClient, token, uri, request, reportError); } } } diff --git a/ShippingRates/Services/UpsBaseService.cs b/ShippingRates/Services/UpsBaseService.cs new file mode 100644 index 0000000..8ccfbbc --- /dev/null +++ b/ShippingRates/Services/UpsBaseService.cs @@ -0,0 +1,59 @@ +using ShippingRates.Models.UPS; +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; + +namespace ShippingRates.Services +{ + internal class UpsBaseService + { + static readonly JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions() + { + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull + }; + + protected static async Task PostAsync(HttpClient httpClient, string token, Uri uri, TRequest request, Action reportError) + { + if (request == null) + throw new ArgumentNullException(nameof(request)); + + var requestMessage = new HttpRequestMessage(HttpMethod.Post, uri); + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); + + var jsonRequest = JsonSerializer.Serialize(request, _jsonSerializerOptions); + requestMessage.Content = new StringContent(jsonRequest, Encoding.UTF8, "application/json"); + + var responseMessage = await httpClient.SendAsync(requestMessage); + var response = await responseMessage.Content.ReadAsStringAsync(); + + if (responseMessage.IsSuccessStatusCode) + { + return JsonSerializer.Deserialize(response); + } + else + { + var errorResponse = JsonSerializer.Deserialize(response); + if ((errorResponse?.Response?.Errors?.Length ?? 0) > 0) + { + foreach (var error in errorResponse.Response.Errors) + { + reportError(new Error() + { + Number = error.Code, + Description = error.Message + }); + } + } + else + { + reportError(new Error() { Description = $"Unknown error while fetching UPS ratings: {responseMessage.StatusCode} {response}" }); + } + + return default; + } + } + } +} diff --git a/ShippingRates/ShippingProviders/DHLProvider.cs b/ShippingRates/ShippingProviders/DHLProvider.cs index e1cd02f..891d3e5 100644 --- a/ShippingRates/ShippingProviders/DHLProvider.cs +++ b/ShippingRates/ShippingProviders/DHLProvider.cs @@ -135,7 +135,7 @@ private string BuildRatesRequestMessage( 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("DimensionUnit", "IN"); // TODO: Add support for CM and KG writer.WriteElementString("WeightUnit", "LB"); writer.WriteStartElement("Pieces"); @@ -143,10 +143,10 @@ private string BuildRatesRequestMessage( { 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.WriteElementString("Height", Shipment.Packages[i].GetRoundedHeight(Models.UnitsSystem.USCustomary).ToString(requestCulture)); + writer.WriteElementString("Depth", Shipment.Packages[i].GetRoundedLength(Models.UnitsSystem.USCustomary).ToString(requestCulture)); + writer.WriteElementString("Width", Shipment.Packages[i].GetRoundedWidth(Models.UnitsSystem.USCustomary).ToString(requestCulture)); + writer.WriteElementString("Weight", Shipment.Packages[i].GetRoundedWeight(Models.UnitsSystem.USCustomary).ToString(requestCulture)); writer.WriteEndElement(); // } writer.WriteEndElement(); // diff --git a/ShippingRates/ShippingProviders/FedExBaseProvider.cs b/ShippingRates/ShippingProviders/FedExBaseProvider.cs index 11eae5e..4d94ff0 100644 --- a/ShippingRates/ShippingProviders/FedExBaseProvider.cs +++ b/ShippingRates/ShippingProviders/FedExBaseProvider.cs @@ -263,19 +263,19 @@ protected void SetPackageLineItems(RateRequest request) // Package weight Weight = new Weight() { - Units = WeightUnits.LB, + Units = WeightUnits.LB, // TODO: Add support for CM and KG UnitsSpecified = true, - Value = package.RoundedWeight, + Value = Shipment.Packages[i].GetRoundedWeight(Models.UnitsSystem.USCustomary), ValueSpecified = true }, // Package dimensions Dimensions = new Dimensions() { - Length = package.RoundedLength.ToString(), - Width = package.RoundedWidth.ToString(), - Height = package.RoundedHeight.ToString(), - Units = LinearUnits.IN, + Length = Shipment.Packages[i].GetRoundedLength(Models.UnitsSystem.USCustomary).ToString(), + Width = Shipment.Packages[i].GetRoundedWidth(Models.UnitsSystem.USCustomary).ToString(), + Height = Shipment.Packages[i].GetRoundedHeight(Models.UnitsSystem.USCustomary).ToString(), + Units = LinearUnits.IN, // TODO: Add support for CM and KG UnitsSpecified = true } }; diff --git a/ShippingRates/ShippingProviders/UPSProvider.cs b/ShippingRates/ShippingProviders/UPSProvider.cs index 245cc78..b47cec3 100644 --- a/ShippingRates/ShippingProviders/UPSProvider.cs +++ b/ShippingRates/ShippingProviders/UPSProvider.cs @@ -1,7 +1,9 @@ -using ShippingRates.Models.UPS; +using ShippingRates.Models; +using ShippingRates.Models.UPS; using ShippingRates.Services; using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; @@ -61,8 +63,8 @@ public override async Task GetRates() if (!string.IsNullOrEmpty(token)) { - var request = ShipmentToRequestAdapter.FromShipment(_configuration, Shipment); - var ratingsResponse = await UPSRatingService.GetRatingAsync(httpClient, token, _configuration.UseProduction, request, AddError); + var request = GetRequest(); + var ratingsResponse = await UpsRatingService.GetRatingAsync(httpClient, token, _configuration.UseProduction, request, AddError); ParseResponse(ratingsResponse); } } @@ -77,7 +79,96 @@ public override async Task GetRates() } } - private void ParseResponse(UPSRatingResponse response) + private UpsRatingRequest GetRequest() + { + var shipFromUS = Shipment.OriginAddress.CountryCode == "US"; + var unitsSystem = shipFromUS ? UnitsSystem.USCustomary : UnitsSystem.Metric; + + var request = new UpsRatingRequest() + { + RateRequest = new RateRequest() + { + PickupType = new PickupType() + { + Code = "03" + }, + Shipment = new UpsShipment() + { + PaymentDetails = new PaymentDetails() + { + ShipmentCharge = new ShipmentCharge() + { + BillShipper = new BillShipper() + { + AccountNumber = _configuration.AccountNumber + }, + Type = "01" + } + }, + Shipper = new Shipper() + { + ShipperNumber = _configuration.AccountNumber, + Address = new UpsAddress(Shipment.OriginAddress) + }, + ShipFrom = new ShipAddress(new UpsAddress(Shipment.OriginAddress)), + ShipTo = new ShipAddress(new UpsAddress(Shipment.DestinationAddress)), + NumOfPieces = Shipment.Packages.Count, + Package = Shipment.Packages.Select(p => new UpsPackage(p, unitsSystem)).ToArray() + } + } + }; + if (!string.IsNullOrEmpty(_configuration.ServiceDescription)) + { + request.RateRequest.Shipment.Service = new Service() + { + Code = GetServiceCode(_configuration.ServiceDescription) + }; + } + if (Shipment.DestinationAddress.IsResidential) + { + request.RateRequest.Shipment.ShipTo.Address.ResidentialAddressIndicator = "Y"; + } + if (Shipment.HasDocumentsOnly) + { + request.RateRequest.Shipment.DocumentsOnlyIndicator = "Document"; + } + if (Shipment.Options.SaturdayDelivery) + { + request.RateRequest.Shipment.ShipmentServiceOptions = new ShipmentServiceOptions() + { + SaturdayDeliveryIndicator = "Y" + }; + } + if (_configuration.UseNegotiatedRates) + { + request.RateRequest.Shipment.ShipmentRatingOptions = new ShipmentRatingOptions() + { + NegotiatedRatesIndicator = "Y" + }; + } + if (shipFromUS) // Valid if ship from US + { + var code = _configuration.UseRetailRates + ? "04" + : (_configuration.UseDailyRates ? "01" : "00"); + } + if (Shipment.Options.ShippingDate != null) + { + request.RateRequest.Shipment.DeliveryTimeInformation = new DeliveryTimeInformation() + { + PackageBillType = Shipment.HasDocumentsOnly ? "02" : "03", + Pickup = new Pickup() + { + Date = Shipment.Options.ShippingDate.Value.ToString("yyyyMMdd"), + Time = "1000" + } + }; + } + + return request; + } + + private void ParseResponse(UpsRatingResponse response) { if (response?.RateResponse?.RatedShipment == null) return; @@ -127,6 +218,19 @@ private void ParseResponse(UPSRatingResponse response) } } + static string GetServiceCode(string serviceDescription) + { + if (serviceDescription.Length == 2) + return serviceDescription; + + var serviceCode = _serviceCodes.FirstOrDefault(c => c.Value == serviceDescription).Key; + + if (string.IsNullOrEmpty(serviceCode)) + throw new ArgumentException($"Invalid UPS service description {serviceCode}"); + + return serviceCode; + } + public static IDictionary GetServiceCodes() => _serviceCodes; } } diff --git a/ShippingRates/ShippingProviders/USPSInternationalProvider.cs b/ShippingRates/ShippingProviders/USPSInternationalProvider.cs index a501980..8b5e27c 100644 --- a/ShippingRates/ShippingProviders/USPSInternationalProvider.cs +++ b/ShippingRates/ShippingProviders/USPSInternationalProvider.cs @@ -149,10 +149,10 @@ private string GetRequestXmlString() writer.WriteElementString("ValueOfContents", package.InsuredValue.ToString()); writer.WriteElementString("Country", Shipment.DestinationAddress.GetCountryName()); writer.WriteElementString("Container", string.IsNullOrEmpty(package.Container) ? "RECTANGULAR" : package.Container); - writer.WriteElementString("Width", package.RoundedWidth.ToString()); - writer.WriteElementString("Length", package.RoundedLength.ToString()); - writer.WriteElementString("Height", package.RoundedHeight.ToString()); - writer.WriteElementString("Girth", package.CalculatedGirth.ToString()); + writer.WriteElementString("Width", package.GetRoundedWidth(Models.UnitsSystem.USCustomary).ToString()); + writer.WriteElementString("Length", package.GetRoundedLength(Models.UnitsSystem.USCustomary).ToString()); + writer.WriteElementString("Height", package.GetRoundedHeight(Models.UnitsSystem.USCustomary).ToString()); + writer.WriteElementString("Girth", package.GetCalculatedGirth(Models.UnitsSystem.USCustomary).ToString()); writer.WriteElementString("OriginZip", Shipment.OriginAddress.PostalCode); writer.WriteElementString("CommercialFlag", Commercial ? "Y" : "N"); diff --git a/ShippingRates/ShippingProviders/USPSProvider.cs b/ShippingRates/ShippingProviders/USPSProvider.cs index a5c7025..92cfec8 100644 --- a/ShippingRates/ShippingProviders/USPSProvider.cs +++ b/ShippingRates/ShippingProviders/USPSProvider.cs @@ -1,3 +1,4 @@ +using ShippingRates.Models; using ShippingRates.ShippingProviders.USPS; using System; using System.Collections.Generic; @@ -177,10 +178,10 @@ private string GetRequestXmlString(List specialServices) writer.WriteElementString("Ounces", package.PoundsAndOunces.Ounces.ToString()); writer.WriteElementString("Container", string.IsNullOrEmpty(package.Container) ? string.Empty : package.Container); - writer.WriteElementString("Width", package.RoundedWidth.ToString()); - writer.WriteElementString("Length", package.RoundedLength.ToString()); - writer.WriteElementString("Height", package.RoundedHeight.ToString()); - writer.WriteElementString("Girth", package.CalculatedGirth.ToString()); + writer.WriteElementString("Width", package.GetRoundedWidth(Models.UnitsSystem.USCustomary).ToString()); + writer.WriteElementString("Length", package.GetRoundedLength(Models.UnitsSystem.USCustomary).ToString()); + writer.WriteElementString("Height", package.GetRoundedHeight(Models.UnitsSystem.USCustomary).ToString()); + writer.WriteElementString("Girth", package.GetCalculatedGirth(Models.UnitsSystem.USCustomary).ToString()); writer.WriteElementString("Value", package.InsuredValue.ToString()); if (RequiresMachinable(_configuration.Service)) { @@ -242,19 +243,26 @@ public bool IsDomesticUSPSAvailable() public static bool IsPackageLarge(Package package) { package = package ?? throw new ArgumentNullException(nameof(package)); - return package.IsOversize || package.Width > 12 || package.Length > 12 || package.Height > 12; + return package.IsOversize + || package.GetWidth(UnitsSystem.USCustomary) > 12 + || package.GetLength(UnitsSystem.USCustomary) > 12 + || package.GetHeight(UnitsSystem.USCustomary) > 12; } public static bool IsPackageMachinable(Package package) { package = package ?? throw new ArgumentNullException(nameof(package)); // Machinable parcels cannot be larger than 27 x 17 x 17 and cannot weight more than 25 lbs. - if (package.Weight > 25) + if (package.GetWeight(UnitsSystem.USCustomary) > 25) { return false; } - return (package.Width <= 27 && package.Height <= 17 && package.Length <= 17) || (package.Width <= 17 && package.Height <= 27 && package.Length <= 17) || (package.Width <= 17 && package.Height <= 17 && package.Length <= 27); + var width = package.GetWidth(UnitsSystem.USCustomary); + var height = package.GetHeight(UnitsSystem.USCustomary); + var length = package.GetLength(UnitsSystem.USCustomary); + + return (width <= 27 && height <= 17 && length <= 17) || (width <= 17 && height <= 27 && length <= 17) || (width <= 17 && height <= 17 && length <= 27); } private void ParseResult(string response, IList includeSpecialServiceCodes = null)