Splitting WooCommerce orders programmatically can be incredibly useful for store owners who need to manage complex workflows.
Whether you’re dealing with pre-orders, dropshipping, or simply organizing items for multiple shipments, dividing an order based on its products ensures smoother operations and a better customer experience.
However, splitting an order isn’t just about separating items; replicating crucial details like payment methods, shipping methods, and totals is equally important to maintain consistency.
In this tutorial, we’ll explore how to programmatically split orders in WooCommerce, ensuring that cloned orders include all relevant details from the original, such as payment method, order status, and billing and shipping addresses.
By the end of this guide, you’ll have a robust solution to automate order splitting directly in your WooCommerce store. Let’s dive into the code and get started!
PHP Snippet: Programmatically Split Order Upon Purchase Based on Shipping Class
Of course, you can change the split behavior and make it happen by product ID, product category, custom meta, etc. I chose shipping class because each product can only have one shipping class (or none, in such a case it’s equal to 0), and it’s a very common case scenario.
/**
* @snippet Split Woo Order Based on Shipping Class
* @tutorial Get CustomizeWoo.com FREE
* @author Rodolfo Melogli, Business Bloomer
* @compatible WooCommerce 9
* @community https://businessbloomer.com/club/
*/
add_action( 'woocommerce_thankyou', 'bbloomer_split_order_after_checkout', 9999 );
function bbloomer_split_order_after_checkout( $order_id ) {
$order = wc_get_order( $order_id );
if ( ! $order || $order->get_meta( '_order_split' ) ) return;
$items_by_shipping_class = array();
foreach ( $order->get_items() as $item_id => $item ) {
$product = $item->get_product();
$class_id = $product->get_shipping_class_id();
$items_by_shipping_class[$class_id][$item_id] = $item;
}
if ( count( $items_by_shipping_class ) > 1 ) {
foreach ( array_slice( $items_by_shipping_class, 1 ) as $class_id => $items ) {
$new_order = wc_create_order();
$new_order->set_address( $order->get_address( 'billing' ), 'billing' );
if ( $order->needs_shipping_address() ) $new_order->set_address( $order->get_address( 'shipping' ) ?? $order->get_address( 'billing' ), 'shipping' );
foreach ( $items as $item_id => $item ) {
$new_item = new WC_Order_Item_Product();
$new_item->set_product( $item->get_product() );
$new_item->set_quantity( $item->get_quantity() );
$new_item->set_total( $item->get_total() );
$new_item->set_subtotal( $item->get_subtotal() );
$new_item->set_tax_class( $item->get_tax_class() );
$new_item->set_taxes( $item->get_taxes() );
foreach ( $item->get_meta_data() as $meta ) {
$new_item->add_meta_data( $meta->key, $meta->value, true );
}
$new_order->add_item( $new_item );
$order->remove_item( $item_id );
}
$new_order->add_order_note( 'Splitted from order ' . $order_id );
$new_order->calculate_totals();
$new_order->set_payment_method( $order->get_payment_method() );
$new_order->set_payment_method_title( $order->get_payment_method_title() );
$new_order->update_status( $order->get_status() );
$order->calculate_totals();
$order->update_meta_data( '_order_split', true );
$order->save();
}
}
}
Function Overview
The function bbloomer_split_order_after_checkout
is hooked into the WooCommerce woocommerce_thankyou
action. This means the code runs once an order has been completed and the user is redirected to the “Thank You” page.
Step-by-Step Breakdown
- Check if Order is Already Split
- The function first checks whether the order has already been split (using a custom meta field
_order_split
). If so, it skips the splitting process to avoid duplication.
- The function first checks whether the order has already been split (using a custom meta field
- Group Items by Shipping Class
- The order’s items are looped through, and they are grouped by their shipping class (
get_shipping_class_id
). This is useful when you want to split the order based on different shipping methods or classes.
- The order’s items are looped through, and they are grouped by their shipping class (
- Check If Splitting is Needed
- If there’s more than one shipping class in the order, it proceeds to split the order into multiple smaller orders.
- Create New Orders for Each Shipping Class
- For each group of items in a shipping class (other than the first one), a new order is created (
wc_create_order
). - Billing and shipping addresses are copied from the original order to the new order.
- The items are cloned, and the new order is populated with these items, along with all relevant details such as quantity, total price, taxes, and meta data.
- The original order has the items removed after they’ve been transferred to the new order.
- For each group of items in a shipping class (other than the first one), a new order is created (
- Order Note & Totals
- A note is added to the new order to indicate that it was split from the original order.
- The new order’s totals are recalculated, and the payment method is copied over from the original order.
- Finalizing the Original and New Orders
- The original order has its totals recalculated and the
_order_split
meta is added to mark it as split. - The original order is saved to reflect the changes, and the new order is finalized with the correct payment method, status, and totals.
- The original order has its totals recalculated and the
This code is ideal for situations where an order needs to be split into multiple smaller orders based on certain conditions—like different shipping classes. It ensures that each new order retains the relevant details from the original, such as billing/shipping address, payment method, and item information. This can be useful in scenarios like dropshipping or orders requiring separate shipments.
Hi! I see that we remove items from original order and we are creating new orders as guest I guess ? So in terms to keep other features like discounts based on money spent we need to assign the user? Also what would happen in user account when he is checking orders? He will see only the original order which now doesnt hold all items in it? Thanks and Happy New Year wishing you all best in 2025!
Hello Martin! This should create guest orders if the order is a guest order, and customer orders if the order is by a registered customer. For custom applications, such as conditional discounts, probably you need additional custom code
Hi Rolf, further to my previous comment i am quite keen to get this working properly with your bbloomer_split_shipping_packages_by_class code.
i placed a test order that had 1 item from shipping class 807 with $12 shipping and 1 from class 826 with $7 shipping. when it split the full $19 shipping remained on the 807 class order and the 826 order had no shipping. Any suggestions on how to get the shipping to split to the correct new order?
Peter, thanks so much for your comment! Yes, this is definitely possible, but I’m afraid it’s custom work. If you’d like to get a quote, feel free to contact me here. Thanks a lot for your understanding!