/// <summary>
        /// Get shipping method rates.
        /// </summary>
        ShippingMethodCollection GetRates(RealTimeRateCalculationContext context)
        {
            // Build shipments
            var shipments = BuildShipments(context);

            // Set shipment info
            var realTimeShipping = new RTShipping
            {
                ShipmentWeight           = GetHeaviestPackageWeightTotal(context.CartItems),
                DestinationResidenceType = context.ShipmentAddress.ResidenceType,
                ShipmentValue            = context.ShipmentValue,
            };

            // Get carriers
            var carriers = shipments.Any()
                                ? shipments.IsInternational
                                        ? AppLogic.AppConfig("RTShipping.InternationalCarriers")
                                        : AppLogic.AppConfig("RTShipping.DomesticCarriers")
                                : AppLogic.AppConfig("RTShipping.ActiveCarrier");

            if (string.IsNullOrWhiteSpace(carriers))
            {
                carriers = AppLogic.AppConfig("RTShipping.ActiveCarrier");
            }

            // Get results
            string rtShipRequest, rtShipResponse;
            var    shippingMethods = (ShippingMethodCollection)realTimeShipping.GetRates(
                shipments,
                carriers,
                context.ShippingTaxRate,
                out rtShipRequest,
                out rtShipResponse,
                context.ShippingHandlingExtraFee,
                AppLogic.AppConfigUSDecimal("RTShipping.MarkupPercent"),
                realTimeShipping.ShipmentValue);

            DB.ExecuteSQL(
                "update customer set RTShipRequest = @rtShipRequest, RTShipResponse = @rtShipResponse where CustomerID = @customerId",
                new SqlParameter("rtShipRequest", rtShipRequest),
                new SqlParameter("rtShipResponse", rtShipResponse),
                new SqlParameter("customerId", context.CustomerId));

            return(shippingMethods);
        }
        Shipments BuildShipments(RealTimeRateCalculationContext context)
        {
            var useDistributors = context.CartItems.HasDistributorComponents() && AppLogic.AppConfigBool("RTShipping.MultiDistributorCalculation");
            var freeShippingAllowsRateSelection = AppLogic.AppConfigBool("FreeShippingAllowsRateSelection");

            var shipments = new Shipments
            {
                HasDistributorItems = useDistributors,
            };

            // Get ship separately cart items
            var packageId = 1;

            foreach (var cartItem in context.CartItems.Where(ci => ci.IsShipSeparately))
            {
                // Only calculate rates for products that require shipping
                if (cartItem.IsDownload ||
                    cartItem.IsSystem ||
                    !cartItem.Shippable ||
                    GiftCard.ProductIsEmailGiftCard(cartItem.ProductID) ||
                    (cartItem.FreeShipping && !freeShippingAllowsRateSelection))
                {
                    continue;
                }

                var packages = new Packages();
                ApplyDestinationForAddress(packages, context.ShipmentAddress);

                if (useDistributors && cartItem.DistributorID > 0)
                {
                    ApplyOriginForDistributor(packages, cartItem.DistributorID);
                }

                packages.AddPackage(new Package(cartItem)
                {
                    PackageId = packageId++,
                });

                shipments.Add(packages);
            }

            // Now get all itmes that do not ship separately, but group them into shipments by distributor.
            // Note that distributor ID 0 will be all of the items without a distributor.
            var maxDistributorId = useDistributors
                                ? DB.GetSqlN("select max(DistributorID) N from ProductDistributor")
                                : 0;

            for (int distributorId = 0; distributorId <= maxDistributorId; distributorId++)
            {
                var remainingItemsWeight         = 0m;
                var remainingItemsInsuranceValue = 0m;
                foreach (var cartItem in context.CartItems.Where(ci => !ci.IsShipSeparately))
                {
                    // Only calculate rates for products that require shipping
                    if (cartItem.IsDownload ||
                        cartItem.IsSystem ||
                        !cartItem.Shippable ||
                        GiftCard.ProductIsEmailGiftCard(cartItem.ProductID) ||
                        (cartItem.FreeShipping && !freeShippingAllowsRateSelection))
                    {
                        continue;
                    }

                    if (useDistributors && cartItem.DistributorID != distributorId)
                    {
                        continue;
                    }

                    var weight = cartItem.Weight;
                    if (weight == 0m)
                    {
                        weight = AppLogic.AppConfigUSDecimal("RTShipping.DefaultItemWeight");
                    }

                    if (weight == 0m)
                    {
                        weight = 0.5m;                         // must have SOMETHING to use!
                    }
                    remainingItemsWeight         += (weight * cartItem.Quantity);
                    remainingItemsInsuranceValue += (cartItem.Price * cartItem.Quantity);
                }

                if (remainingItemsWeight == 0m)
                {
                    continue;
                }

                var package = new Package
                {
                    PackageId    = packageId++,
                    Weight       = remainingItemsWeight + AppLogic.AppConfigUSDecimal("RTShipping.PackageExtraWeight"),
                    Insured      = AppLogic.AppConfigBool("RTShipping.Insured"),
                    InsuredValue = remainingItemsInsuranceValue,
                };

                var packages = new Packages();
                ApplyDestinationForAddress(packages, context.ShipmentAddress);

                if (useDistributors && distributorId != 0)
                {
                    ApplyOriginForDistributor(packages, distributorId);
                }

                packages.AddPackage(package);
                shipments.Add(packages);
            }

            return(shipments);
        }