While we do have integrations with USPS, FedEx, UPS for live rate shipping costs based on the store and customer address, and it's possible to use third party services like Shippo too, sometimes you might want to calculate flat rate shipping based on distance yourself. That is possible with our custom shipping code by performing a request to the Google Maps Distance Matrix API.
To get the distance to the customer's shipping address, we will be using the Google Maps API. They require an account with billing account connected for access there though, so you'll need to sign up and create an account if you haven't already. They provide a $200 free allowance per month, which should allow for 40,000 requests per month before any billing would kick in.
Distance Matrix API
.Now for the fun part - adding the custom shipping code to your store!
// Google Maps API key const google_maps_api_key = ''; // Shipping Origin Address const origin_address = 'My Address 1, City, STATE, Country, 12345'; // Unit type (either 'metric' or 'imperial') const unit_type = 'metric'; // Distance will be in metres in the response from Google Maps // Set this to true to convert the value to kilometres/miles based on the unit type above const convert_distance = true; // Get address details const shipment = cart['_embedded']['fx:shipment']; const country = shipment['country']; const region = shipment['region']; const city = shipment['city']; const postal_code = shipment['postal_code']; let address1 = ''; for (let c in cart['_embedded']['fx:custom_fields']) { if (cart['_embedded']['fx:custom_fields'][c]['name'] == 'shipping_address1') { address1 = cart['_embedded']['fx:custom_fields'][c]['value']; break; } } const customer_address = address1 + ', ' + city + ', ' + region + ', ' + country + ', ' + postal_code; // Get address coordinates from Google Maps const maps_url = 'https://maps.googleapis.com/maps/api/distancematrix/json?origins=' + encodeURIComponent(origin_address) + '&destinations=' + encodeURIComponent(customer_address) + '&units=' + unit_type + '&key=' + google_maps_api_key; let distance = -1; await request(maps_url, function (error, response, body) { let payload = JSON.parse(body); if (response && response.statusCode == 200 && payload.status == 'OK' && payload.rows[0].elements[0].status == 'OK') { distance = payload.rows[0].elements[0].distance.value; if (convert_distance) { // Convert distance from metres to kilometres/miles based on unit_type if configured to distance = (unit_type == 'metric') ? distance/1000 : distance/1609.34; } } }); // Begin Custom Logic if (distance > -1) { // Distance was calculated let shipping = 5 + (0.45 * Math.ceil(distance)); rates.add(10000, shipping, '', 'Standard Delivery'); } else { // Distance was not calculated rates.error("Sorry, we're unable to deliver to your address"); }
const google_maps_api_key = '';
to use your Google Maps API key you generated above, pasting it within the quotations.origin_address
string just below that. Replace the placeholder address with the full origin address that you're wanting to calculate the distance from.unit_type
string to be 'imperial'
instead.convert_distance
variable allows you to set whether this should be converted to kilometres/miles (based on what you've set for unit_type
). Set to false to leave it in metres.//Begin Custom Logic if (distance > -1) { // Distance was calculated let shipping = 5 + (0.45 * Math.ceil(distance)); rates.add(10000, shipping, '', 'Standard Delivery'); } else { // Distance was not calculated rates.error("Sorry, we're unable to deliver to your address"); }
One other good use case for this integration is to charge a surcharge if a customer lives outside a specific range from your shipping point. Here's an example of what the custom logic for that could look like as part of the snippet above. This example charges a $20 flat fee, but adds $15 if the customer's address is greater than 35 miles/kilometres away, but also provides free shipping if the order is $150 or more:
if (shipment['total_item_price'] < 150) { let shipping = 20; if (distance > 35) { // Customers address is more than 35 miles away shipping += 15; } rates.add(10000, shipping, '', 'Standard Delivery'); } else { rates.add(10001, 0, '', 'Free Delivery'); }
You'll still need to include the rest of the snippet as detailed above - but this replaces the custom logic at the bottom of the snippet.
As a last step, you'll just need to add in some custom code to your store. The lookup relies on the customers address line 1, which isn't currently sent to the shipping rate endpoint natively. We'll add some custom code to make it pass over. Their address line 1 won't be captured on the cart - but it will still be able to calculate the distance to their town, so should still give a general idea of what the shipping cost would be. They would then see a more accurate rate on the checkout after entering their full address. It can be hidden on the cart though if you prefer, as noted in these steps:
<style type="text/css"> #fc .fc-sidebar--cart .fc-address-entry { display: none; } </style>
{% if context == "checkout" %} <script> FC.client.on("render.done", function() { $("[name^='shipping_address1']").attr("data-fc-shipping-custom-field", true); }); FC.client.on("checkout-shipping-options-update", function() { return $('[data-fc-shipping-custom-field]:visible').filter(function() { return this.value == ""; }).length == 0; }); </script> {% endif %}
At this stage - you've completed all the configuration! You should now be able to add a product to your cart, proceed to the checkout, and enter shipping addresses that are inside and outside of your shippable zone to test it out.
To see what's returned in the API, you can use the following url. Fill in the values needed, then put the url directly in your address bar:
https://maps.googleapis.com/maps/api/distancematrix/json?origins=YOUR_ORIGIN_ADDRESS&destinations=YOUR_DESTINATION_ADDRESS&units=metric&key=YOUR_API_KEY
Note that this will be the raw response - and so the distance value will be in metres. Depending on your configuration of the snippet above, the value you will be referencing as distance
in the custom code may have been converted to kilometres or miles.