Manipulating shipping packages in WooCommerce 2.1

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:


Array
(
[0] => Array
(
[contents] => Array
[contents_cost] => 86
[applied_coupons] => Array
[destination] => Array
(
[country] => GB
[state] =>
[postcode] =>
[city] =>
[address] =>
[address_2] =>
)
)
)

view raw

gistfile1.txt

hosted with ❤ by GitHub

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:

2013-12-12 at 15.16

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:


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;
}

view raw

gistfile1.php

hosted with ❤ by GitHub

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:

2013-12-12 at 15.30

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.


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;
}

view raw

gistfile1.php

hosted with ❤ by GitHub

Notice the ‘ship_via’ row which has been added. Now during cart and checkout, only flat rate will be allowed for the bulky items:

2013-12-12 at 15.34

Neat!

After an order is placed, this is displayed in the backend like this:

2013-12-12 at 15.37

Use-cases

So how can this new feature be used? Here are some example use cases:

  1. Shipping method restrictions
  2. Per product shipping with a different selectable method per product
  3. True per-product shipping costs
  4. Shipping per class
  5. 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 🙂


Posted

in

by

Comments

19 responses to “Manipulating shipping packages in WooCommerce 2.1”

  1. Agus MU avatar
    Agus MU

    this is very nice improvement. thanks for sharing.

    WP3.8 has been released. when will you release WC2.1RC?

    1. mikejolley avatar
      mikejolley

      Jan

  2. Justin Stern avatar
    Justin Stern

    I like it! The only thing I’d change in the example would be to use the cart item id on lines 15 and 17 rather than an incrementing index, so that as association can be made between the package item and the underlying cart item.

  3. Andrew Harris avatar

    Mike, thank you so much for sharing this. If I can get this implemented on my site it would revolutionize my shipping process. I do have a question though. First of all, where in my back-end do I need to put this code. Also, I have about 4 different shipping classes that I need to separate. What adjustments to the code do I need make for it to split up all my shipping classes at checkout. If I can get this to work I will definitely buy you a coffee!

    1. Aaron DeVandry avatar
      Aaron DeVandry

      Andrew, try adding the third code presented on this page to your functions.php file. Create a shipping class where the slug is “bulky” without the quotes. Then in the WooCommerce shipping options, go to the Flat Rate tab and add a shipping class for Bulky and the flat rate shipping amount you want to charge.

      1. Andrew Harris avatar

        Aaron, thanks. I will try this today and let you know how it turns out.

  4. Jon avatar
    Jon

    Hi Mike. This is a great feature. The trouble I am having is that WooCommerce does not seem to store which items were separated into which packages. An Order will show me that there are multiple shipments and the price that the customer paid, but I think it would be helpful for the order to show me which items were grouped into which packages. Or am I missing something?

    1. Koff avatar
      Koff

      Hi,
      Did any of you perhaps find any solutions for that?

  5. Erica avatar
    Erica

    This is a great idea and I have a couple clients who are looking to implement this system now that it is available, however I’m struggling to get it all working. I was fairly confident I followed all of the steps here, but it’s just not working properly. When you first load the cart page, it looks brilliant! Exactly as it should, but if I change one of the selections to a different shipping method, I lose everything. It goes back to the single box selection. Same goes for the checkout page, except I never seen any additional options, not even at time of loading. It’s always just the one shipping box. Have I missed something? Are there other hooks/filters I need to loop my function into? I would appreciate any tips you can give me!

    1. Erica avatar
      Erica

      Never mind, minor PHP error that altered everything. I second Jon’s idea though. How can the seller be 100% sure which items are shipping which way? My client ships per item, and some of the items qualify for rates with the same title, so telling them apart isn’t easy. Just like on the cart and checkout pages, it should label the items it includes. Thanks!

      1. Malik avatar
        Malik

        did you find solution for this problem?

  6. Andres avatar
    Andres

    Hoe would you do this using a foreach loop so that for each new shipping class, we get a new package?

  7. Ankit Aggarwal avatar
    Ankit Aggarwal

    Hi,

    I tried the snippet given above but could not get “shipping section per-package, and each can be chosen independently” in my cart..

    If you can please help me solving the issue.
    Thanks

  8. Erica avatar
    Erica

    I ended up being very surprised how many of my clients were excited for this feature, and it was so simple to setup that it seemed a waste of time to keep spending a few minutes here and there to enable it on various sites. So I took the advice of your last comment and I made a UI. I hope it can help out others reading this post 🙂

    http://wordpress.org/plugins/multiple-packages-for-woocommerce/

    1. Andrew Harris avatar

      Erica, thank you SOOO MUCH for making that plugin. That is an absolutely tremendous help for my company’s website. Shoot me a dm on twitter (@ugreptiles). I would like to send you some compensation for making this plugin. Thanks again.

  9. drio_rachel avatar
    drio_rachel

    One problem I am having is that the flat rate is not multiplying when they add multiple “bulky” items to the cart.

  10. Brad Trivers avatar

    Great post! Using the Ignite WooCommerce UPS Drop-shipping plugin along with a custom filter based on the code above, I was able to filter the packages to set the available shipping methods for each product in the cart based on the shipping class assigned to the product. I already had purchased the WooCommerce UPS Drop-shipping plugin before I saw your post, or I might also have built the packages myself too instead of relying on it (although it’s good to have a well-maintained commercial plugin). Thanks again!

  11. Jo avatar
    Jo

    Thanks so much for this! I have been pulling my hair out for ages trying to get something like this working but all of the doc and related questions out there are using out of date filters and nothing would work. This makes sense and works brilliantly.

  12. Barry avatar
    Barry

    In my store, each product has it’s own individual shipping methods and unique costs for shipping each method for each product. So it would be great if in the product setup, you could put each method and then cost, and then this would be displayed as radio buttons before the add to card button. The shipping cost would then be added to the item cost. Is there a way to do this?

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.