Table of Contents
Flat Rate Shipping Modification
This snippet has been replaced. In place of the following snippet, we now have a custom shipping code feature in FoxyCart 2.0 that allows you to modify returned live rates and provide custom rates to your customers without needing to add extra javascript to change the way the checkout works. Please use the new custom shipping code functionality instead of this snippet. Review this page for notes on migrating to the new functionality. This page will remain for reference.
If you're using handling fees, you will need to set them as part of your custom shipping logic - instead of through the administration. Setting a handling fee in the admin while making use of this snippet can have unintended results.
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.
Custom shipping snippets do not work for subscriptions. The reason for this is because the snippets are done as Javascript modifications and run in the browser, while subscriptions run completely on the server side and never touch the browser. Subscriptions will always calculate on the server side based on how the category is configured.
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: Disable Shipping Rate Signing
To ensure the snippet works as normal, you'll also need to ensure that shipping rate signing is disabled for your store. To do that, head to the “shipping” section of your store's FoxyCart administration, and at the bottom of the page ensure that “enable shipping rate signing” is disabled.
Step 3: Add Javascript
Add the following in the “footer” section of the template configuration's “Add custom header and footer code to your templates” option. You will find this option within the “Cart” group on the “Configuration” section of your store's FoxyCart administration.
{% if (context == 'cart' or context == 'checkout') and item_count > 0 %} <script type="text/javascript" charset="utf-8"> (function (FC, $) { FC.customFlatRates = {}; FC.customFlatRates.shipping_results = []; FC.customFlatRates.config = { 'autoSelect': false /* Set to true if you'd like the top shipping option to be automatically selected for the user */ }; FC.customFlatRates.logic = function(address) { /* BEGIN CUSTOM SHIPPING LOGIC */ /* END CUSTOM SHIPPING LOGIC */ }; /* Flat Rate Shipping Modification Logic v2.0.12 */ FC.customFlatRates.add=function(a,b,c,d){FC.customFlatRates.shipping_results.push({method:c,price:b,service_id:a,service_name:d})};FC.customFlatRates.hide=function(a){a=FC.customFlatRates.filterShippingOptions(a);for(var b=0;b<a.length;b++)FC.customFlatRates.shipping_results[a[b]].hide=!0};FC.customFlatRates.show=function(a){a=FC.customFlatRates.filterShippingOptions(a);for(var b=0;b<a.length;b++)FC.customFlatRates.shipping_results[a[b]].hide=!1}; FC.customFlatRates.remove=function(a){a=FC.customFlatRates.filterShippingOptions(a);a=a.sort(function(a,b){return b-a});for(var b=a.length-1;0<=b;b--)FC.customFlatRates.shipping_results.splice(a[b],1)}; FC.customFlatRates.update=function(a,b,c,d){a=FC.customFlatRates.filterShippingOptions(a);for(var e=0;e<a.length;e++){if("number"===typeof b||"string"===typeof b&&""!==b){var f=FC.customFlatRates.modifyPrice(FC.customFlatRates.shipping_results[a[e]].price,b);FC.customFlatRates.shipping_results[a[e]].price=f}"string"===typeof c&&(FC.customFlatRates.shipping_results[a[e]].method=c);"string"===typeof d&&(FC.customFlatRates.shipping_results[a[e]].service_name=d)}}; FC.customFlatRates.reset=function(){FC.customFlatRates.shipping_results=[];FC.customFlatRates.error_message=!1};FC.customFlatRates.error=function(a){FC.customFlatRates.error_message=a}; FC.api.getShippingOptions=function(a){var b={address_name:a.address_name,state:a.region,country:a.country,city:a.city,postal_code:a.postal_code};FC.customFlatRates.reset();FC.customFlatRates.logic(b);b=!1;for(var c=FC.customFlatRates.shipping_results.length-1;0<=c;c--)FC.customFlatRates.shipping_results[c].hide?FC.customFlatRates.shipping_results.splice(c,1):FC.customFlatRates.shipping_results[c].service_id==a.shipping_service_id&&(b=!0);b||(a.shipping_service_description="",a.shipping_service_id= 0,a.total_shipping=0,a.total_future_shipping=0);if(0<FC.customFlatRates.shipping_results.length&&(FC.customFlatRates.shipping_results.sort(function(a,b){return a.price-b.price}),FC.customFlatRates.config.autoSelect&&0==a.shipping_service_id||1==FC.customFlatRates.shipping_results.length)){if(FC.json.has_multiship)for(c=0;c<FC.json.multiship_data.length;c++)FC.json.multiship_data[c].address_name==a.address_name&&(FC.json.multiship_data[c].shipping_service_id=FC.customFlatRates.shipping_results[0].service_id, FC.json.multiship_data[c].shipping_service_description=FC.customFlatRates.shipping_results[0].method+" "+FC.customFlatRates.shipping_results[0].service_name);else FC.json.shipping_address.shipping_service_id=FC.customFlatRates.shipping_results[0].service_id,FC.json.shipping_address.shipping_service_description=FC.customFlatRates.shipping_results[0].method+" "+FC.customFlatRates.shipping_results[0].service_name;FC.json.show_shipping_tbd=!1}return $.Deferred(function(a){setTimeout(function(){FC.customFlatRates.error_message? a.reject(Error(FC.customFlatRates.error_message)):a.resolve({ok:!0,data:{shipping_results:FC.customFlatRates.shipping_results}})},1E3)})}; FC.customFlatRates.filterShippingOptions=function(a){if("number"==typeof a)for(var b=0;b<FC.customFlatRates.shipping_results.length;b++){if(FC.customFlatRates.shipping_results[b].service_id==a)return[b]}else{if("string"==typeof a){var c=[],d={};for(b=0;b<FC.customFlatRates.shipping_results.length;b++){var e=FC.customFlatRates.shipping_results[b];d[b]=e.method+" "+e.service_name}if("all"!=a.toLowerCase()){a=/(fedex|usps|ups)?\s?([\w\s]+)?/i.exec(a);if(void 0==a||""==a)return;for(b in d)void 0!=a[1]&& ""!=a[1]&&-1==d[b].toLowerCase().indexOf(a[1].toLowerCase())?delete d[b]:void 0!=a[2]&&""!=a[2]&&-1==d[b].toLowerCase().indexOf(a[2].toLowerCase())&&delete d[b]}for(b in d)c.push(parseInt(b));return c}if("object"==typeof a){c=[];for(b=0;b<a.length;b++)for(d=0;d<FC.customFlatRates.shipping_results.length;d++)FC.customFlatRates.shipping_results[d].service_id==a[b]&&c.push(a[b]);return c}}}; FC.customFlatRates.modifyPrice=function(a,b){b=b.toString();var c=/([\+\-=\*\/])?(\d+(?:\.\d+)?)(%)?/.exec(b);a=parseFloat(a);var d=parseFloat(c[2]);void 0!=c[3]&&""!=c[3]&&(d=d/100*a);switch(void 0==c[1]&&""!=c[1]?"=":c[1]){case "+":a+=d;break;case "-":a-=d;break;case "/":a/=d;break;case "*":a*=d;break;default:a=d}return 0>a?0:a}; function requireShipping(){var a=!1;FC.customFlatRates.config.onLocationChange=!1;if(FC.json.has_multiship)for(i=0;i<FC.json.multiship_data.length;i++)FC.json.multiship_data[i].has_shippable_products&&!FC.json.multiship_data[i].has_live_rate_shippable_products&&(FC.json.multiship_data[i].has_live_rate_shippable_products=!0,FC.json.shipping_address.custom_flat_rates_applied=!0,0==FC.json.multiship_data[i].shipping_service_id&&(FC.json.show_address_entry=!0,"cart"==FC.json.context&&(FC.json.show_shipping_tbd= !0)));FC.json.shipping_address.has_shippable_products&&!FC.json.shipping_address.has_live_rate_shippable_products&&(FC.json.shipping_address.has_live_rate_shippable_products=!0,FC.json.has_current_live_rate_shipping=!0,FC.json.has_current_flat_rate_shipping=!1,a=!0,0==FC.json.shipping_address.shipping_service_id&&(FC.json.show_address_entry=!0,FC.json.show_shipping_tbd=!0));a&&(a=!1,1==$('[data-fc-id="block-coupon-entry"] .fc-alert p').length&&(a=$('[data-fc-id="block-coupon-entry"] .fc-alert p').html(), FC.util.addError("coupon",a),a=!0),FC.Template(FC.json.context).clearOutput(),FC[FC.json.context].render(),a&&FC.util.removeError("coupon"));$.each(FC.json.has_multiship?FC.json.multiship_data:[FC.json.shipping_address],function(a,c){(!FC.customFlatRates.config.onLocationChange&&FC.util.addressHasLocationInfo(c)||FC.customFlatRates.config.onLocationChange)&&c.has_live_rate_shippable_products&&FC[FC.json.context].getShippingOptions({address:c})})} function saveShippingId(a,b,c){FC.customFlatRates.shipping_service_ids=[];$.each(FC.json.has_multiship?FC.json.multiship_data:[FC.json.shipping_address],function(a,b){FC.customFlatRates.shipping_service_ids[a]=b.shipping_service_id})} function restoreShippingId(a,b,c){a=/ThisAction=(\w+)/;if(void 0!==c&&c.hasOwnProperty("url")){if(-1!=c.url.search(a))var d=c.url.match(a)[1];else-1<c.url.indexOf("/cart")&&(d="cart");-1==$.inArray(d,["SaveCartContactInfo","GetAddressByPostalCode","SaveShippingServiceInfo","GetTaxes"])&&(FC.customFlatRates.hasOwnProperty("shipping_service_ids")&&$.each(FC.json.has_multiship?FC.json.multiship_data:[FC.json.shipping_address],function(a,b){0==b.shipping_service_id&&(b.shipping_service_id=void 0===FC.customFlatRates.shipping_service_ids[a]? 0:FC.customFlatRates.shipping_service_ids[a])}),requireShipping())}FC.customFlatRates.shipping_service_ids=[]}$(document).ajaxStart(saveShippingId);$(document).ajaxComplete(restoreShippingId); {% if cart_is_fullpage or context == 'checkout' %}FC.client.on("ready.done",requireShipping);{% else %}requireShipping();{% endif %} })(FC, jQuery); </script> {% endif %}
If you have created your own cart or checkout templates, ensure you've followed the steps to include the custom code placeholders as detailed here
Step 4: Update Configuration
This script currently provides a single option to alter the way the script works. The following code is found at the top of the snippet you just copied in step 3, and if you'd like to alter the setting detailed below, you can alter that where you've just pasted the snippet code.
FC.customFlatRates.config = { 'autoSelect': false };
autoSelect
: If set to true, the top option for rates will be automatically selected after your custom logic has run.
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.
Address Information
The logic()
function where your custom shipping logic is placed is parsed an object of address information for the shipping address that is being requested. Specifically, the following information is passed to the function in a variable named address
:
address.address_name
Used for multiship - will contain the multiship nameaddress.state
2 character code if auto-completed, or the name as enteredaddress.country
2 character country code likeUS
orGB
address.city
:address.postal_code
Helper functions
There are also a number of functions you can utilise to execute your custom shipping logic:
FC.customFlatRates.add()
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.customFlatRates.add(100, 4.99, 'PostBox', 'Local Delivery');
FC.customFlatRates.add(125, 30, ' ', 'Standard Mail');
Notes: Make sure that the code you give the added rate doesn't duplicate a code for an existing rate. Also, you don't have to provide both the carrier and the service parameters - but at least one of them is required.
FC.customFlatRates.hide()
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.customFlatRates.hide(1);
- Will hide rate 1FC.customFlatRates.hide('all');
- Will hide all ratesFC.customFlatRates.hide('FedEx');
- Will hide all rates for FedExFC.customFlatRates.hide('Overnight');
- Will hide all rates with a service name that contains 'Overnight'FC.customFlatRates.hide('USPS Express');
- Will hide any rates from USPS that contain the word 'Express'FC.customFlatRates.hide([1,2,5,7]);
- Will hide rates with codes 1,2,5 and 7
Notes: 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.
FC.customFlatRates.show()
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.customFlatRates.show(1);
- Will show rate 1FC.customFlatRates.show('all');
- Will show all ratesFC.customFlatRates.show('FedEx');
- Will show all rates for FedExFC.customFlatRates.show('Overnight');
- Will show all rates with a service name that contains 'Overnight'FC.customFlatRates.show('USPS Express');
- Will show any rates from USPS that contain the word 'Express'FC.customFlatRates.show([1,2,5,7]);
- Will show rates with codes 1,2,5 and 7
FC.customFlatRates.update()
Updates 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%.carrier
(String)service
(String)
Examples:
FC.customFlatRates.update(1, 5);
- Will set rate 1 to be $5FC.customFlatRates.update('all', '*2');
- Will set all returned rates to double their returned costFC.customFlatRates.update('FedEx', '+5');
- Will set all rates for FedEx to be $5 more than what they returned asFC.customFlatRates.update('Overnight', '-5');
- Will set all rates with a service name that contains 'Overnight' to be $5 less than returnedFC.customFlatRates.update('USPS Express', '=6');
- Will set any rates from USPS that contain the word 'Express' to be $6FC.customFlatRates.update([1,2,5,7], '/2');
- Will set rates with codes 1,2,5 and 7 to be half their returned costFC.customFlatRates.update('USPS', '+20%');
- Will add 20% of the returned rate to each of the USPS ratesFC.customFlatRates.update('USPS Ground', null, null, 'Super Saver');
- Will change “USPS Ground” to be called “USPS Super Saver”
FC.customFlatRates.remove()
Removes 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.customFlatRates.remove(1);
- Will remove rate 1FC.customFlatRates.remove('all');
- Will remove all ratesFC.customFlatRates.remove('FedEx');
- Will remove all rates for FedExFC.customFlatRates.remove('Overnight');
- Will remove all rates with a service name that contains 'Overnight'FC.customFlatRates.remove('USPS Express');
- Will remove any rates from USPS that contain the word 'Express'FC.customFlatRates.remove([1,2,5,7]);
- Will remove rates with codes 1,2,5 and 7
FC.customFlatRates.reset()
Resets the shipping results to be empty.
Example:
FC.customFlatRates.reset();
FC.customFlatRates.error()
Trigger an error response for the rates.
Parameters:
message
(String)
Example:
FC.customFlatRates.error('Sorry, we can\'t ship to Canada');
Migrating from pre 2.0
If you're migrating from a 1.1 store or older, there were two common snippets available:
Multiple Flat Rates
addShippingOption()
→FC.customFlatRates.add()
updateShippingOptionCost()
→FC.customFlatRates.update()
removeShippingOption()
→FC.customFlatRates.remove()
removeCustomShippingContainer()
→FC.customFlatRates.reset()
Tiered Flat Rates
Tiered flat rates provided a single rate to customers. To migrate this snippet, after updating any JSON references within your logic to match what the new 2.0 structure, add the following to the end of your custom logic:
FC.customFlatRates.add(1, shippingCost, '', 'Standard Shipping');
The above line assumes that shippingCost
is the name of the variable you set the shipping cost in, and that the shipping you're providing is called “Standard Shipping”. You can edit that line to match your required details.
If you want to run the flat rate logic again based on an event on your checkout (like changing a custom field select box), you can use this code in the .change() function:
FC.checkout.getShippingOptions({address: FC.json.shipping_address});
If you just have one option and would like to hide the selection area, you can use the following code.
FC.client.on('render.done', function(params, next) { jQuery(".fc-checkout__section--shipping-results").hide(); });
Examples
Example 1
- Free shipping if the customer orders $40 or more, otherwise it's $5 flat rate
FC.customFlatRates.add(1, 5, 'Postmaster', 'Standard Delivery'); if (FC.json.total_item_price >= 40) { FC.customFlatRates.update(1, 0); }
Example 2
- 3 default shipping options: standard and priority with one postal provider, and express with another.
- If there are more than 5 products, remove the express option.
- If the total weight of the cart is greater that 10, adjust the shipping costs.
FC.customFlatRates.add(1, 5, 'Postmaster', 'Standard Delivery'); FC.customFlatRates.add(2, 9.45, 'Postmaster', 'Priority Delivery'); FC.customFlatRates.add(3, 10, 'PostPlus', 'Express (Next Day)'); if (FC.json.total_weight > 10) { FC.customFlatRates.update(1, 6); FC.customFlatRates.update(2, 10); FC.customFlatRates.update(3, 11.99); } if (FC.json.item_count > 5) { FC.customFlatRates.remove(3); }
Example 3
- Postage is calculated as a base price per product, with each subsequent product adding an additional cost.
- 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.
- 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.
if (address.country == "US") { postage = 10 + ((FC.json.item_count - 1) * 0.50); FC.customFlatRates.add(1, postage, 'USPS', 'Standard'); postage = 12 + ((FC.json.item_count - 1) * 1.50); FC.customFlatRates.add(2, postage, 'USPS', 'Express'); } else { postage = 15 + ((FC.json.item_count - 1) * 2); FC.customFlatRates.add(3, postage, 'USPS', 'International'); }
Example 4
- Postage is assigned per category.
- If there is a product from CategoryA in the cart, then present express option
- 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.items) { switch (FC.json.items[p].category) { case "CategoryA": hasCategoryA = true; break; case "CategoryB": hasCategoryB = true; break; } } if (hasCategoryB && !hasCategoryA) { FC.customFlatRates.add(1, 0, '', 'Free Ground Shipping'); } else if (hasCategoryA) { FC.customFlatRates.add(2, 5.99, 'USPS', 'Express'); }
Example 5
- Postage is flat rate, but free if “free shipping” coupon is present
- Allow free shipping only if a certain coupon code is present. In this case, a coupon that contains the words “free shipping” (case insensitive).
FC.customFlatRates.add(1, 5, "Postmaster", "Standard"); if(!jQuery.isEmptyObject(FC.json.coupons)) { jQuery.each(FC.json.coupons, function(i, coupon){ if(coupon.name.search(/free shipping/i) > -1) { FC.customFlatRates.update(1, 0); } }); }
Example 6
- Pricing tiers, one for the UK, one for Europe and then the rest of the world
var tier1 = ['GB']; var tier2 = ['AL', 'AD', 'AM', 'AT', 'BY', 'BE', 'BA', 'BG', 'CH', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FO', 'FI', 'FR', 'GB', 'GE', 'GI', 'GR', 'HU', 'HR', 'IE', 'IS', 'IT', 'LT', 'LU', 'LV', 'MC', 'MK', 'MT', 'NO', 'NL', 'PL', 'PT', 'RO', 'RU', 'SE', 'SI', 'SK', 'SM', 'TR', 'UA', 'VA']; if (tier1.indexOf(address.country) > -1) { // United Kingdom FC.customFlatRates.add(1, 10, 'Royal Mail', 'Standard'); } else if (tier2.indexOf(address.country) > -1) { // Europe FC.customFlatRates.add(2, 20, 'Royal Mail', 'International'); } else { // Rest of world FC.customFlatRates.add(3, 30, 'Royal Mail', 'International'); }
Changelog
- 2014/09/13 - v2.0 - Initial version
- 2014/09/16 - v2.0.1 - TBD was persisting on cart when autoselect was enabled or a single rate was returned
- 2014/09/18 - v2.0.2 - Ensure the shipping description is correctly set when auto-selecting
- 2014/09/20 - v2.0.3 - Fix missing semi-colons causing issues in Sidecart
- 2015/01/16 - v2.0.4 - Fix issue with early versions of IE acting strangely with regex
- 2015/02/20 - v2.0.5 - Add refresh of rates on checkout submit to prevent specific scenarios
- 2015/03/30 - v2.0.6 - Minor update
- 2015/09/21 - v2.0.7 - Ensure selected shipping service is maintained when altering the cart
- 2015/10/15 - v2.0.8 - Update to the events that the snippet uses to hook into the page
- 2017/08/28 - v2.0.9 - Correcting logic to only try sorting and selecting a default rate if there are actual results
- 2017/09/13 - v2.0.10 - Small bugfix from previous version relating to auto-selecting rates
- 2017/11/20 - v2.0.11 - Small change to the delay until the custom rates are returned to the page
- 2018/05/03 - v2.0.12 - Persist coupon error message temporarily to allow error response to be seen