Calculate flat rate shipping based on distance

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.

Generate Google Maps Platform API Key

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.

  1. Head to the Google Maps Platform site at https://cloud.google.com/maps-platform, and log in or create an account if needed.
  2. Click the “Get Started” button.
  3. In the popup that appears, select the “Places” product option and continue
  4. On the next panel you'll be prompted to either select an existing project or create a new one. Make a selection there and continue.
  5. If prompted to create a billing account, complete that. Google has a $200 free allowance per month, but requires you assign a billing account in case you go over that allowance.
  6. Once you've created your new project, you'll be redirected to the Google Maps Platform portal for your project.
  7. On the overview page for your project, review the “Enabled APIs” panel, and confirm that the “Distance Matrix API” is displayed there.
    1. If not, click the “All Google Maps APIs” link at the top of the page next to the “Overview” title, and select “Enable API”.
    2. This will redirect you to the API marketplace. Search for the “Distance Matrix API, and select and enable it for your account.
  8. Select the hamburger menu in the top left, and select “APIs & Services” and “Credentials”
  9. Click “Create Credentials” on the Credentials page, and select “API Key”.
  10. Copy the resulting API key into a text file to use later
  11. If given the option to restrict your API key - currently you won't be able to use Application restrictions, but you can set an API restriction to only allow the Distance Matrix API.

Build custom shipping code

Now for the fun part - adding the custom shipping code to your store!

  1. Open your store's Foxy administration and head to the “shipping” section
  2. If it's not already enabled, enable the “use custom code” option at the bottom of the page.
  3. Copy and paste the following code into the code editor that appears:
    // 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");
    }
  4. Update the line of code const google_maps_api_key = ''; to use your Google Maps API key you generated above, pasting it within the quotations.
  5. Update the origin_address string just below that. Replace the placeholder address with the full origin address that you're wanting to calculate the distance from.
  6. If you need Imperial measurements rather than Metric, update the unit_type string to be 'imperial' instead.
  7. Google returns the distance value in metres in their API. The 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.
  8. Next, you'll need to set the shipping rate and error that is displayed to customers. The snippet above sets a rate that is $5 plus 45¢ per mile/km, but you'll obviously want to customise that to show your own rates. Check out the documentation here on interacting with our rates.

    Specifically you'll be editing this section:
    //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");
    }
  9. Save your custom shipping code
  10. Wait 15-20 seconds and refresh the page to ensure the shipping code has saved (it's a separate system so takes a moment to initialize)
  11. If you haven't already, you'll also need to update any categories that will use this shipping rate code to have a delivery type of “Shipped using live shipping rates”.

Example: Delivery surcharge based on distance

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.

Add custom template code

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:

  1. In the Foxy administration, head to the “configuration” section
  2. If not already, enable the “Add custom header and footer code to your templates”
  3. If you want to hide the shipping rate calculation on the cart, in the “custom header” section, paste this code after any existing code you have in that field:
    <style type="text/css">
    #fc .fc-sidebar--cart .fc-address-entry {
        display: none;
    }
    </style>
  4. In the “custom footer” section, paste this code after any existing code you have in that field:
    {% 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 %}
  5. Save the configuration

Test it out!

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.

Troubleshooting

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.

Site Tools