Today I ran into an interesting problem, I’d share with others, because I think there is confusion is quite big in the minds – at least in terms of the people of the Internet and some users of Stackoverflow.
There is a WooCommerce 4.8 (but I think it also affects other versions). The task is to execute something after placing an order in WooCommerce.
We have several options for this. A WooCommerce order is effectively a custom WordPress post (shop_order), therefore has a post_status field. Like standard WordPress posts, these have several statuses, and you can even set them custom ones. Usually, by default status is wc-processing
. With an SQL query, we can check how many types of status orders exist in our WooCommerce online store:
SELECT post_status
FROM `wp_posts`
WHERE `post_type` = 'shop_order'
GROUP BY post_status
You can read more about Woocommerce order statuses and the Order object here.
This short introduction was necessary because it does not matter which status of the orders, what should be executed. Woocommerce offers several built-in hooks for this. What is a hook? This is a callback. What is a callback? Well, that’s when 15 things have to be explained so that you can understand one word at the end 🙂 You can read more about callback here. Read more about WordPress hooks here.
For transition management after the placed order, Woocommerce provides several hooks, such as these:
- woocommerce_checkout_order_processed
- woocommerce_payment_complete
- woocommerce_thankyou
You can find the full list here.
There are several different descriptions on the internet, and there are even differences between the Woocommerce versions. You also have to take care, and many people screw this up, that every hook has a different amount of parameters. If you hand over more, it’s no problem, but if you give less, it won’t even run. Unfortunately, there are some hooks that don’t throw an error, they just don’t work. In the end of example below 10, 3 means that it expects 3 parameters and runs with 10 priority. Because you can nest more than one, and one can overwrite the other, so that life is not so simple. This is especially worse if another plugin is already using it with some stupid 99999 priority and you can’t figure out why yours doesn’t work… 😀
Today, what I ran into is seems simple problem. I usually use the woocommerce_checkout_order_processed hook. It expects three parameters. The 1st is the Woocommerce order identifier (wc_order_id), the 2nd is the processed personal data (posted_data), and the 3rd is the order object itself (order class):
add_action( 'woocommerce_checkout_order_processed', 'exibio_woocommerce_order_processed', 10, 3 );
function exibio_woocommerce_order_processed( $wc_order_id, $posted_data, $order ) {
// it runs after placing the order
}
This hook is executed when the order has already changed to processing status (so after wc-processed). Unfortunately, there are extensions that incorrectly use the item meta data that belongs to the order and is saved, i.e. the order item meta data (these are usually the custom fields, parameters, or even variables passed by users, etc.) belonging to the order and not the woocommerce_checkout_order_processed
hook, but the woocommerce_thankyou
hook. It is not meant for the same thing, but unfortunately many people confuse it or use it incorrectly.
For this reason, I waited in vain for the fields collected by a plugin among the meta data of the order, but it only became available only after the order was placed already. This was because the plugin did not save the meta data at the moment after processed, but at the moment after the thank you endpoint. This caused that even though I queried the order_itemmeta table on-the-fly during the loop after the hook, there was no information of the Woocommerce saved data.
First, after the woocommerce_checkout_order_processed hook, I used a simple SQL query (item_id is a variable in order object). Iterating the $items object with an SQL query:
global $wpdb;
$items = $order->get_items();
foreach ($items as $item_id => $item) {
$sql = "
SELECT meta_value
FROM wp_woocommerce_order_itemmeta
WHERE `order_item_id` = {$item_id}
AND (meta_key = '_sumo_pp_payment_id')
";
$result = $wpdb->get_results($sql, ARRAY_A);
if ( !empty($result) ) {
$sumo_pp_payment_obj = $result[0];
$sumo_pp_payment_id = (int) $sumo_pp_payment_obj['meta_value'];
}
}
The same, with a internal Woocommerce function, is more simple, but none of them returned results.
$items = $order->get_items();
foreach ($items as $item_id => $item) {
$sumo_pp_payment_id = (int) wc_get_order_item_meta( $item_id, '_sumo_pp_payment_id', true );
}
Nothing. Then, when the order was placed (and the woocommerce_thankyou hook filter also processed, which happened after about 1 second), the content of the record called _sumo_pp_payment_id was already there. Lesson learned: the next time you run into one of these and you can’t find the given meta field, try all three hooks to see if that solution will be the winner.
Recommended reading:
- A WooCommerce error where the woocommerce_checkout_order_processed hook runs earlier than it should: https://github.com/woocommerce/woocommerce/issues/28113
- Why is woocommerce-checkout-order-processed is not working? https://stackoverflow.com/questions/45997729/any-idea-why-woocommerce-checkout-order-processed-not-firing-when-user-is-not-lo
- Using woocommerce_thankyou hook: https://stackoverflow.com/questions/43301940/woocommerce-thankyou-hook-not-working
- The woocommerce_payment_complete hook, which useful when you watch when paid an order:
https://stackoverflow.com/questions/46686075/woocommerce-checkout-order-processed-hook-executing-function-twice