Table of Contents

type:
snippet
category:
shipping
name:
Multiple Flat Rate
versions:
0.6.0, 0.7.0, 0.7.1, 0.7.2, 1.0, 1.1
reference:
http://forum.foxycart.com/comments.php?DiscussionID=3132&page=1
tags:
snippets, shipping, advance
date:
2011-05-25

Multiple Custom Flat Rate Shipping Options

Versions 0.7.1 and older: Note that applying javascript shipping modifications using either live or flat rates where you are setting custom values has been found to not work as expected for subscription based products where you need the shipping to apply to each subscription renewal. A fix is in place for versions 0.7.2 and newer.

Using version 2.0? There is a new snippet available for our latest version, available from here.

If you're using handling fees note that you can't overwrite handling fees using javascript on the checkout to provide free shipping. While it will appear that the shipping is $0 on the checkout, the handling fees will be added back in server-side, and the customer will see a shipping fee that matches the handling fee cost you have set in the administration.

The following functionality allows you to add and update any number of custom flat rate shipping options based on any criteria (eg: what categories are products in, total cost of the cart, shipping destination country). This script is limited to flat rate shipping only.

The functionality described on this page require advanced javascript knowledge, and are not officially supported. They are included in our official wiki because certain shipping methods and functionality are not natively supported, and though we are working on radically improving our shipping functionality, in the meantime these methods may be great workarounds. Use with caution, test, and post in our forum if you run into problems.

See the changelog for details on updates to this script.

Step 1: Update Categories

Update all categories to 'Shipped using a flat rate fee' with a value of 0 in the 'Product Delivery Option' section.

Step 2: Add HTML

Add the following right after the ^^checkout^^ placeholder in your checkout template:

^^custom_begin^^
<div id="fc_custom_shipping_methods_container">
</div>
^^custom_end^^

Step 3: Add Javascript

Add the following right before the closing </head> tag in your checkout template (we will add code to the “customShippingLogic” function later):

<script type="text/javascript" charset="utf-8">
  //<![CDATA[
 
  FC.checkout.config.customShipping = {
    onLoad: true,  // Set to false if you don't want shipping calculated when the checkout loads
    onLocationChange: false, // Set to true if your shipping logic relies on updating whenever the shipping location for the order changes
    onPreSubmit: true // Set to false if you don't want to load shipping if it hasn't already loaded before the user tries to checkout
  };
 
  function customShippingLogic() {
    /* BEGIN CUSTOM SHIPPING LOGIC */
 
    // ... add your custom logic here
 
    /* END CUSTOM SHIPPING LOGIC */
  }
 
  //]]>
</script>
 
<script type="text/javascript" charset="utf-8">
  //<![CDATA[
  /* Multiple Flat Rate Shipping Options Logic v2.4 */
 
  jQuery(document).ready(function() {
    jQuery("#fc_custom_shipping_methods_container").on('click', 'input[name=shipping_service]', function(){
      shipping_service_description = jQuery(this).siblings(".fc_shipping_carrier").html();
      shipping_service_description += ((shipping_service_description == "") ? '' : ' ');
      shipping_service_description += jQuery(this).siblings(".fc_shipping_service").html();
      $("#shipping_details").val(shipping_service_description);
      // Launch FoxyCart functionality
      FC.checkout.updatePrice(-1);
    });
 
    if (FC.checkout.config.customShipping.onLoad) {
      runShippingLogic();
    }
 
    if (FC.checkout.config.customShipping.onLocationChange) {
      isValidateAndSubmit = false;
      FC.checkout.overload("updateTaxes", function() { if (!isValidateAndSubmit) { runShippingLogic(); } }, null);
      FC.checkout.overload("validateAndSubmit", function() { isValidateAndSubmit = true; }, function() { isValidateAndSubmit = false });
    }
 
    if (FC.checkout.config.customShipping.onPreSubmit) {
      FC.checkout.overload("validateAndSubmit", function() {if (!jQuery("#shipping_service_id").length) { runShippingLogic(); }}, null);
    }
  });
 
  function runShippingLogic() {
    // Check to see if there are actually shippable products in the current cart before running the custom shipping (0.7.1+ only), or just run it for older carts
    if ((typeof(FC.checkout.config.hasShippableProducts) === "boolean" && FC.checkout.config.hasShippableProducts) || typeof(FC.checkout.config.hasShippableProducts) === "undefined") {
        customShippingLogic();
    }
  }
 
  // example: addShippingOption(1, 4.99, 'PostBox', 'Express Local');
  function addShippingOption(code, cost, carrier, service) {
    if (jQuery("#fc_shipping_methods_inner").length == 0) {
      addCustomShippingContainer();
    }
    carrier = (typeof(carrier) == 'undefined' || carrier == null) ? "" : carrier;
    service = (typeof(service) == 'undefined' || service == null) ? "" : service;
    var newShippingOption = '<label for="shipping_service_' + code + '" class="fc_radio"><input type="radio" class="fc_radio fc_required" value="' + code + '|' + cost + '" id="shipping_service_' + code + '" name="shipping_service" /><span class="fc_shipping_carrier">' + carrier + '</span><span class="fc_shipping_service">' + service + '</span><span class="fc_shipping_cost">' + FC.formatter.currency(cost, true) + '</span></label>';
    jQuery("#fc_shipping_methods_inner").append(newShippingOption);
  }
 
  // example: updateShippingOptionCost(1, 4);
  function updateShippingOptionCost(code, cost) {
    jQuery("input#shipping_service_" + code).val(code + '|' + cost).siblings("span.fc_shipping_cost").html(FC.formatter.currency(cost, true));
    FC.checkout.updatePrice(-1);
  }
 
  // example: removeShippingOption(1);
  function removeShippingOption(code) {
    jQuery("label[for=shipping_service_" + code + "]").remove();
    if (jQuery("#fc_shipping_methods_inner").html() == "") {
      removeCustomShippingContainer();
    }
    FC.checkout.updatePrice(-1);
  }
 
  function addCustomShippingContainer() {
 
jQuery("#fc_custom_shipping_methods_container").html('<h2>Shipping Options</h2><div class="fc_row fc_shipping_methods_container" id="fc_shipping_methods_container"><div class="fc_radio_group_container fc_row fc_shipping_methods" id="fc_shipping_methods"><input type="hidden" value="0" id="shipping_service_id" name="shipping_service_id"><input type="text" style="display:none;" value="" id="shipping_service_description" name="shipping_service_description"><input type="text" value="" id="shipping_details" name="Shipping_Details" style="display:none;" /><div class="fc_shipping_methods_inner" id="fc_shipping_methods_inner"></div><label style="display: none;" class="fc_error" for="fc_shipping_methods">Please select a shipping method.</label></div></div>');
  }
 
  function removeCustomShippingContainer() {
    jQuery("#fc_custom_shipping_methods_container").html("");
    FC.checkout.updatePrice(-1);
  }
  //]]>
</script>

Step 4: Set Update Options

Based on the type of shipping options you're providing, you may need to customise when the shipping options are run in your script. At the top of the script, you need to edit the options array to match the type of functionality you're looking for. Note: If all options are set to false, the shipping options will never run.

FC.checkout.config.customShipping = {
  onLoad: true,
  onLocationChange: false,
  onPreSubmit: true
};

Step 5: Customise Shipping Options

Now the fun part, based on whatever criteria you want, add in the different shipping options you require for your site. Add your custom code between the /* BEGIN CUSTOM SHIPPING LOGIC */ and /* END CUSTOM SHIPPING LOGIC */ lines in the first script block. There are four functions available to you.

addShippingOption()
Description: Adds a shipping option to the checkout.
Parameters: code, cost, carrier, service
Example: addShippingOption(1, 4.99, 'PostBox', 'Local Delivery');
Notes: You don't have to provide both the carrier and the service parameters - but at least one of them is required.
updateShippingOptionCost()
Description: Updates the cost of a existing shipping option.
Parameters: code, cost
Example: updateShippingOptionCost(1, 5.50);
removeShippingOption()
Description: Removes an existing shipping option.
Parameters: code
Example: removeShippingOption(1);
removeCustomShippingContainer()
Description: Removes all existing shipping options.
Parameters: none
Example: removeCustomShippingContainer();

Note that the code parameter must be a number. Setting it to a string will result in your checkout failing in a situation where the gateway returns your customer to the checkout with an error.

Examples

Example 1
  1. 3 default shipping options: standard and priority with one postal provider, and express with another.
  2. If there are more than 5 products, remove the express option.
  3. If the total weight of the cart is greater that 10, adjust the shipping costs.
addShippingOption(1, 5, 'Postmaster', 'Standard Delivery');
addShippingOption(2, 9.45, 'Postmaster', 'Priority Delivery');
addShippingOption(3, 10, 'PostPlus', 'Express (Next Day)');
 
if (fc_json.total_weight > 10) {
  updateShippingOptionCost(1, 6);
  updateShippingOptionCost(2, 10);
  updateShippingOptionCost(3, 11.99);
}
 
if (fc_json.product_count > 5) {
  removeShippingOption(3);
}
Example 2
  1. Postage is calculated as a base price per product, with each subsequent product adding an additional cost.
  2. Two different groups of shipping options are presented, one for local delivery within the US, and one for international addresses based off of the shipping country.
  3. Shipping methods are first displayed after the customer has entered address details like country, state and postcode, and reset whenever they change the shipping address to be a different country.
  4. Requires onLocationChange being set to true
if (typeof(country_code) === "undefined") {country_code = "";}
new_country_code = (jQuery("#use_different_addresses").is(":checked") ? $("#shipping_country").val() : $("#customer_country").val());
if (country_code != new_country_code) { // The shipping country has changed!
  country_code = new_country_code;
  removeCustomShippingContainer(); // This call will make sure that when it updates, it starts fresh.
  if (country_code == "US") {
    postage = 10 + ((fc_json.product_count - 1) * 0.50);
    addShippingOption(1, postage, 'USPS', 'Standard');
 
    postage = 12 + ((fc_json.product_count - 1) * 1.50);
    addShippingOption(2, postage, 'USPS', 'Express');
  } else {
    postage = 15 + ((fc_json.product_count - 1) * 2);
    addShippingOption(3, postage, 'USPS', 'International');
  }
}
Example 3
  1. Postage is assigned per category.
  2. If there is a product from CategoryA in the cart, then present express option
  3. If there is only a product from CategoryB in the cart, provide free shipping as an option
var hasCategoryA = false;
var hasCategoryB = false;
for (p in fc_json.products) {
  switch (fc_json.products[p
].category) {
    case "CategoryA":
      hasCategoryA = true;
      break;
    case "CategoryB":
      hasCategoryB = true;
      break;
  }
}
if (hasCategoryB && !hasCategoryA) {
  addShippingOption(1, 0, '', 'Free Ground Shipping');
} else if (hasCategoryA) {
  addShippingOption(2, 5.99, 'USPS', 'Express')
}

Changelog