Is it possible to limit shipping carrier by country?

1 month ago
I want to ship UPS to valid US addresses and APO / FPO, and USPS to all other (international).  Magento is capable of filtering shipping methods by country, but I cannot find a similar feature inside of NOP.  I've found that I can completely disable shipping for each country individually, but theres no way to affect the shipping providers.

I may write a feature that allows NOP to do this and submit it as pull request to the main project. I already sent in 2 pull requests that fixed small bugs on the USPS and UPS carrier plugins.

Thoughts?
1 month ago
If your shipping methods are defined per carrier (UPS Ground, UPS 2nd Day Air, USPS Priority, USPS 1st Class, etc) then this would do it:
https://docs.nopcommerce.com/user-guide/configuring/settingup/shipping/providers/manual/restrictions.html
1 month ago
Rates are not calculated on the fly for each customer using that plugin.  Every product we sell weighs a different amount.  There are hundreds of products, each with a different weight.  Additionally, I can't figure out how to select from available shipping methods that each carrier provides. I don't think the plugin does this.
1 month ago
Ah, I misunderstood.  You could override the PrepareShippingMethodModel method in the CheckoutModelFactory, it has the shippingAddress so you could check against CountryId and exclude as necessary.
1 month ago
Thanks for that.. I was wondering just how it could filter them using my own plugin. I would love core to do it.. are the project owners pretty active?
1 month ago
(You've already contacted us at noptools.com, but for the benefit of others who don't want to modify core code, or create their own plugin... ;)

If you requiring that if the destination address is domestic US that you only offer UPS, and if the destination is non-US that you only offer USPS, then  Shipping Director can easily handle that with two rules:


Option
US
Country = "US"
Shipping.UPS


Option
non-US
Country <> "US"
Shipping.USPS
4 weeks ago
I thought I'd post what I finally ended up writing. It is very basic, but satisfies our business needs.


using System;
using System.Collections.Generic;
using System.Linq;
using Nop.Core;
using Nop.Core.Domain.Common;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Core.Domain.Shipping;
using Nop.Services.Catalog;
using Nop.Services.Common;
using Nop.Services.Directory;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Services.Payments;
using Nop.Services.Plugins;
using Nop.Services.Shipping;
using Nop.Services.Shipping.Pickup;
using Nop.Services.Stores;
using Nop.Services.Tax;
using Nop.Web.Factories;
using Nop.Web.Models.Checkout;

namespace Nop.Plugin.Shipping.Sigma
{
    public class SigmaCheckoutModelFactory : CheckoutModelFactory
    {
        public SigmaCheckoutModelFactory(AddressSettings addressSettings, CommonSettings commonSettings, IAddressModelFactory addressModelFactory, IAddressService addressService, ICountryService countryService, ICurrencyService currencyService, IGenericAttributeService genericAttributeService, ILocalizationService localizationService, IOrderProcessingService orderProcessingService, IOrderTotalCalculationService orderTotalCalculationService, IPaymentPluginManager paymentPluginManager, IPaymentService paymentService, IPickupPluginManager pickupPluginManager, IPriceFormatter priceFormatter, IRewardPointService rewardPointService, IShippingPluginManager shippingPluginManager, IShippingService shippingService, IShoppingCartService shoppingCartService, IStateProvinceService stateProvinceService, IStoreContext storeContext, IStoreMappingService storeMappingService, ITaxService taxService, IWorkContext workContext, OrderSettings orderSettings, PaymentSettings paymentSettings, RewardPointsSettings rewardPointsSettings, ShippingSettings shippingSettings) : base(addressSettings, commonSettings, addressModelFactory, addressService, countryService, currencyService, genericAttributeService, localizationService, orderProcessingService, orderTotalCalculationService, paymentPluginManager, paymentService, pickupPluginManager, priceFormatter, rewardPointService, shippingPluginManager, shippingService, shoppingCartService, stateProvinceService, storeContext, storeMappingService, taxService, workContext, orderSettings, paymentSettings, rewardPointsSettings, shippingSettings)
        {
        }

        public override CheckoutShippingMethodModel PrepareShippingMethodModel(IList<ShoppingCartItem> cart, Address shippingAddress)
        {
            CheckoutShippingMethodModel currentShippingModel = base.PrepareShippingMethodModel(cart, shippingAddress);
            bool isDomestic = shippingAddress.Country.ThreeLetterIsoCode == "USA";
            IList<CheckoutShippingMethodModel.ShippingMethodModel> tempShippingMethods = currentShippingModel.ShippingMethods;

            for (int i = 0; i < tempShippingMethods.Count; i++)
            {
                if (isDomestic)
                {
                    // Remove USPS for all domestic (USA) shipments
                    if (tempShippingMethods[i].ShippingRateComputationMethodSystemName.Contains("USPS"))
                    {
                        tempShippingMethods.RemoveAt(i);
                    }
                }
                else
                {
                    // Remove UPS for all international shipments
                    if (tempShippingMethods[i].ShippingRateComputationMethodSystemName.Contains("UPS"))
                    {
                        tempShippingMethods.RemoveAt(i);
                    }
                }
            }

            currentShippingModel.ShippingMethods = tempShippingMethods.ToArray();
            
            return currentShippingModel;
        }
    }
}

4 weeks ago
Update: Revised method.  My code from the last post was modifying an array as it was being iterated! Whoops.  This code creates a new array.

public override CheckoutShippingMethodModel PrepareShippingMethodModel(IList<ShoppingCartItem> cart, Address shippingAddress)
        {
            CheckoutShippingMethodModel currentShippingModel = base.PrepareShippingMethodModel(cart, shippingAddress);
            bool isDomestic = shippingAddress.Country.ThreeLetterIsoCode == "USA";
            
            List<CheckoutShippingMethodModel.ShippingMethodModel> newShippingMethodList = new List<CheckoutShippingMethodModel.ShippingMethodModel>();
            foreach (CheckoutShippingMethodModel.ShippingMethodModel shippingMethodModel in currentShippingModel.ShippingMethods)
            {
                if (isDomestic)
                {
                    // Remove USPS for all domestic (USA) shipments
                    if (shippingMethodModel.ShippingRateComputationMethodSystemName.Contains("UPS"))
                    {
                        newShippingMethodList.Add(shippingMethodModel);
                    }
                }
                else
                {
                    // Remove UPS for all international shipments
                    if (shippingMethodModel.ShippingRateComputationMethodSystemName.Contains("USPS"))
                    {
                        newShippingMethodList.Add(shippingMethodModel);
                    }
                }
            }

            currentShippingModel.ShippingMethods = newShippingMethodList;
            
            return currentShippingModel;
        }
4 weeks ago
That is fine, but FYI, you do still incur the 'cost' of calling both the UPS and USPS shipping APIs all the time.  (I.e. the customer waits a little longer ;)
4 weeks ago
Yes. Although it’s a very quick way of accomplishing the goal. I wish I could delete my posts here and post the final code that makes an allowance for the military addresses.