The 1.4 version of USPSComputationMethod.cs is very broken when it comes to multi-package support (including a TODO comment that says as much). I've updated it, but I don't know how relavent it is to NC1.5, so I'll post it here:
// USPSComputationMethod.cs
//------------------------------------------------------------------------------
// The contents of this file are subject to the nopCommerce Public License Version 1.0 ("License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at https://www.nopcommerce.com/License.aspx.
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is nopCommerce.
// The Initial Developer of the Original Code is NopSolutions.
// All Rights Reserved.
//
// Contributor(s): RJH 08/07/2009.
// csells 5/13/2010.
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml;
using NopSolutions.NopCommerce.BusinessLogic;
using NopSolutions.NopCommerce.BusinessLogic.Configuration.Settings;
using NopSolutions.NopCommerce.BusinessLogic.Measures;
using NopSolutions.NopCommerce.BusinessLogic.Shipping;
using NopSolutions.NopCommerce.Common;
namespace NopSolutions.NopCommerce.Shipping.Methods.USPS {
/// <summary>
/// US Postal Service computation method
/// </summary>
public class USPSComputationMethod : IShippingRateComputationMethod {
#region Const
private const decimal MAXPACKAGEWEIGHT = 70;
//private const decimal MAXPACKAGESIZE = 130; // Oversize doesn't work for Priority or Express
private const decimal MAXPACKAGESIZE = 108; // Large is the largest we can do
#endregion
#region Utilities
private string CreateRequest(string Username, string Password, ShipmentPackage ShipmentPackage, out int totalPackages) {
decimal length = ShipmentPackage.GetTotalLength();
decimal height = Math.Ceiling(ShipmentPackage.GetTotalHeight());
decimal width = Math.Ceiling(ShipmentPackage.GetTotalWidth());
decimal weight = ShippingManager.GetShoppingCartTotalWeigth(ShipmentPackage.Items);
string zipPostalCodeFrom = ShipmentPackage.ZipPostalCodeFrom;
string zipPostalCodeTo = ShipmentPackage.ShippingAddress.ZipPostalCode;
//TODO convert measure weight
MeasureWeight baseWeightIn = MeasureManager.BaseWeightIn;
if (baseWeightIn.SystemKeyword != "lb")
throw new NopException("USPS shipping service. Base weight should be set to lb(s)");
//TODO convert measure dimension
MeasureDimension baseDimensionIn = MeasureManager.BaseDimensionIn;
if (baseDimensionIn.SystemKeyword != "inches")
throw new NopException("USPS shipping service. Base dimension should be set to inch(es)");
// NOTE: round up to next heavier pounds for USPS
//decimal pounds = Convert.ToInt32(Math.Truncate(weight));
//decimla ounces = Convert.ToInt32((weight - pounds) * 16.0M);
decimal pounds = Math.Ceiling(weight);
decimal ounces = 0;
StringBuilder sb = new StringBuilder();
sb.AppendFormat("<RateV3Request USERID=\"{0}\" PASSWORD=\"{1}\">", Username, Password);
var packageSize = USPSPackageSize.TooLarge;
if (IsPackageTooHeavy(pounds) || IsPackageTooLarge(length, height, width)) {
decimal totalPackagesSize = TotalPackageSize(length, height, width);
int totalPackagesByDims = Convert.ToInt32(Math.Ceiling(totalPackagesSize / MAXPACKAGESIZE));
int totalPackagesByWeight = Convert.ToInt32(Math.Ceiling(pounds / MAXPACKAGEWEIGHT));
totalPackages = Math.Max(Math.Max(totalPackagesByDims, totalPackagesByWeight), 1);
pounds = Math.Ceiling(pounds / (decimal)totalPackages);
ounces = Math.Ceiling(ounces / (decimal)totalPackages);
packageSize = GetPackageSize(totalPackagesSize / (decimal)totalPackages);
}
else {
totalPackages = 1;
packageSize = GetPackageSize(length, height, width);
}
// NOTE: this shouldn't happen
if (packageSize == USPSPackageSize.TooLarge) { throw new NopException("Shipping Error: Package too large."); }
// NOTE: instead of filtering by service from USPSStrings.Elements the way the original code was doing,
// we're asking for All potential shipping service rates to be calculated. This is because USPS errors on
// certain size/weight/service combindations. On the other hand, if we just give it size/weight info,
// it'll tell us which services are available, which we then post-filter using USPSStrings.Names.
sb.Append("<Package ID=\"0\">");
sb.Append("<Service>All</Service>");
sb.AppendFormat("<ZipOrigination>{0}</ZipOrigination>", zipPostalCodeFrom);
sb.AppendFormat("<ZipDestination>{0}</ZipDestination>", zipPostalCodeTo);
sb.AppendFormat("<Pounds>{0}</Pounds>", pounds);
sb.AppendFormat("<Ounces>{0}</Ounces>", ounces);
sb.AppendFormat("<Size>{0}</Size>", packageSize);
sb.Append("<Machinable>FALSE</Machinable>");
sb.Append("</Package>");
sb.Append("</RateV3Request>");
return "API=RateV3&XML=" + sb.ToString();
}
private string DoRequest(string URL, string RequestString) {
byte[] bytes = new ASCIIEncoding().GetBytes(RequestString);
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = bytes.Length;
Stream requestStream = request.GetRequestStream();
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
WebResponse response = request.GetResponse();
string responseXML = string.Empty;
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
responseXML = reader.ReadToEnd();
return responseXML;
}
private bool IsPackageTooLarge(decimal length, decimal height, decimal width) {
return TotalPackageSize(length, height, width) > MAXPACKAGESIZE;
}
private decimal TotalPackageSize(decimal length, decimal height, decimal width) {
return (height + height + width + width) + length; // girth + length
}
private bool IsPackageTooHeavy(decimal weight) {
return weight > MAXPACKAGEWEIGHT;
}
private USPSPackageSize GetPackageSize(decimal length, decimal height, decimal width) {
return GetPackageSize(TotalPackageSize(length, height, width));
}
private USPSPackageSize GetPackageSize(decimal total) {
if (total <= 84) { return USPSPackageSize.Regular; }
else if ((total > 84) && (total <= 108)) { return USPSPackageSize.Large; }
else if ((total > 108) && (total <= 130)) { return USPSPackageSize.Oversize; }
else { return USPSPackageSize.TooLarge; }
}
private ShippingOptionCollection ParseResponse(string response, ref string error) {
ShippingOptionCollection shippingOptions = new ShippingOptionCollection();
using (StringReader sr = new StringReader(response))
using (XmlTextReader tr = new XmlTextReader(sr))
do {
// Read the next XML record
tr.Read();
if ((tr.Name == "Error") && (tr.NodeType == XmlNodeType.Element)) {
string errorText = "";
while (tr.Read()) {
if ((tr.Name == "Description") && (tr.NodeType == XmlNodeType.Element))
errorText += "Error Desc: " + tr.ReadString();
if ((tr.Name == "HelpContext") && (tr.NodeType == XmlNodeType.Element))
errorText += "USPS Help Context: " + tr.ReadString() + ". ";
}
error = "USPS Error returned: " + errorText;
}
// Process the inner postage XML
if ((tr.Name == "Postage") && (tr.NodeType == XmlNodeType.Element)) {
string serviceCode = "";
string postalRate = "";
do {
tr.Read();
if ((tr.Name == "MailService") && (tr.NodeType == XmlNodeType.Element)) {
serviceCode = tr.ReadString();
tr.ReadEndElement();
if ((tr.Name == "MailService") && (tr.NodeType == XmlNodeType.EndElement))
break;
}
// if (((tr.Name == "Postage") && (tr.NodeType == XmlNodeType.EndElement)) || ((tr.Name == "Postage") && (tr.NodeType == XmlNodeType.Element)))
// break;
if ((tr.Name == "Rate") && (tr.NodeType == XmlNodeType.Element)) {
postalRate = tr.ReadString();
tr.ReadEndElement();
if ((tr.Name == "Rate") && (tr.NodeType == XmlNodeType.EndElement))
break;
}
} while (!((tr.Name == "Postage") && (tr.NodeType == XmlNodeType.EndElement)));
if (shippingOptions.Find((s) => s.Name == serviceCode) == null) {
ShippingOption shippingOption = new ShippingOption();
shippingOption.Rate = Convert.ToDecimal(postalRate, new CultureInfo("en-US"));
shippingOption.Name = serviceCode;
shippingOptions.Add(shippingOption);
}
}
} while (!tr.EOF);
return shippingOptions;
}
#endregion
#region Methods
/// <summary>
/// Gets available shipping options
/// </summary>
/// <param name="ShipmentPackage">Shipment option</param>
/// <param name="Error">Error</param>
/// <returns>Shipping options</returns>
public ShippingOptionCollection GetShippingOptions(ShipmentPackage ShipmentPackage, ref string Error) {
ShippingOptionCollection shippingOptions = new ShippingOptionCollection();
if (ShipmentPackage == null)
throw new ArgumentNullException("ShipmentPackage");
if (ShipmentPackage.Items == null)
throw new NopSolutions.NopCommerce.Common.NopException("No shipment items");
if (ShipmentPackage.ShippingAddress == null) {
Error = "Shipping address is not set";
return shippingOptions;
}
string url = SettingManager.GetSettingValue("ShippingRateComputationMethod.USPS.URL");
string username = SettingManager.GetSettingValue("ShippingRateComputationMethod.USPS.Username");
string password = SettingManager.GetSettingValue("ShippingRateComputationMethod.USPS.Password");
decimal additionalHandlingCharge = SettingManager.GetSettingValueDecimalNative("ShippingRateComputationMethod.USPS.AdditionalHandlingCharge");
ShipmentPackage.ZipPostalCodeFrom = SettingManager.GetSettingValue("ShippingRateComputationMethod.USPS.DefaultShippedFromZipPostalCode");
int totalPackages;
string requestString = CreateRequest(username, password, ShipmentPackage, out totalPackages);
string responseXML = DoRequest(url, requestString);
shippingOptions = new ShippingOptionCollection();
// filter for allowed options and adjust rates based on total number of packages
USPSStrings uspsStrings = new USPSStrings();
foreach (var option in ParseResponse(responseXML, ref Error)) {
if (uspsStrings.Names.Any(service => string.Compare(service, option.Name, true) == 0)) {
shippingOptions.Add(option);
option.Rate = option.Rate * totalPackages + additionalHandlingCharge;
}
}
if (String.IsNullOrEmpty(Error) && shippingOptions.Count == 0)
Error = "Shipping options could not be loaded";
return shippingOptions;
}
/// <summary>
/// Gets fixed shipping rate (if shipping rate computation method allows it and the rate can be calculated before checkout).
/// </summary>
/// <param name="ShipmentPackage">Shipment package</param>
/// <returns>Fixed shipping rate; or null if shipping rate could not be calculated before checkout</returns>
public decimal? GetFixedRate(ShipmentPackage ShipmentPackage) {
return null;
}
#endregion
}
}
Also, my USPSPackageSize.cs and USPSStrings.cs files are different, too:
//USPSPackageSize.cs
//------------------------------------------------------------------------------
// The contents of this file are subject to the nopCommerce Public License Version 1.0 ("License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at https://www.nopcommerce.com/License.aspx.
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is nopCommerce.
// The Initial Developer of the Original Code is NopSolutions.
// All Rights Reserved.
//
// Contributor(s): _______.
// csells 5/13/2010.
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml;
using NopSolutions.NopCommerce.BusinessLogic.Shipping;
namespace NopSolutions.NopCommerce.Shipping.Methods.USPS {
internal enum USPSPackageSize {
Regular,
Large,
Oversize,
TooLarge,
}
}
// USPSStrings.cs
//------------------------------------------------------------------------------
// The contents of this file are subject to the nopCommerce Public License Version 1.0 ("License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at https://www.nopcommerce.com/License.aspx.
//
// Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is nopCommerce.
// The Initial Developer of the Original Code is NopSolutions.
// All Rights Reserved.
//
// Contributor(s): RJH 08/07/2009.
// csells 5/13/2010.
//------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml;
using NopSolutions.NopComme