Ask your WordPress questions! Pay money and get answers fast! Comodo Trusted Site Seal
Official PayPal Seal

woocommerce order splitting WordPress

  • SOLVED

I have tried modifying the answer provided in https://stackoverflow.com/questions/59544167/split-a-woocommerce-order-and-create-a-new-order-if-the-original-order-has-produ/59544491#59544491. I am not getting any errors when testing it, but also nothing really happens to my test order.

Can someone help me fix this code so that it's actually splitting the order?

The idea is that line items with the tag "stock" should get split into their own orders, from the main/parent order. Also the line item should be the deleted from the main/parent order.

add_action( 'woocommerce_checkout_order_processed', 'action_woocommerce_checkout_order_processed', 10, 3 );

function action_woocommerce_checkout_order_processed( $order_id, $posted_data, $order ) {
// Initialize
$check_for_stock = false;
$taxonomy = 'product_tag';

// Loop through order items
foreach ( $order->get_items() as $item_key => $item ) {
// Get product
$product = $item->get_product();

// Product has tag 'stock'
if ( has_term( 'stock', $taxonomy, $product ) ) {
// Will only be executed once if the order contains line items with tag "stock"
if ( $check_for_stock == false ) {
$check_for_stock = true;

// Create new order with stock items
$stock_order = wc_create_order();
}

// Add product to 'backorder' order
$stock_order->add_product( $product, $item['quantity'] );
$stock_order->save();

// Delete item from original order
$order->remove_item( $item->get_id() );
}
}

// If current order contains line items with product tag "stock", retrieve the necessary data from the existing order and apply it in the new order
if ( $check_for_stock ) {
// Recalculate and save original order
$order->calculate_totals();
$order->save();

// Obtain necessary information
// Get address
$address = array(
'first_name' => $order->get_billing_first_name(),
'last_name' => $order->get_billing_last_name(),
'email' => $order->get_billing_email(),
'phone' => $order->get_billing_phone(),
'address_1' => $order->get_billing_address_1(),
'address_2' => $order->get_billing_address_2(),
'city' => $order->get_billing_city(),
'state' => $order->get_billing_state(),
'postcode' => $order->get_billing_postcode(),
'country' => $order->get_billing_country()
);

// Get shipping
$shipping = array(
'first_name' => $order->get_shipping_first_name(),
'last_name' => $order->get_shipping_last_name(),
'address_1' => $order->get_shipping_address_1(),
'address_2' => $order->get_shipping_address_2(),
'city' => $order->get_shipping_city(),
'state' => $order->get_shipping_state(),
'postcode' => $order->get_shipping_postcode(),
'country' => $order->get_shipping_country()
);

// Get order currency
$currency = $order->get_currency();

// Get order payment method
$payment_gateway = $order->get_payment_method();

// Required information has been obtained, assign it to the 'backorder' order
// Set address
$stock_order->set_address( $address, 'billing' );
$stock_order->set_address( $shipping, 'shipping' );

// Set the correct currency and payment gateway
$stock_order->set_currency( $currency );
$stock_order->set_payment_method( $payment_gateway );

// Calculate totals
$stock_order->calculate_totals();

// Set order note with original ID
$stock_order->add_order_note( 'Automated "stock" order. Created from the original order ID: ' . $order_id );

// Optional: give the new 'stock' order a new status
//$stock_order->update_status( 'on-hold' );
}
}

Answers (2)

2022-09-13

Arnav Joy answers:

Which version of woocommerce you are using?


cruiseback comments:

Version 6.8.2

2022-09-13

Naveen Chand answers:

This below line in your code generates a product object instead of product id.

// Get product
$product = $item->get_product();


For has_term to work, you actually need ProductID which can be obtained as below:
// Get product id
$item_product_id = $item->get_product_id();



And then you can use your IF condition as follows:


if ( has_term( 'stock', $taxonomy, $item_product_id ) ) {
//rest of your code here


Hope this helps.


cruiseback comments:

Thanks for the input, I tried implementing your code and now the order is split, but there are some problems.

The line item containing tag "stock" is removed from the parent order, but it isn't being inserted into the new order. So I end up having the parent order and the new order (which is empty).

Also, when the line item is removed, it is also not shown on the 'thank you' page, because it now belongs to a new order (I assume).


Naveen Chand comments:

In this line:
$stock_order->add_product( $product, $item['quantity'] );

I guess the quantity that you are trying to add to the new order is going in as 'zero' and that is the reason the order is split but it is just empty with no items in it.

So, the correct way to get an item's quantity from an existing order is to use this line:


$item_quantity = $item->get_quantity(); // Get the item quantity


Please add the above line just before adding product to your new order. The new code should look like this:



//get item quantity
$item_quantity = $item->get_quantity(); // Get the item quantity

// Add product to 'stock' order
$stock_order->add_product( $product, item_quantity);
$stock_order->save();



This will send the item quantity correctly to the new order.

Pls let me know how it goes.


Naveen Chand comments:

There was a typo in my previous answer. I forgot to add $ before item_quantity. use the revised one as below:

//get item quantity
$item_quantity = $item->get_quantity(); // Get the item quantity

// Add product to 'stock' order
$stock_order->add_product( $product, $item_quantity);
$stock_order->save();


cruiseback comments:

I tried implementing your suggestions, but the new order still remains empty. I now have the following:

add_action("woocommerce_checkout_order_processed","action_woocommerce_checkout_order_processed",10,3);

function action_woocommerce_checkout_order_processed($order_id,$posted_data,$order) {
// Initialize
$check_for_stock = false;
$taxonomy = "product_tag";

// Loop through order items
foreach ($order->get_items() as $item_key => $item) {
// Get product
$item_product_id = $item->get_product_id();

// Product has tag 'stock'
if ( has_term( 'stock', $taxonomy, $item_product_id ) ) {
// Will only be executed once if the order contains line items with tag "stock"
if ($check_for_stock == false) {
$check_for_stock = true;

// Create new order with stock items
$stock_order = wc_create_order();
}

//get item quantity
$item_quantity = $item->get_quantity(); // Get the item quantity

// Add product to 'stock' order
$stock_order->add_product( $product, $item_quantity);
$stock_order->save();

// Delete item from original order
$order->remove_item($item->get_id());
}
}

// If current order contains line items with product tag "stock", retrieve the necessary data from the existing order and apply it in the new order
if ($check_for_stock) {
// Recalculate and save original order
$order->calculate_totals();
$order->save();

// Obtain necessary information
// Get address
$address = [
"first_name" => $order->get_billing_first_name(),
"last_name" => $order->get_billing_last_name(),
"email" => $order->get_billing_email(),
"phone" => $order->get_billing_phone(),
"address_1" => $order->get_billing_address_1(),
"address_2" => $order->get_billing_address_2(),
"city" => $order->get_billing_city(),
"state" => $order->get_billing_state(),
"postcode" => $order->get_billing_postcode(),
"country" => $order->get_billing_country(),
];

// Get shipping
$shipping = [
"first_name" => $order->get_shipping_first_name(),
"last_name" => $order->get_shipping_last_name(),
"address_1" => $order->get_shipping_address_1(),
"address_2" => $order->get_shipping_address_2(),
"city" => $order->get_shipping_city(),
"state" => $order->get_shipping_state(),
"postcode" => $order->get_shipping_postcode(),
"country" => $order->get_shipping_country(),
];

// Get order currency
$currency = $order->get_currency();

// Get order payment method
$payment_gateway = $order->get_payment_method();

// Required information has been obtained, assign it to the 'backorder' order
// Set address
$stock_order->set_address($address, "billing");
$stock_order->set_address($shipping, "shipping");

// Set the correct currency and payment gateway
$stock_order->set_currency($currency);
$stock_order->set_payment_method($payment_gateway);

// Calculate totals
$stock_order->calculate_totals();

// Set order note with original ID
$stock_order->add_order_note(
'Automated "stock" order. Created from the original order ID: ' .
$order_id
);

// Optional: give the new 'stock' order a new status
$stock_order->update_status( 'on-hold' );
}
}


Naveen Chand comments:

Ok, I found the bug. In my first response, when I asked you to insert the $item_product_id, you have actually replaced the $product line. The $product variable should have been there as it is because it is used in:

$stock_order->add_product( $product, $item_quantity);


I have added that line back and below is the full code. Please use the below code as it is:

add_action("woocommerce_checkout_order_processed","action_woocommerce_checkout_order_processed",10,3);

function action_woocommerce_checkout_order_processed($order_id,$posted_data,$order) {
// Initialize
$check_for_stock = false;
$taxonomy = "product_tag";

// Loop through order items
foreach ($order->get_items() as $item_key => $item) {
// Get product
$product = $item->get_product();
$item_product_id = $item->get_product_id();

// Product has tag 'stock'
if ( has_term( 'stock', $taxonomy, $item_product_id ) ) {
// Will only be executed once if the order contains line items with tag "stock"
if ($check_for_stock == false) {
$check_for_stock = true;

// Create new order with stock items
$stock_order = wc_create_order();
}

//get item quantity
$item_quantity = $item->get_quantity(); // Get the item quantity

// Add product to 'stock' order
$stock_order->add_product( $product, $item_quantity);
$stock_order->save();

// Delete item from original order
$order->remove_item($item->get_id());
}
}

// If current order contains line items with product tag "stock", retrieve the necessary data from the existing order and apply it in the new order
if ($check_for_stock) {
// Recalculate and save original order
$order->calculate_totals();
$order->save();

// Obtain necessary information
// Get address
$address = [
"first_name" => $order->get_billing_first_name(),
"last_name" => $order->get_billing_last_name(),
"email" => $order->get_billing_email(),
"phone" => $order->get_billing_phone(),
"address_1" => $order->get_billing_address_1(),
"address_2" => $order->get_billing_address_2(),
"city" => $order->get_billing_city(),
"state" => $order->get_billing_state(),
"postcode" => $order->get_billing_postcode(),
"country" => $order->get_billing_country(),
];

// Get shipping
$shipping = [
"first_name" => $order->get_shipping_first_name(),
"last_name" => $order->get_shipping_last_name(),
"address_1" => $order->get_shipping_address_1(),
"address_2" => $order->get_shipping_address_2(),
"city" => $order->get_shipping_city(),
"state" => $order->get_shipping_state(),
"postcode" => $order->get_shipping_postcode(),
"country" => $order->get_shipping_country(),
];

// Get order currency
$currency = $order->get_currency();

// Get order payment method
$payment_gateway = $order->get_payment_method();

// Required information has been obtained, assign it to the 'backorder' order
// Set address
$stock_order->set_address($address, "billing");
$stock_order->set_address($shipping, "shipping");

// Set the correct currency and payment gateway
$stock_order->set_currency($currency);
$stock_order->set_payment_method($payment_gateway);

// Calculate totals
$stock_order->calculate_totals();
$stock_order->save();

// Set order note with original ID
$stock_order->add_order_note(
'Automated "stock" order. Created from the original order ID: ' .
$order_id
);

// Optional: give the new 'stock' order a new status
$stock_order->update_status( 'on-hold' );
}
}


It is working and tested on my server. I am sure it will also work for you now.


cruiseback comments:

This works brilliantly! Thanks a lot Naveen.

One last question, would it be possible to somehow delay the order splitting action or run it later? The problem is that the products that are being split are being removed from the thank you page, which will probably confuse some customers.

Otherwise this is excellent!


Naveen Chand comments:

Yes, that should be possible. Instead of woocommerce_checkout_order_processed, you will need to use woocomerce_thankyou hook. That also means that there will be slight change in the code because order object will need to be generated with order id first.

Below is the code, where user will see the 'thank you' page as if they have a single order but the splitting happens after that. Please try this code to see how it works:


add_action("woocommerce_thankyou","action_woocommerce_checkout_order_processed",10,3);

function action_woocommerce_checkout_order_processed($order_id) {
// Initialize
$order = wc_get_order($order_id);
$check_for_stock = false;
$taxonomy = "product_tag";

// Loop through order items
foreach ($order->get_items() as $item_key => $item) {
// Get product
$product = $item->get_product();
$item_product_id = $item->get_product_id();

// Product has tag 'stock'
if ( has_term( 'stock', $taxonomy, $item_product_id ) ) {
// Will only be executed once if the order contains line items with tag "stock"
if ($check_for_stock == false) {
$check_for_stock = true;

// Create new order with stock items
$stock_order = wc_create_order();
}

//get item quantity
$item_quantity = $item->get_quantity(); // Get the item quantity

// Add product to 'stock' order
$stock_order->add_product( $product, $item_quantity);
$stock_order->save();

// Delete item from original order
$order->remove_item($item->get_id());
}
}

// If current order contains line items with product tag "stock", retrieve the necessary data from the existing order and apply it in the new order
if ($check_for_stock) {
// Recalculate and save original order
$order->calculate_totals();
$order->save();

// Obtain necessary information
// Get address
$address = [
"first_name" => $order->get_billing_first_name(),
"last_name" => $order->get_billing_last_name(),
"email" => $order->get_billing_email(),
"phone" => $order->get_billing_phone(),
"address_1" => $order->get_billing_address_1(),
"address_2" => $order->get_billing_address_2(),
"city" => $order->get_billing_city(),
"state" => $order->get_billing_state(),
"postcode" => $order->get_billing_postcode(),
"country" => $order->get_billing_country(),
];

// Get shipping
$shipping = [
"first_name" => $order->get_shipping_first_name(),
"last_name" => $order->get_shipping_last_name(),
"address_1" => $order->get_shipping_address_1(),
"address_2" => $order->get_shipping_address_2(),
"city" => $order->get_shipping_city(),
"state" => $order->get_shipping_state(),
"postcode" => $order->get_shipping_postcode(),
"country" => $order->get_shipping_country(),
];

// Get order currency
$currency = $order->get_currency();

// Get order payment method
$payment_gateway = $order->get_payment_method();

// Required information has been obtained, assign it to the 'backorder' order
// Set address
$stock_order->set_address($address, "billing");
$stock_order->set_address($shipping, "shipping");

// Set the correct currency and payment gateway
$stock_order->set_currency($currency);
$stock_order->set_payment_method($payment_gateway);

// Calculate totals
$stock_order->calculate_totals();
$stock_order->save();

// Set order note with original ID
$stock_order->add_order_note(
'Automated "stock" order. Created from the original order ID: ' .
$order_id
);

// Optional: give the new 'stock' order a new status
$stock_order->update_status( 'on-hold' );
}
}


I tried in my server and it works. The user sees all items in the order on their thank you page but in the background, the order gets split.


cruiseback comments:

Excellent Naveen, that is a brilliant solution.

But won't the 'woocomerce_thankyou' hook be fired everytime the thank you page is reloaded, or if the customer comes back and visits that page (by accident via browser history for example).

Will that split the order again in that case?


Naveen Chand comments:

thanks.

But won't the 'woocomerce_thankyou' hook be fired everytime the thank you page is reloaded, or if the customer comes back and visits that page (by accident via browser history for example).


Even though 'woocommerce_thankyou' hook is triggered every time thank_you page is reloaded, there is no way that the splitting will happen again because:

1. the split would have already happened the first time, so there will not be any product with the tag 'stock' in that order anymore.

2. the new order will show only the left-over items whenever thank_you page is manually loaded from second-time onwards. It is only the first-time that the thank you page shows all items in single order. Because in our recent version (unlike the first version), we have triggered the split after thank you page is loaded. Until then they were all in one order. But if you refresh that page, it will be evident that it has been split.

Also, I recommend that you need to have a system where the customer is informed about the split so that it explains him why an order has all items on thank you page and when she returns to that page, she will no longer find it the same. This is best done via order email or order notes - but I guess its a custom work :) which I will be happy to do separately.