Removing WooCommerce Shipping Methods by Distance

Inside WooCommerce, shipping methods can be placed inside shipping zones, which can be ZIP codes, states, countries, etc… However, what if you want to base your shipping method on distance from your business, for instance, if you are farm – how is this achieved? Using the “woocommerce_package_rates” filter, the shipping methods and their rates can be filtered out before it reaches the user.

First off, let me start by saying in this article we will be using the Census Bureau’s Geocoder service since it costs nothing to use. The downside here is the distance is between x,y coordinates and not actual driving miles. For that more accurate driving miles, a paid service like the Google Maps Distance Matrix API is better suited.

Step 1: Grab the user’s information

The first step is to grab the customer’s shipping information so we can later pass that on to the geocoder service. Since a user may not be logged in, the information is grabbed from the session. From there, the shipping address, city, and state details can be grabbed.

Note that if any of the shipping details are empty, the rates are just returned and we do nothing. A consideration here would be to return an actual empty array versus returning the rates since now those rates are not accurate.


add_filter(
'woocommerce_package_rates',
function ($rates) {
$customer = WC()->session->get('customer');
$address = !empty($customer['shipping_address_1'])
? urlencode($customer['shipping_address_1'])
: false;
$city = !empty($customer['shipping_city'])
? urlencode($customer['shipping_city'])
: false;
$state = !empty($customer['shipping_state'])
? urlencode($customer['shipping_state'])
: false;
if (!$address || !$city || !$state) {
return $rates;
}
return $rates;
},
10,
2,
);

view raw

function.php

hosted with ❤ by GitHub

Step 2: Get the customer’s x and y coordinates

Now that we have the customer’s address, the geocoordinates can be retrieved. The United States Census Bureau offers the Census Geocoder service and an accompanying API (for free), which is very handy. There are many different things you can do with this, but for the purposes of this article the address, city, and state are passed, and using the 2020 dataset, matching addresses are returned, of which we grab the first one and its geocoordinates.


$url = "https://geocoding.geo.census.gov/geocoder/locations/address?street={$address}&city={$city}&state={$state}&benchmark=2020&format=json";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$output = json_decode($output);
if (count($output->result->addressMatches) == 0) {
$rates = [];
return $rates;
}
$x = $output->result->addressMatches[0]->coordinates->x;
$y = $output->result->addressMatches[0]->coordinates->y;

view raw

function.php

hosted with ❤ by GitHub

Step 3: Math

Thanks to people smarter than myself, you can take a set of x,y coordinates and figure out the distance between them and another set. I’m not going to claim I know exactly what is going on here, however it works so take it for what it is.


$location_x = '-85.491302';
$location_y = '38.36621792373851';
$theta = $location_x – $x;
$distance = sin(deg2rad($location_y)) * sin(deg2rad($y)) + cos(deg2rad($location_y)) * cos(deg2rad($y)) * cos(deg2rad($theta));
$distance = round(rad2deg(acos($distance)) * 60 * 1.1515);

view raw

function.php

hosted with ❤ by GitHub

Step 4: Removing Shipping Methods

The logic here is all shipping methods are enabled by default and they are removed as you get further from the business location. Since we have all the shipping methods being passed to the filter, we can use the unset function to pop off the methods we do not want.

The shipping method name, which is the top level key in the data being passed to the filter, is generally the shipping method name and then the instance of it. An example would be “local_pickup:5”, which is the local pickup shipping method and the 5th instance of it.


if ($distance > 25) {
unset($rates['flat_rate:4']);
}
if ($distance > 100) {
unset($rates['local_pickup:5']);
}
return $rates;

view raw

function.php

hosted with ❤ by GitHub

Final Filter

The finished product looks something like below


add_filter(
'woocommerce_package_rates',
function ($rates) {
$customer = WC()->session->get('customer');
$address = !empty($customer['shipping_address_1'])
? urlencode($customer['shipping_address_1'])
: false;
$city = !empty($customer['shipping_city'])
? urlencode($customer['shipping_city'])
: false;
$state = !empty($customer['shipping_state'])
? urlencode($customer['shipping_state'])
: false;
if (!$address || !$city || !$state) {
return $rates;
}
$url = "https://geocoding.geo.census.gov/geocoder/locations/address?street={$address}&city={$city}&state={$state}&benchmark=2020&format=json";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
$output = json_decode($output);
if (count($output->result->addressMatches) == 0) {
$rates = [];
return $rates;
}
$x = $output->result->addressMatches[0]->coordinates->x;
$y = $output->result->addressMatches[0]->coordinates->y;
$location_x = '-85.491302';
$location_y = '38.36621792373851';
$theta = $location_x – $x;
$distance =
sin(deg2rad($location_y)) * sin(deg2rad($y)) +
cos(deg2rad($location_y)) *
cos(deg2rad($y)) *
cos(deg2rad($theta));
$distance = round(rad2deg(acos($distance)) * 60 * 1.1515);
if ($distance > 25) {
unset($rates['flat_rate:4']);
}
if ($distance > 100) {
unset($rates['local_pickup:5']);
}
return $rates;
},
10,
2,
);

view raw

function.php

hosted with ❤ by GitHub