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.
Using Multiship? Unfortunately this snippet is only designed to work with single ship stores.
The following functionality allows you to add, update and remove any number of live 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 live 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.
Update categories to 'Shipped using live rates' and select any relevant live rate options from the 'shipping' page.
Add the following right before the closing </head>
tag in your checkout template:
<script type="text/javascript" charset="utf-8"> //<![CDATA[ FC.customLiveShipping = {} FC.customLiveShipping.config = { autoSelect: false // Set to true if you'd like the top shipping option to be automatically selected for the user }; FC.customLiveShipping.logic = function() { /* BEGIN CUSTOM SHIPPING LOGIC */ /* END CUSTOM SHIPPING LOGIC */ } //]]> </script> <script type="text/javascript" charset="utf-8"> //<![CDATA[ /* Live Rate Shipping Modification Logic v1.3 */ jQuery(document).ready(function() { jQuery(document).ajaxComplete(function(event, request, settings) { if (settings.url.indexOf('GetShippingCost') != -1) { FC.customLiveShipping.execute(); } }); if (FC.checkout.config.postedCheckout && FC.checkout.config.shippingServiceId > 0) { FC.customLiveShipping.execute(); } }); /** * PUBLIC FUNCTIONS * Use these functions to modify the returned live rates */ FC.customLiveShipping.add = function(code, cost, carrier, 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" onclick="FC.checkout.updatePrice(-1)" /><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); } FC.customLiveShipping.hide = function(selector) { if (typeof(selector) != "number") { FC.customLiveShipping.alterShippingOptions(FC.customLiveShipping.hide, selector); } else { jQuery("label[for=shipping_service_" + selector + "]").hide(); } } FC.customLiveShipping.show = function(selector) { if (typeof(selector) != "number") { FC.customLiveShipping.alterShippingOptions(FC.customLiveShipping.show, selector); } else { jQuery("label[for=shipping_service_" + selector + "]").show(); } } FC.customLiveShipping.update = function(selector, modifier) { if (typeof(selector) != "number") { FC.customLiveShipping.alterShippingOptions(FC.customLiveShipping.update, selector, modifier); } else { var rateAttributes = FC.customLiveShipping.getShippingAttributes(jQuery("input#shipping_service_" + selector)), price = FC.customLiveShipping.modifyPrice(rateAttributes.price, modifier); jQuery("input#shipping_service_" + selector).val(selector + '|' + price).siblings("span.fc_shipping_cost").html(FC.formatter.currency(price, true)); } } FC.customLiveShipping.remove = function(selector) { if (typeof(selector) != "number") { FC.customLiveShipping.alterShippingOptions(FC.customLiveShipping.remove, selector); } else { jQuery("label[for=shipping_service_" + selector + "]").remove(); } } FC.customLiveShipping.reset = function() { FC.checkout.updateShipping(-1); } /** * PRIVATE FUNCTIONS * These aren't the droids you're looking for */ FC.customLiveShipping.execute = function() { if (!jQuery("#fc_shipping_methods_inner input[type='radio']:first").data("custom-shipping-logic-applied")) { FC.customLiveShipping.logic(); if (FC.customLiveShipping.config.autoSelect) { jQuery("#fc_shipping_methods_inner input[type='radio']:first").attr("checked", "checked"); } // Remove hidden rates - they're obviously not needed jQuery("#fc_shipping_methods_inner label[for^='shipping_service']:hidden").remove(); FC.checkout.updatePrice(-1); // Set the inputs to be marked as updated, to prevent it being run twice. jQuery("#fc_shipping_methods_inner input[type='radio']").data("custom-shipping-logic-applied", true); } } FC.customLiveShipping.alterShippingOptions = function(func, selector, modifier) { if (typeof(selector) == "number") { // They've just provided a rate code, pass them on. func.call(null, selector, modifier); } else if (typeof(selector) == "string") { // It's a string, must be a combination of carrier and service or all var rates = []; if (selector.toLowerCase() == "all") { // This applies to all returned rates rates = jQuery("#fc_shipping_methods_inner label[for^='shipping_service']"); } else { // Some filter has been specified var regex = /(fedex|usps|ups)?\s?([\w\s]+)?/i, provider = regex.exec(selector); if (provider == undefined) return; var carrierSelector = "span.fc_shipping_carrier"; if (provider[1] != undefined) { switch(provider[1].toLowerCase()) { case "fedex": carrierSelector = "span.fc_shipping_carrier:contains('FedEx')"; break; case "usps": carrierSelector = "span.fc_shipping_carrier:contains('USPS')"; break; case "ups": carrierSelector = "span.fc_shipping_carrier:contains('UPS')"; break; } } if (provider[2] != undefined) { rates = jQuery("#fc_shipping_methods_inner label[for^='shipping_service'] "+carrierSelector).siblings("span.fc_shipping_service").filter(function() { return (jQuery(this).text().toLowerCase().indexOf(provider[2].toLowerCase()) > -1); }).parent(); } else { rates = jQuery("#fc_shipping_methods_inner label[for^='shipping_service'] "+carrierSelector).parent(); } } rates.each(function() { var rateAttributes = FC.customLiveShipping.getShippingAttributes(jQuery(this).children("input[name='shipping_service']")); func.call(null, parseInt(rateAttributes.id), modifier); }); } else if (typeof(selector) == "object") { // Assume it's an array of codes for (var i in selector) { func.call(null, parseInt(selector[i]), modifier); } } } FC.customLiveShipping.getShippingAttributes = function(item) { if (typeof(item) == "number") { // Passed the code item = jQuery("input#shipping_service_" + item); } var carrier = "", service = "", inputVal = 0, id = 0, price = 0; if (item.length) { carrier = item.siblings(".fc_shipping_carrier").html(), service = item.siblings(".fc_shipping_service").html(), inputVal = item.val().split("|"), id = parseInt(inputVal[0]), price = parseFloat(inputVal[1]); } return {"carrier": carrier, "service": service, "id": id, "price": price}; } FC.customLiveShipping.modifyPrice = function(price, modifier) { var modifier = modifier.toString(), regex = /([\+\-\=\*\/])?(\d+(?:\.\d+)?)(\%)?/, parts = regex.exec(modifier), price = parseFloat(price), modifyBy = parseFloat(parts[2]); if (parts[3] != undefined) { modifyBy = price * (modifyBy / 100); } var operator = (parts[1] == undefined) ? "=" : parts[1]; switch(operator) { case "+": price = (price + modifyBy); break; case "-": price = (price - modifyBy); break; case "/": price = (price / modifyBy); break; case "*": price = (price * modifyBy); break; default: price = modifyBy; } return (price < 0) ? 0 : price; } //]]> </script>
This script currently provides a single option to alter the way the script works
FC.customLiveShipping.config = { autoSelect: false };
autoSelect
: If set to true, the top option for rates will be automatically selected after your custom logic has run.
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 six functions available to you.
Adds an additional shipping option to the checkout.
Parameters:
code
(Number) - Can not be the same as another ratecost
(Number) - A number to two decimal placescarrier
(String)service
(String)Example:
FC.customLiveShipping.add(100, 4.99, 'PostBox', 'Local Delivery');
FC.customLiveShipping.add(125, 30, ' ', 'Standard Mail');
Notes: Make sure that the code you give the added rate doesn't duplicate a code for a returned rate you've selected. We suggest just giving your custom added rates a high code number. Also, you don't have to provide both the carrier and the service parameters - but at least one of them is required.
Hides one or many existing shipping options.
Parameters:
selector
(Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.Example:
FC.customLiveShipping.hide(1);
- Will hide rate 1FC.customLiveShipping.hide('all');
- Will hide all ratesFC.customLiveShipping.hide('FedEx');
- Will hide all rates for FedExFC.customLiveShipping.hide('Overnight');
- Will hide all rates with a service name that contains 'Overnight'FC.customLiveShipping.hide('USPS Express');
- Will hide any rates from USPS that contain the word 'Express'FC.customLiveShipping.hide([1,2,5,7]);
- Will hide rates with codes 1,2,5 and 7Notes: Any rates that are still hidden at the end of the custom logic block will be removed to prevent them being shown when you don't want them to. They will be returned if the shipping options are refreshed by an address change by the customer.
Shows one or many existing shipping options.
Parameters:
selector
(Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.Example:
FC.customLiveShipping.show(1);
- Will show rate 1FC.customLiveShipping.show('all');
- Will show all ratesFC.customLiveShipping.show('FedEx');
- Will show all rates for FedExFC.customLiveShipping.show('Overnight');
- Will show all rates with a service name that contains 'Overnight'FC.customLiveShipping.show('USPS Express');
- Will show any rates from USPS that contain the word 'Express'FC.customLiveShipping.show([1,2,5,7]);
- Will show rates with codes 1,2,5 and 7Updates the cost of one or many existing shipping options.
Parameters:
selector
(Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.modifier
(String or Number) - Can either be a number (which sets the price to match) or a string containing the operator and a number eg '+20'
, '-10'
, '*2'
, '/2.5'
, '=15'
. You can also append the string with a %
sign to make the operation based on a percentage, eg '+20%'
- add 20%, '-20%'
- less 20%, '/20%'
- divide by 20%, '*20%'
- multiply by 20%.Examples:
FC.customLiveShipping.update(1, 5);
- Will set rate 1 to be $5FC.customLiveShipping.update('all', '*2');
- Will set all returned rates to double their returned costFC.customLiveShipping.update('FedEx', '+5');
- Will set all rates for FedEx to be $5 more than what they returned asFC.customLiveShipping.update('Overnight', '-5');
- Will set all rates with a service name that contains 'Overnight' to be $5 less than returnedFC.customLiveShipping.update('USPS Express', '=6');
- Will set any rates from USPS that contain the word 'Express' to be $6FC.customLiveShipping.update([1,2,5,7], '/2');
- Will set rates with codes 1,2,5 and 7 to be half their returned costFC.customLiveShipping.update('USPS', '+20%');
- Will add 20% of the returned rate to each of the USPS ratesRemoves one or many existing shipping options.
Parameters:
selector
(Number, String or Array) - Can be the code number of a rate, a string containing the carrier and the service (or a combination of) or an array of codes.Example:
FC.customLiveShipping.remove(1);
- Will remove rate 1FC.customLiveShipping.remove('all');
- Will remove all ratesFC.customLiveShipping.remove('FedEx');
- Will remove all rates for FedExFC.customLiveShipping.remove('Overnight');
- Will remove all rates with a service name that contains 'Overnight'FC.customLiveShipping.remove('USPS Express');
- Will remove any rates from USPS that contain the word 'Express'FC.customLiveShipping.remove([1,2,5,7]);
- Will remove rates with codes 1,2,5 and 7Resets the shipping options by re-requesting them from the FoxyCart servers
Example:
FC.customLiveShipping.reset();
Notes: This function should not need to be called from within your custom logic. However you may want to call this function on custom fields you have added to the checkout that may affect shipping costs - for example if you have a gift wrapping service that only allows for certain shipping services and adds a premium to the shipping cost.
In the show()
, hide()
, update()
and remove()
functions, you can specify a selector, which can be either a number, a string or an array. See the following for details of how they work:
1
or 25
'all'
- By specifying this string, all returned rates will be modified'fedex'
, 'ups'
, 'usps'
- will select all rates for the specified carrier. Note that only one carrier can be specified in a selector'overnight'
, 'priority mail'
, '2 day'
- if the string does not match a carrier as above, it will be assumed it is a service, and will look for the string anywhere within it's service name. So if 'overnight'
is specified, it would match a string of really quick overnight
and overnight service
.'fedex priority overnight'
- if both the carrier and the service filter is specified (the carrier must always be the first word, followed by a space, followed by the service string), then it will be filtered by both. Again 'fedex'
will match against the carrier name, and the rest of the string provided will match against the service. In this example, the service matching would be looking for priority overnight
within the service name.[1,4,6,8]
var state = (jQuery("#use_different_addresses").is(":checked") ? $("#shipping_state").val() : $("#customer_state").val()); var country = (jQuery("#use_different_addresses").is(":checked") ? $("#shipping_country").val() : $("#customer_country").val()); if (state != "NY") FC.customLiveShipping.remove("pickup"); if ((country == "US" && fc_json.total_price <= 100) || country != "US") FC.customLiveShipping.remove("free");
var food = 0; for (p in fc_json.products) { switch (fc_json.products[p].category) { case "food": food += fc_json.products[p].quantity; break; } } if (food > 0) { // Hide all shipping options FC.customLiveShipping.hide('all'); // Show only the overnight options FC.customLiveShipping.show('overnight'); // If 3 or more food items, update priority overnight to be free if (food >= 3) FC.customLiveShipping.update('priority overnight', 0); }
FC.customLiveShipping.hide('free'); if(fc_json.hasOwnProperty('coupons')) { jQuery.each(fc_json.coupons, function(i, coupon){ if(coupon.name.search(/free shipping/i) > -1) { FC.customLiveShipping.show('free'); } }); }
FC.customLiveShipping.hide('free'); if(fc_json.product_count > 5) { FC.customLiveShipping.show('free'); }
ajaxComplete()
to match changes made in jQuery 1.8