Posted on

Shipping rates by ZIP/Postcode on WooCommerce without extra Shipping Zones

In this post, will discuss how WooCommerce uses a system of Shipping Zones and Shipping Methods and explain how you can set Shipping rates by ZIP/PostCode on WooCommerce without adding extra Shipping Zones.

In the first part of this post, we will discuss how WooCommerce uses a system of Shipping Zones and Shipping Methods to determine how to assign a shipping rate to a specific shipping address and specific cart, and explain how you set traditionally shipping rates by ZIP/PostCode on WooCommerce: defining Shipping Zones.

The second part highlights some of the potential issues that may arise when using WooCommerce Shipping Zones & methods, like ZIP/postcodes forgotten or duplicated shipping methods, that needs more mantainance time and prone to errors. Then we will see how to set shipping rates by ZIP/Postcode programatically or easily through Fish and Ships, and how you can use this to reduce shipping zones quantity to prevent mistakes and minimize maintenance time on shipping rate updatings.

The WooCommerce Shipping Zones and the Shipping Methods

You can imagine the Shipping zones as file drawers, that each can contain one or more WooCommerce Shipping Methods. Each shipping address will match with an unique shipping zone only.

A Shipping Method represents a specific way to ship products to customers, such as “Flat Rate Shipping,” “Free Shipping,” “Local Pickup,” and so on. Each Shipping Method can have its own set of rules and conditions: from a basic flat rate to an advanced conditional shipping table rate, that some of them maybe will inhibit herself, under certain conditions, like perishable or weigh products on cart, for example.

When there is more than one shipping method available for the assigned shipping zone, your customer will be able to choose the shipping option that best suits their needs, between the available shipping options for them.

By assigning specific Shipping Methods to each Shipping Zone, you can tailor your shipping options to the needs of customers in different regions. This can helps you provide a more personalized and efficient shopping experience.

The importance of the order of the WooCommerce Shipping Zones

When a customer enters their shipping address on cart page or during checkout, WooCommerce checks his address against the first Shipping Zone in the list. If the address matches the criteria for that zone, the corresponding Shipping Methods are made available to the customer and the system stops checking any further Shipping Zones. If the address doesn’t match the first zone, the system moves on to the next zone in the list and repeats the process until a match is found or all zones have been checked.

This means that the order of the Shipping Zones is crucial. You need to make sure that your Shipping Zones are listed in the correct order, with the most specific zones listed first and the more general zones listed last. For example, if you have a Shipping Zone for the United States and another one for California, the California zone should be listed first so that customers in California are assigned to that zone before being assigned to the zone that includes the entire United States.

The Shipping Zones matching criteria

When you set up a Shipping Zone in WooCommerce, you have two fields for defining matching criteria: Zone regions, that are required and works with inclusive logic, and limit zipcodes, that are optional and works in restrictive logic.

Zone regions: You can choose to include one or more regions in your Shipping Zone, such as a continent, a country, or a state/province. This means that customers with shipping addresses in any of the included regions will be assigned to the zone. For example, if you include the region “North America” in your zone, customers with shipping addresses in the United States, Canada, and Mexico will all be assigned to that zone.

ZIP/Postal codes: You can also restrict your Shipping Zone using specific ZIP/postcodes. This option is more restrictive, because only customers with shipping addresses in the selected zone regions and in the specified postal codes will be assigned to the shipping zone. However, you can save yourself from entering each ZIP/postcode using wildcards and ranges to specify multiple postal codes at once. You must put every ZIP/postcode, range or postcode with wildcard, in one separate line. This is the tradicional/official way to set shipping rates by ZIP/postcode on WooCommerce. Please, continue reading to discover alternative ways…

Shipping rates by ZIP/Postcode on WooCommerce: ranges and wildcards

For example, if you specify the postal code “S61*” in your zone, all ZIP/postcodes starting with “S61” will be included, for example: “S61 3QY”, “S61 1TF”, etc. Will not match: “S60 1RW” or “S6 6LS”

You can also use ranges, for example: “90210…99000”.

Please, note: range method is not compatible with the US 9-digit/Zip+4 format ( e.g. 90210-1234…99000-4321 ). WooCommerce guys recommend using a wildcard as shown in the example above.

Troubles with the WooCommerce Shipping Zones system

Suppose we want to set up a fast delivery service in our city, with riders. We need to discriminate by zip code, because distance is crucial. So we must create a few shipping zones, drawing concentric rings through our warehouse. But for all other shipping methods, we have the same rates for all the country, so we must to duplicate all of them on each shipping zone. This can be confusing, time-consuming on pricing updates and error-prone, especially if we have a large number of shipping methods to manage.

Or let’s imagine we sell fresh cakes only on our town, and this kind of product is a small part of our product list. We need to create a first shipping zone, with the same methods for the whole country. And then, on the broader one, add conditional block for the fresh cakes… or worse! maybe we give up selling this product online!

Now let’s say we have distinct parcel companies that don’t use the same shipping zones. For example, one use Western Europe as a unique shipping zone, and we have another that have distinct prices per each country. And another one, cheaper that doesn’t sell to islands. With all that we’re covered in this post from now on, we must create one shipping zone for each country, one for islands or maybe for each country/island, and put a lot of duplications on almost of the shipping zones. What a craziness! …or maybe we renunce to some shipping rate, specially interesting for some place, to keep the things simple.

In other words: shipping zones are a clear way to order the shipping methods into shipping zones, but sometimes will be too inflexible for us.

This headache is solved (in part) using the big guys: UPS, FedEx, USPS, etc. gives us for free plugins that uses their own API to calculate on the fly the shipping rates, ignoring our WooCommerce shipping zones. But… that about if we want to use conditional rates, or small parcel companies too?

Shipping rates by ZIP/postcode on WooCommerce programmatically

If you’re writting your own Shipping Method to manage the shipping rates, here you have a code to set the shipping rates by ZIP/postcode on WooCommerce without the need of create Shipping Zones.

We’re used a sample Shipping Method, found at WooCommerce documentation. It’s a standalone file plugin with a shipping class inside. It has a really basic calculate_shipping() function, that we’re modified with the following code (here only the modified function):

/* Shipping rates by ZIP/postcode on WooCommerce
 * 
 * This is just a sample, but, if you want to test it:
 * you can find the full plugin code here: https://woocommerce.com/document/shipping-method-api/
 * The code is a standalone file plugin with a shipping class inside.
 *
 * Keep all as is, and simply replace the inside class function calculate_shipping() with that,
 * activate the plugin and place the method inside a shipping zone that have "Europe" as region
 */

public function calculate_shipping( $package = array() )
{
	$shipping_cost = '10'; // default cost for unmatched addresses
	
	$my_special_postcodes = array
	(	
		// France, State: Aquitaine, City: Agonac. Zip code: 24460
		array(
			'country_code'  => 'FR',
			'postcodes'     => '24460', // Exact comparison
			'shipping_cost' => '20'
		),
		
		// Italy, Sicily. Postcodes: from 90x to 99x
		array(
			'country_code'  => 'IT',
			'postcodes'     => '90000...99999', // Comparison by numerical range
			'shipping_cost' => '30.50'
		),

		// United Kingdom, City: London, Area: South East. 
		// Postcodes follow the pattern: (area)(district)[space](sector)(unit)
		// All South East postcodes start by SE, for example: SE1 2UP
		array(
			'country_code'  => 'GB',
			'postcodes'     => 'SE*', // Comparison by wildcard 
			'shipping_cost' => '50.25'
		),
	);			
	
	// Get ZIP/postcode and country from $package
	$postcode  = $package[ 'destination' ][ 'postcode' ];
	$country   = $package[ 'destination' ][ 'country' ];
	
	foreach ( $my_special_postcodes as $lookin_postcodes )
	{
		// We won't a matching ZIP/postcode from another country, let's check it first!
		if( $country != $lookin_postcodes[ 'country_code' ] )
			continue;
		
		// Prepare postcode object for comparison ( WC way )
		$obj = new stdClass();
		$obj->zone_id = 1; // dummy value
		$obj->location_code = $lookin_postcodes[ 'postcodes' ];
		
		// Note: You can add multiple items into the array to perform multiple comparison at once
		$objects = array( $obj ); 

		// Call the WooCommerce postal code checker (pass also the address country as parameter):
		if( wc_postcode_location_matcher( $postcode, $objects, 'zone_id', 'location_code', $country ) )
		{
			$shipping_cost = $lookin_postcodes[ 'shipping_cost' ];
			break; // stop loop on first matching
		}
	}
	
	$rate = array(
		'label'    => $this->title . ' (' . $country . ')',
		'cost'     => $shipping_cost,
		'calc_tax' => 'per_item',
		'package' => $package
	);

	// Register the rate
	$this->add_rate( $rate );
}

The easiest way to set shipping rates by ZIP/postcode on WooCommerce

From now on, you have a way to write conditional rules based on regions and/or ZIP/postcodes easily. So you can set distinct shipping rates by ZIP/postcode on WooCommerce, combining if needed with regions and whatelse, without the need to create a shipping zone for it. Or lock a purchase under some circumstances, like perishable products outside your city. The possibilities are infinite. Without writting a line of code:

To achieve that, we’ve added four new selectors on our Fish and Ships Pro shipping rules:

selectors for Shipping rates by ZIP/postcode on WooCommerce
Selectors for Shipping rates by ZIP/postcode on WooCommerce

The region selector uses the WooCommerce list of regions, and also have a negative logic version: NOT In region.

Selectors for Shipping rates by zone regions on WooCommerce
Selectors for Shipping rates by zone regions on WooCommerce

The ZIP/postcode use the WooCommerce ZIP/postcode parser, so you can expect the same results as on the Shipping Zone limit per ZIP/postcode field: you can use ranges or wildcards. This selection method also have the negative logic: NOT In ZIP/postcode.

Shipping rates by ZIP/postcode on WooCommerce
Shipping rates by ZIP/postcode on WooCommerce: using wildcards

These methods are combinable with all others: based on weight, price, dimensions, volume, shipping class, category, tags, etc. Let’s see some examples of application:

Limit cart weight and city

Suppose we have a shipping zone for the entire country, with a method that only can sell locally into the same town as our wharehouse are, and limited to 20kg. We can add a shipping rule that disables at the top, and a second rule that does the calculation (whatever):

Shipping rates restricted by weight and city
Shipping rates restricted by weight and city

Limit fresh cake product to our city

Now let’s see a more advanced case. Let’s say we have a whole country/big region shipping zone, with a few shipping methods: One WooCommerce built-in flat rate, and another one using the UPS shipping plugin. And we have one product that can only be sent in our city, because must be delivered carefully on the same day. So we must to disable ALL the shipping methods.

To do this, we can combine the new selectors with the special action “hide other shipping methods”:

Limit selling by product type and city: using shipping class and zone region
Limit selling by product type and city: using shipping class and zone region

…or we can do in another way, adding an error message, that will lock also the checkout page:

Locking sell by city and product type, using a sticky error message
Locking sell by city and product type, using a sticky error message

Distinct Shipping Rates by ZIP/postcode on WooCommerce

In this case, we want to sell products in our city, using riders. We must take in consideration the distance from our wharehouse, setting price per ZIP/postcode, in a concentric rings. And we don’t want to set up shipping zones for that.

First, we will limit it by weight and city, like in the first example. On rules #2 and #3, we will set distinct prices, due the ZIP/postcode. Here we can add as many ranges as we need, and in the last position, we add a fallback rule for other/far/forgotten ZIP/postcodes.

Shipping rates by ZIP/postcode on WooCommerce defining concentric areas
Shipping rates by ZIP/postcode on WooCommerce defining concentric areas

Thanks for reading this long post, hope you’re find it useful! 🙂