More at Features | Support | Blog | Demo | Community | Download


This section explains how Spree represents shipping options and how it calculates expected costs, and shows how you can configure the system with your own shipping methods. After reading it you should know:

1 Overview

Spree uses a very flexible and effective system to calculate shipping, accomodating the full range of shippment pricing: from simple flat rate to complex product-type and weight dependent calculations.

Explaining each piece separately and how they fit together can be a cumbersome task. Fortunately, using a few simple examples makes it much easier to grasp.

In that spirit, the examples are shown first in this guide.

2 Examples

2.1 Simple setup

Consider you sell t-shirts to US and Europe

And you work with 2 deliverers:

  • USPS Ground (to US)
  • FedEx (to EU)

And their pricing is as follow:

  • USPS charges $5 for one t-shirt and $2 for each additional one
  • FedEx charges $10 each, regardless of the quantity

To achieve this setup you need the following configuration:

  • Shipping Categories: All your products are the same, so you don’t need any.
  • 2 Shipping Methods (Configuration→Shipping Methods):
Name Zone Calculator
USPS Ground US Flexi Rate($5,$2)
FedEx EU_VAT FlatRate-per-item($10)

2.2 Advanced setup

Consider you sell products to a single zone (US)

And you work with 3 deliverers (Shipping Methods):

  • FedEx
  • DHL
  • US postal service.

And your products can be classified into 3 Shipping Categories:

  • Light
  • Regular
  • Heavy

And their pricing is as follow:

FedEx charges:

  • $10 for all light items regardles of how many you have
  • $2 per regular item
  • $20 for first heavy item and $15 for each next one.

DHL charges:

  • $5 per item if it’s light or regular
  • $50 per item if it’s heavy.

US Postal Service always charge based on the package weight.

To achieve this setup you need the following Calculators mapping (Configuration→Shipping Rates):

S.Category \ S.Method DHL FedEx USPS
Default (*) Per Item($5) - Weight Bucket
Light - Flat Rate($10) -
Regular - Per Item($2) -
Heavy Per Item($50) Flexi Rate($20,$15) -

(*) The default calcultor is the one defined on the Shipping Method configuration (Configuration→Shipping Methods) and it’s used when no mapping exists for a specific Shipping Category ↔ Shipping Method pair.

For US Postal Service we define only the default calculator for shipping method, since we don’t care about shipping category, and we assign it a custom calculator.

3 Design and functionality

To properly leverage Spree’s shipping system’s flexibility you must understand a few key concepts:

  • Shipping Methods
  • Zones
  • Shipping Categories
  • Calculators (through Shipping Rates)

3.1 Shipping Methods

Shipping methods are the actual service used to send the product. For example:

  • UPS Ground
  • UPS One Day
  • FedEx 2Day
  • FedEx Overnight
  • DHL International

Each shipping method is only applicable to an specific Zone as, for example, you can’t ship internationally using a local postal service.

Ie. you can’t ship from Dallas, USA to Rio de Janeiro, Brazil using UPS Ground.

3.2 Zones

Zones serve as a mechanism for grouping geographic areas together into a single entity. You can read all about how to configure and use Zones in the Zones Guide

The Shipping Address entered during checkout will define the zone the customer is in and limit the Shipping Methods available to him.

3.3 Shipping Category

Shipping category is useful if you sell products whose shipping pricing varies greatly (TVs and Mugs, for instance).

For simple setups, where shipping for all products are priced the same (ie. t-shirt only shop), there is no need to setup categories.

An example of Shipping Categories would be:

  • light
  • regular
  • heavy

Shipping Categories are created in the admin interface (Configuration → Shipping Categories) and then assigned to products (Products→Products→edit→Product Details pane).

During checkout, the shipping category of the product will determine which calculator will be used to price its shipping for each Shipping Method.

3.4 Calculators

A calculator is the component responsible for calculating the shipping price for each available Shipment Method.

Spree ships with 4 default calculators:

  • Flat rate (per order)
  • Flat rate (per item/product)
  • Flat percent
  • Flexible rate

Flexible rate: Flat rate for the first product + Flat rate for each additional product.

You can define your own calculator if you have more complex needs. In that case, check out the Calculators Guide

4 UI

4.1 What the Customer Sees

In the standard system, there is no mention of shipping until the checkout phase.

After entering a shipping address, the system displays the available shipping options and their costs for the whole order.

Only the shipping options whose zones include the shipping address are presented.

The Customer must choose one before proceeding to the next stage. At confirmation, the shipping cost has been added to the order total.

You can enable collection of extra shipping instructions by setting the option Spree::Config.set(:shipping_instructions => true). This is turned off by default. See Shipping Instructions below.

4.2 What the Orders Administrator Sees

A shipment object is created at checkout time for the order. Initially it records just the shipping method and the order it applies to. The administrator can update the record with actual shipping cost and a tracking code, and may also (once only) confirm the dispatch. This confirmation causes a shipping date to be set as the time of confirmation.

5 Advanced Shipping Methods

The main work is to provide a suitable calculator. If the calculators that comes with Spree are not enough for your needs, you might want to use an extension, if one exists, or create a custom one.

5.1 Extensions

There are a few Spree extensions which provides additional shipping methods, including special support for the fees for common carriers, or support for bulk orders. See the Spree Extension Registry for the latest information.

5.2 Writing your own

For more detailed information, check out the Calculators Guide

Your calculator should accept an Array of LineItems and return a cost. It can look at any reachable data, but typically uses the address, the order and the information from variants which are contained in the line_items.

6 Product Configuration

Users can assign products to specific ShippingCategories or include extra information in variants to enable the calculator to determine results.

Each product has an (optional) ShippingCategory: this adds product-specific information to the calculations beyond the standard information from the shipment (which is destination address, variants and quantities, and possibly some weight and dimension information if given for a variant). ShippingCategory is basically a wrapper for a string. One use is to code up specific rates, eg “Fixed $20” or “Fixed $40”, from which a calculator could extract imposed prices (and not go through its other calculations).

6.1 Variant configuration

Variants can be specified with weight and dimension information. Some shipping method calculators will use this information if it is present.

7 Shipping Instructions

The option Spree::Config[:shipping_instructions] controls collection of additional shipping instructions. This is turned off by default. If an order has any shipping instructions attached, they will be shown in an order’s shipment admin page and can also be edited at that stage. Observe that instructions are currently attached to the order and not to actual shipments.

8 The Active Shipping Extension

The popular active_shipping extension harnesses the active_shipping gem to interface with carrier APIs such as USPS, Fedex and UPS, ultimately providing Spree-compatible calculators for the different delivery services of those carriers.

To install the spree-active-shipping extension run this command at the root of your project: $ script/extension install

As an example of how to use the active_shipping extension we’ll demonstrate how to configure it to work with the USPS API. The other carriers follow a very similar pattern.

For each USPS delivery service you want to offer (e.g. “USPS Media Mail”), you will need a Shipping Method with a descriptive name (recall that shipping methods are set up through the admin panel) and a calculator (registered in the active_shipping extension) that ties the delivery service and the shipping method together.

8.1 Default Calculators

The active_shipping extension comes with several pre-configured calculators out of the box. For example here are the ones provided for the USPS carrier:

#in vendor/extensions/active_shipping/active_shipping_extension.rb def activate [ #... calculators for Fedex and UPS not shown ... Calculator::Usps::MediaMail, Calculator::Usps::ExpressMail, Calculator::Usps::PriorityMail, Calculator::Usps::PriorityMailSmallFlatRateBox, Calculator::Usps::PriorityMailRegularMediumFlatRateBoxes, Calculator::Usps::PriorityMailLargeFlatRateBox ].each(&:register) end

Each USPS delivery service you want to make available at checkout has to be associated with a corresponding shipping method. Which shipping methods are made available at checkout is ultimately determined by the zone of the customer’s shipping address. The USPS’ basic shipping categories are domestic and international, so we’ll set up zones to mimic this distinction. We need to set up two zones then, a domestic one, consisting of the USA and its territories and another international one, consisting of all other countries.

With zones in place we can now start adding some shipping methods through the admin panel. The only other essential requirement to calculate the shipping total at checkout is that each product and variant be assigned a weight.

The active_shipping gem needs some configuration variables set in order to consume the carrier web service. Among other things, it needs the API username and the origin location:

# these can be set in an initializer in your site extension Spree::ActiveShipping::Config.set(:usps_login => "YOUR_USPS_LOGIN") Spree::ActiveShipping::Config.set(:origin_country => "US") Spree::ActiveShipping::Config.set(:origin_state => "HI") Spree::ActiveShipping::Config.set(:origin_city => "Pahoa") Spree::ActiveShipping::Config.set(:origin_zip => "96778")

8.2 Adding Additional Calculators

Additional delivery services, that is not pre-configured as a calculator in the active_shipping extension, can be easily added. Say, for example, you need First Class International Parcels.

First create a calculator class that inherits from Calculator::Usps::Base and implements a description class method:

#in vendor/extensions/site/app/models/calculator/usps/first_class_mail_international_parcels.rb class Calculator::Usps::FirstClassMailInternationalParcels < Calculator::Usps::Base def self.description "USPS First-Class Mail International Package" end end

Note that unlike calculators that you write yourself, these calculators do not have to implement a #compute instance method that returns a shipping amount. The superclasses take care of that requirement.

There is one gotcha to bear in mind. The string returned by the description method must exactly match the name of the USPS delivery service. To determine the exact spelling of the delivery service you’ll need to examine what gets returned from the API:

#vendor/extensions/active_shipping/app/models/calculator/active_shipping.rb class Calculator::ActiveShipping < Calculator def compute(line_items) #.... rates = retrieve_rates(origin, destination, packages(order)) # the key of this hash is the name you need to match # raise rates.inspect return nil unless rates rate = rates[self.description].to_f + (Spree::ActiveShipping::Config[:handling_fee].to_f || 0.0) return nil unless rate # divide by 100 since active_shipping rates are expressed as cents return rate/100.0 end def retrieve_rates(origin, destination, packages) #.... # carrier is an instance of ActiveMerchant::Shipping::USPS response = carrier.find_rates(origin, destination, packages) # turn this beastly array into a nice little hash h = Hash[*response.rates.collect { |rate| [rate.service_name, rate.price] }.flatten] #.... end end

As you can see in the code above, the active_shipping gem returns an array of services with their corresponding prices, which the #retrieve_rates method converts into a hash. Below is what would get returned for an order with an international destination:

{"USPS Priority Mail International Flat Rate Envelope"=>1345, "USPS First-Class Mail International Large Envelope"=>376, "USPS USPS GXG Envelopes"=>4295, "USPS Express Mail International Flat Rate Envelope"=>2895, "USPS First-Class Mail International Package"=>396, "USPS Priority Mail International Medium Flat Rate Box"=>4345, "USPS Priority Mail International"=>2800, "USPS Priority Mail International Large Flat Rate Box"=>5595, "USPS Global Express Guaranteed Non-Document Non-Rectangular"=>4295, "USPS Global Express Guaranteed Non-Document Rectangular"=>4295, "USPS Global Express Guaranteed (GXG)"=>4295, "USPS Express Mail International"=>2895, "USPS Priority Mail International Small Flat Rate Box"=>1345}

From all the viable shipping services in this hash, the #compute method selects the one that matches the description of the calculator. At this point an optional flat handling fee (set via preferences) can be added:

rate = rates[self.description].to_f + (Spree::ActiveShipping::Config[:handling_fee].to_f || 0.0)

Finally, don’t forget to register the calculator you added. In extensions, this is accomplished in the activate method:

def activate Calculator::Usps::FirstClassMailInternationalParcels.register end

8.3 Filtering Shipping Methods On Criteria Other Than the Zone

Ordinarily it is the zone of the shipping address that determines which shipping methods are displayed to a customer at checkout. Here is how the availability of a shipping method is determined:

class Checkout < ActiveRecord::Base #... def shipping_methods return [] unless ship_address ShippingMethod.all_available(order) end #... end class ShippingMethod < ActiveRecord::Base #..... def available?(order) calculator.available?(order) end def available_to_order?(order) available?(order) && zone && zone.include?(order.ship_address) end def self.all_available(order) { |method| method.available_to_order?(order)} end end

Unless overridden, the calculator’s #available? method returns true by default. It is therefore the zone of the destination address that filters out the shipping methods. However, in some circumstances it may be necessary to filter out additional shipping methods.

Consider the case of the USPS First Class domestic shipping service, which is not offered if the weight of the package is greater than 13oz. Even though the USPS API does not return the option for First Class in this instance, nonetheless First Class will appear as an option in the checkout view with an unfortunate value of 0, since it has been set as a Shipping Method.

To ensure that First Class shipping is not available for orders that weigh more than 13oz, the calculator’s #available method must be overridden as follows:

#in vendor/extensions/site/app/models/calculator/usps/first_class_mail_parcels.rb class Calculator::Usps::FirstClassMailParcels < Calculator::Usps::Base def self.description "USPS First-Class Mail Parcel" end def available?(order) multiplier = Spree::ActiveShipping::Config[:unit_multiplier] weight = order.line_items.inject(0) do |weight, line_item| weight + (line_item.variant.weight ? (line_item.quantity * line_item.variant.weight * multiplier) : 0) end #if weight in ounces > 13, then First Class Mail is not available for the order weight > 13 ? false : true end end