Up until 2.1, each order had to be shipped via a single method with a single price. 2.1 changes that and allows each package to be quoted and shipped individually.
By default, each order is a package, so to get this new functionality to kick in you must split it into multiple packages first.
Filtering the packages
Each package has cart items, a total cost, applied coupons, and the destination:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Array | |
( | |
[0] => Array | |
( | |
[contents] => Array | |
[contents_cost] => 86 | |
[applied_coupons] => Array | |
[destination] => Array | |
( | |
[country] => GB | |
[state] => | |
[postcode] => | |
[city] => | |
[address] => | |
[address_2] => | |
) | |
) | |
) |
Once items are placed into this package, the package is then filtered through the woocommerce_cart_shipping_packages
hook. This is where you can manipulate the packages and create more if needed.
Lets say for this example we have a regular items and a bulky item in our cart:
The bulky item cannot ship with regular items, so we give it a shipping class called ‘Bulky’ and then filter the packages to separate it out via some code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
add_filter( 'woocommerce_cart_shipping_packages', 'bulky_woocommerce_cart_shipping_packages' ); | |
function bulky_woocommerce_cart_shipping_packages( $packages ) { | |
// Reset the packages | |
$packages = array(); | |
// Bulky items | |
$bulky_items = array(); | |
$regular_items = array(); | |
// Sort bulky from regular | |
foreach ( WC()->cart->get_cart() as $item ) { | |
if ( $item['data']->needs_shipping() ) { | |
if ( $item['data']->get_shipping_class() == 'bulky' ) { | |
$bulky_items[] = $item; | |
} else { | |
$regular_items[] = $item; | |
} | |
} | |
} | |
// Put inside packages | |
if ( $bulky_items ) { | |
$packages[] = array( | |
'contents' => $bulky_items, | |
'contents_cost' => array_sum( wp_list_pluck( $bulky_items, 'line_total' ) ), | |
'applied_coupons' => WC()->cart->applied_coupons, | |
'destination' => array( | |
'country' => WC()->customer->get_shipping_country(), | |
'state' => WC()->customer->get_shipping_state(), | |
'postcode' => WC()->customer->get_shipping_postcode(), | |
'city' => WC()->customer->get_shipping_city(), | |
'address' => WC()->customer->get_shipping_address(), | |
'address_2' => WC()->customer->get_shipping_address_2() | |
) | |
); | |
} | |
if ( $regular_items ) { | |
$packages[] = array( | |
'contents' => $regular_items, | |
'contents_cost' => array_sum( wp_list_pluck( $regular_items, 'line_total' ) ), | |
'applied_coupons' => WC()->cart->applied_coupons, | |
'destination' => array( | |
'country' => WC()->customer->get_shipping_country(), | |
'state' => WC()->customer->get_shipping_state(), | |
'postcode' => WC()->customer->get_shipping_postcode(), | |
'city' => WC()->customer->get_shipping_city(), | |
'address' => WC()->customer->get_shipping_address(), | |
'address_2' => WC()->customer->get_shipping_address_2() | |
) | |
); | |
} | |
return $packages; | |
} |
This code puts bulky items in one package, and regular items in another – once done, during cart and checkout you will get a shipping section per-package, and each can be chosen independently:
Limiting available methods for a package
Each package can now also be marked to ‘ship via’ a method of your choosing. This is useful if certain packages can only be shipped by certain methods.
For this example, lets ensure bulky items are only shipped via flat rate, and not shipped for free.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
add_filter( 'woocommerce_cart_shipping_packages', 'bulky_woocommerce_cart_shipping_packages' ); | |
function bulky_woocommerce_cart_shipping_packages( $packages ) { | |
// Reset the packages | |
$packages = array(); | |
// Bulky items | |
$bulky_items = array(); | |
$regular_items = array(); | |
// Sort bulky from regular | |
foreach ( WC()->cart->get_cart() as $item ) { | |
if ( $item['data']->needs_shipping() ) { | |
if ( $item['data']->get_shipping_class() == 'bulky' ) { | |
$bulky_items[] = $item; | |
} else { | |
$regular_items[] = $item; | |
} | |
} | |
} | |
// Put inside packages | |
if ( $bulky_items ) { | |
$packages[] = array( | |
'ship_via' => array( 'flat_rate' ), | |
'contents' => $bulky_items, | |
'contents_cost' => array_sum( wp_list_pluck( $bulky_items, 'line_total' ) ), | |
'applied_coupons' => WC()->cart->applied_coupons, | |
'destination' => array( | |
'country' => WC()->customer->get_shipping_country(), | |
'state' => WC()->customer->get_shipping_state(), | |
'postcode' => WC()->customer->get_shipping_postcode(), | |
'city' => WC()->customer->get_shipping_city(), | |
'address' => WC()->customer->get_shipping_address(), | |
'address_2' => WC()->customer->get_shipping_address_2() | |
) | |
); | |
} | |
if ( $regular_items ) { | |
$packages[] = array( | |
'contents' => $regular_items, | |
'contents_cost' => array_sum( wp_list_pluck( $regular_items, 'line_total' ) ), | |
'applied_coupons' => WC()->cart->applied_coupons, | |
'destination' => array( | |
'country' => WC()->customer->get_shipping_country(), | |
'state' => WC()->customer->get_shipping_state(), | |
'postcode' => WC()->customer->get_shipping_postcode(), | |
'city' => WC()->customer->get_shipping_city(), | |
'address' => WC()->customer->get_shipping_address(), | |
'address_2' => WC()->customer->get_shipping_address_2() | |
) | |
); | |
} | |
return $packages; | |
} |
Notice the ‘ship_via’ row which has been added. Now during cart and checkout, only flat rate will be allowed for the bulky items:
Neat!
After an order is placed, this is displayed in the backend like this:
Use-cases
So how can this new feature be used? Here are some example use cases:
- Shipping method restrictions
- Per product shipping with a different selectable method per product
- True per-product shipping costs
- Shipping per class
- Free shipping for qualifying products only, not the whole cart
Right now its obviously only do-able via code, but I plan on building some extra functionality into the per-product shipping plugin to use this at some point, and I’m sure someone will make a UI for this eventually 🙂
Leave a Reply