WooCommerce: Change order status based on custom Meta Value - php

I'm trying to run the following function daily, to auto-complete all processing order older than 10 days and who have a specific custom meta value.
I'm using the following snippet, however this will simply not work. Any idea as to why?
function autoComplete(){
$orders = wc_get_orders( array(
'status' => 'wc-processing',
'date_created' => '<' . ( time() - 10 * DAY_IN_SECONDS ),
'meta_key' => 'status_us',
'meta_compare' => '=',
'meta_value' => 'Sent to USA',
) );
foreach ($orders as $order){
$order->update_status( 'completed' );
}
}
if ( ! wp_next_scheduled( 'autoComplete' ) ) {
wp_schedule_event( time(), 'daily', 'autoComplete' );
}
Is there any error I've missed? Thanks for your help!

You did a good attempt but you made a few mistakes.
The following code goes inside your functions.php.
add_action( 'wp_loaded','start_custom_code' );
// Once everything theme and plugins are loaded we register our cron job.
function start_custom_code() {
if ( ! wp_next_scheduled( 'bks_mark_processing_order_complete_if_sent_to_usa' ) ) {
wp_schedule_event( time(), 'daily', 'bks_mark_processing_order_complete_if_sent_to_usa' );
}
}
add_action( 'bks_mark_processing_order_complete_if_sent_to_usa', 'bks_mark_processing_order_complete_if_sent_to_usa' );
Your function has minor errors bks_mark_processing_order_complete_if_sent_to_usa()
function bks_mark_processing_order_complete_if_sent_to_usa(){
$args = array(
'status' => array( 'wc-processing'),
'limit' => -1,
'date_created' => '>' . ( time() - 864000 ), // your mistake 1
'status_us' => 'Sent to USA', // your mistake 2
);
$orders = wc_get_orders( $args );
foreach ($orders as $order){
$order->update_status( 'completed' );
$order->save(); // your mistake 3
}
};
Mistake Explanations
While your attempt was in right direction but 'date_created' => '<' . ( time() - 10 * DAY_IN_SECONDS ), you had to use > instead of < also you didn't acutally set DAY_IN_SECONDS You had to replace it with 86400. So the correct value would be '>' . ( time() - 864000 ). For 10 days 10 * 86400 = 864000. You can read about this explanation here in the WooCommerce documentation.
Here I have created new custom variable for you which is set using woocommerce_order_data_store_cpt_get_orders_query and then queried. Code that needs to be added.
function handle_custom_query_var( $query, $query_vars ) {
if ( ! empty( $query_vars['status_us'] ) ) {
$query['meta_query'][] = array(
'key' => 'status_us',
'value' => esc_attr( $query_vars['status_us'] ),
);
}
return $query;
}
add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', 'handle_custom_query_var', 10, 2 );
You updated the status but you forgot to save it. $order->save();
So to summarise you have to add the following code in your functions.php
add_action( 'wp_loaded','start_custom_code' );
add_action( 'bks_mark_processing_order_complete_if_sent_to_usa', 'bks_mark_processing_order_complete_if_sent_to_usa' );
function start_custom_code() {
if ( ! wp_next_scheduled( 'bks_mark_processing_order_complete_if_sent_to_usa' ) ) {
wp_schedule_event( time(), 'daily', 'bks_mark_processing_order_complete_if_sent_to_usa' );
}
}
function bks_mark_processing_order_complete_if_sent_to_usa(){
$args = array(
'status' => array( 'wc-processing'),
'limit' => -1,
'date_created' => '>' . ( time() - 864000 ),
'status_us' => 'Sent to USA',
);
$orders = wc_get_orders( $args );
foreach ($orders as $order){
$order->update_status( 'completed' );
$order->save();
}
};
function handle_custom_query_var( $query, $query_vars ) {
if ( ! empty( $query_vars['status_us'] ) ) {
$query['meta_query'][] = array(
'key' => 'status_us',
'value' => esc_attr( $query_vars['status_us'] ),
);
}
return $query;
}
add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', 'handle_custom_query_var', 10, 2 );
The above code is TESTED and WORKS.
Proof:
Install WP CRON plugin to check your cron. See above screenshot. You can test by hitting Run Now.
Caveat :
WP Cron runs, when somebody visits your website. Thus if nobody visits, ?>the cron never runs.
Read this : https://wordpress.stackexchange.com/a/179774/204925

Here is what I do to validate orders based on a CRON task.
$order->payment_complete('transaction ID string'); //validate payment
wc_reduce_stock_levels($order->get_id()); //reduce stock
$woocommerce->cart->empty_cart(); //empty cart
$order->update_status('completed', 'A order note string'); //change order statyus
I believe that you don't need the "wc-" prefix.
Are you sure that your scheduled event is working? and that $orders is filled? Please test your method without the schedule first.

Related

Woocommerce's wc_get_orders not working in function.php?

I am trying to get all orders from woocommerce. Following the instruction on https://github.com/woocommerce/woocommerce/wiki/wc_get_orders-and-WC_Order_Query
I put the following code in my function.php
// Get latest 3 orders.
$args = array(
'limit' => 3,
);
$orders = wc_get_orders( $args );
var_dump($orders);
However, it outputs an empty array.
I checked my code and found I actually used wc_get_orders in a hook as below
add_action( 'woocommerce_order_status_changed', 'change_role_on_first_purchase',10,4 );
function change_role_on_first_purchase( $order_id,$old_status, $new_status, $order ) {
$userID = $order->user_id;
$user = new WP_User( $userID );
if ( in_array('subscriber',$user->roles) ){
$args = array(
'customer' => $userID,
'exclude' => array( $order->get_id() ),
'status' => array('completed')
);
$orders = wc_get_orders($args);
if (!$orders && $new_status == "completed"){
$user->set_role('customer');
}
}
}
This is used to change the user's role from subscriber to customer after he places the first order. This function works on my site. So wc_get_orders works here. Why then it is not working in my function.php?
It outputs an empty array because Woocommerce has not yet registered order types/statuses when you're calling wc_get_orders()
Try adding it after init event:
add_action( 'init', 'test_init' );
function test_init() {
// Get latest 3 orders.
$args = array(
'limit' => 3,
);
$orders = wc_get_orders( $args );
var_dump($orders);
}
https://developer.wordpress.org/reference/hooks/init/

Update order status from custom statuses to completed in WooCommerce everyday at 23.00 if meta_key = Delivery Date and meta_value = this day

I am trying to make a Function, that automaticly updates the Post status to Completed if the status is wc-ishoej-afhentning, wc-roskilde-afhent, wc-koege-afhentning, wc-soeborg-afhent or wc-birkeroed-afhent. But it can only change the status, if a metakey(Delivery date) in postmeta is == the meta value of the Delivery date is == The current date.
This is an image of the values from every order in the database
I believe the format is j F, Y
I am really hoping someone could help me.. i have some different codes, which i have been looking at trying to figure it out myself, but i seem to get nowhere.
First code below, this one i found on stackoverflow, but can seem to figure out how to change it into what i need.
add_action( 'admin_init', 'update_order_status_on_monday' );
function update_order_status_on_monday() {
if ( date( 'D', strtotime( 'now' ) ) === 'Mon' && !get_transient( '_updated_order_status_on_monday' ) ) {
$processing_orders = wc_get_orders( $args = array(
'numberposts' => -1,
'post_status' => 'wc-processing',
) );
if ( !empty( $processing_orders ) ) {
foreach ( $processing_orders as $order )
$order->update_status( 'completed' );
}
set_transient( '_updated_order_status_on_monday', true );
} elseif ( date( 'D', strtotime( 'now' ) ) !== 'Mon' ) {
delete_transient( '_updated_order_status_on_monday' );
}
}
The second code, i one i am currently using to change to the custom statuses, which i have added here below
add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_ishoej_order' );
function custom_woocommerce_auto_ishoej_order( $order_id ) {
if ( ! $order_id ) {
return;
}
if( get_post_meta($order_id, 'Pickup Location', true ) == "Ishøj" ) {
$order = wc_get_order( $order_id );
$order->update_status( 'wc-ishoej-afhentning' );
} elseif ( get_post_meta($order_id, 'Pickup Location', true ) == "Roskilde" ) {
$order = wc_get_order( $order_id );
$order->update_status( 'wc-roskilde-afhent' );
} elseif ( get_post_meta($order_id, 'Pickup Location', true ) == "Køge" ) {
$order = wc_get_order( $order_id );
$order->update_status( 'wc-koege-afhentning' );
} elseif ( get_post_meta($order_id, 'Pickup Location', true ) == "Søborg" ) {
$order = wc_get_order( $order_id );
$order->update_status( 'wc-soeborg-afhent' );
} elseif ( get_post_meta($order_id, 'Pickup Location', true ) == "Birkerød" ) {
$order = wc_get_order( $order_id );
$order->update_status( 'wc-birkeroed-afhent' );
}
}
This is how it should be done. Go through the code carefully. I have added comments for you to understand what every function does.
/*
* Trigger the code when everything is loaded.
* */
add_action( 'wp_loaded','start_custom_code' );
/*
* Set a cron job for daily Midnight.
* */
function start_custom_code() {
if ( ! wp_next_scheduled( 'bks_delivery_to_complete' ) ) {
wp_schedule_event( (strtotime('midnight') + (3600 * 23)) , 'daily', 'bks_delivery_to_complete' );
}
}
// Plug the cron job with function 'bks_mark_order_complete_if_delivered_today'
add_action( 'bks_delivery_to_complete', 'bks_mark_order_complete_if_delivered_today' );
/*
* This function selects order with wc-processing status and with orders
* having meta key 'Delivery Date' set as current date.
* */
function bks_mark_order_complete_if_delivered_today(){
$args = array(
'status' => array( 'wc-processing'), // Add any other statuses order of which you want.
'limit' => -1,
'delivery_date' => date("d M, Y"), // Use Current date to fetch Orders.
);
$orders = wc_get_orders( $args );
foreach ($orders as $order){
$order->update_status( 'completed' );
$order->save();
}
};
/* This functions add a new key 'delivery_date'
* which enables user to pass date to check against
* Meta Data 'Delivery Date'.
* */
function bks_handle_custom_query_var( $query, $query_vars ) {
if ( ! empty( $query_vars['delivery_date'] ) ) {
$query['meta_query'][] = array(
'key' => 'Delivery Date',
'value' => esc_attr(date("d M, Y") ),
);
}
return $query;
}
add_filter( 'woocommerce_order_data_store_cpt_get_orders_query', 'bks_handle_custom_query_var', 10, 2 );
Things to keep in mind :-
We are using wordpress cron job, and according to the doc WP-Cron does not run constantly as the system cron does; it is only triggered on page load. and as the code is set to run at 11 O`Clock every night if somone doesn't visit your website the cron job will not run. And once the date passes, the code will be applicable for other day. Solution to this is to use system crons. [ Google to set system crons intead of wordpress cron, they are more reliable.]
According to your question your meta tag is Delivery Date, it should be strictly followed or else because an error won't select the order and it will be missed. so Deliver Date ❌ (with two spaces) wont work Deliver date ❌ (Small d for date). I guess you understand what I am saying.
Date has to strictly follow "d M, Y" format. Which means today is 05 Jun, 2021 not 5 Jun, 2021. It is a string comparision. If the meta data is set using a code I don't think there could be a problen.
I couldn't understand the statuses that you suggested so I have just used processing for now. You can add others in bks_mark_order_complete_if_delivered_today() function.
The code is tested and WORKS.
Install WP CRON plugin to check your cron. You can test by hitting Run Now.
This question is worth looking at when working with wp_cron https://wordpress.stackexchange.com/a/179774/204925

Writing to an SQL table on order completion

I recently migrated my Wordpress website to a hosting server and have had issues getting one function to work. When testing it on my local server it worked perfectly but now I can't seem to figure out why it will not write to my SQL table. The function is here below, any help will be very appreciated. I was also wondering if there is a way to only have the SQL query run when I receive the payment from PayPal if it would require a large change to this program it is not an issue.
add_action( 'woocommerce_order_status_changed', 'checkout_custom_table_insert', 20, 4 );
function checkout_custom_table_insert( $order_id, $old_status, $new_status, $order ){
// Only for 'processing' and 'completed' order status changes
$statuses = array( 'processing', 'completed' );
if ( ! in_array( $new_status, $statuses ) ) return;
// Check if data has been already updated (avoid repetitions)
$is_done = get_post_meta( $order_id, '_checkout_table_updated', true );
if( ! empty($is_done) ) return; // We exit if it has been already done
global $wpdb;
// Loop through order items
foreach ($order->get_items() as $item){
$product = $item->get_product(); // Get the variation product object
// Choose in the array which data you want to insert (each line is a table column)
$args = array(
'rank' => $product->get_attribute( 'pa_rank' ),
'money' => $product->get_attribute( 'pa_money' ),
'spawner' => $product->get_attribute( 'pa_spawner' ),
'permission' => $product->get_attribute( 'pa_permission' ),
'kit' => $product->get_attribute( 'pa_kit' ),
'crate' => $product->get_attribute( 'pa_crate' ),
'stag' => $product->get_attribute( 'pa_stag' ),
'duration' => $product->get_attribute( 'pa_duration' ),
'end_date' => date("Y-m-d", strtotime("+$duration Months")),
'username' => get_post_meta( $order_id, 'My Field', true ),
'executed' => "false",
);
// The SQL INSERT query
$table = "checkout"; // or "{$wpdb->prefix}checkout";
$wpdb->insert( $table, $args ); // Insert the data
}
// Mark this task as done for this order
update_post_meta( $order_id, '_checkout_table_updated', '1' );
}
Ohh, you have order status as below to check against order status:
$statuses = array( 'processing', 'completed' );
but woocommerce order status are as follow:
'wc-pending','wc-processing', 'wc-on-hold' ,'wc-completed' , 'wc-cancelled', 'wc-refunded' , 'wc-failed'
update your array to:
$statuses = array( 'wc-processing', 'wc-completed' );

Avoiding action Being Triggered Twice in woocommerce_thankyou hook

I am using the woocommerce action below to call a custom function, but for some reason it's being triggered twice on every order. Does anyone know why this could be or how to fix it so that it only calls once per order?
add_action( 'woocommerce_thankyou', 'parent_referral_for_all', 10, 1 );
function parent_referral_for_all( $order_id ) {
....
}
UPDATE
I thought the action was being triggered twice but I'm not so sure now. I'm using this action to add another referral inside the affiliatewp plugin, which is adding twice, yet my echo of "Thank You" only appears once.
Everything is working as intended except that the referral (and it's associated order note) are being added twice.
Any help would be greatly appreciated.
That full function:
function parent_referral_for_all( $order_id ) {
//Direct referral
$existing = affiliate_wp()->referrals->get_by( 'reference', $order_id );
$affiliate_id = $existing->affiliate_id;
//Total amount
if( ! empty( $existing->products ) ) {
$productsarr = maybe_unserialize( maybe_unserialize( $existing->products ) );
foreach( $productsarr as $productarr ) {
$bigamount = $productarr['price'];
}
}
//Parent amount
$parentamount = $bigamount * .1;
$affiliate_id = $existing->affiliate_id;
$user_info = get_userdata( affwp_get_affiliate_user_id( $existing->affiliate_id ) );
$parentprovider = $user_info->referral;
//Affiliate id by username
$userparent = get_user_by('login',$parentprovider);
$thisid = affwp_get_affiliate_id($userparent->ID);
$args = array(
'amount' => $parentamount,
'reference' => $order_id,
'description' => $existing->description,
'campaign' => $existing->campaign,
'affiliate_id' => $thisid,
'visit_id' => $existing->visit_id,
'products' => $existing->products,
'status' => 'unpaid',
'context' => $existing->context
);
$referral_id2 = affiliate_wp()->referrals->add( $args );
echo "Thank you!";
if($referral_id2){
//Add the order note
$order = apply_filters( 'affwp_get_woocommerce_order', new WC_Order( $order_id ) );
$order->add_order_note( sprintf( __( 'Referral #%d for %s recorded for %s', 'affiliate-wp' ), $referral_id2, $parentamount, $parentamount ) );
}
}
add_action( 'woocommerce_thankyou', 'parent_referral_for_all', 10, 1 );
To avoid this repetition, you couldadd a custom post meta data to the current order, once your "another" referral has been added the first time.
So your code will be:
function parent_referral_for_all( $order_id ) {
## HERE goes the condition to avoid the repetition
$referral_done = get_post_meta( $order_id, '_referral_done', true );
if( empty($referral_done) ) {
//Direct referral
$existing = affiliate_wp()->referrals->get_by( 'reference', $order_id );
$affiliate_id = $existing->affiliate_id;
//Total amount
if( ! empty( $existing->products ) ) {
$productsarr = maybe_unserialize( maybe_unserialize( $existing->products ) );
foreach( $productsarr as $productarr ) {
$bigamount = $productarr['price'];
}
}
//Parent amount
$parentamount = $bigamount * .1;
$affiliate_id = $existing->affiliate_id;
$user_info = get_userdata( affwp_get_affiliate_user_id( $existing->affiliate_id ) );
$parentprovider = $user_info->referral;
//Affiliate id by username
$userparent = get_user_by('login',$parentprovider);
$thisid = affwp_get_affiliate_id($userparent->ID);
$args = array(
'amount' => $parentamount,
'reference' => $order_id,
'description' => $existing->description,
'campaign' => $existing->campaign,
'affiliate_id' => $thisid,
'visit_id' => $existing->visit_id,
'products' => $existing->products,
'status' => 'unpaid',
'context' => $existing->context
);
$referral_id2 = affiliate_wp()->referrals->add( $args );
echo "Thank you!";
if($referral_id2){
//Add the order note
$order = apply_filters( 'affwp_get_woocommerce_order', new WC_Order( $order_id ) );
$order->add_order_note( sprintf( __( 'Referral #%d for %s recorded for %s', 'affiliate-wp' ), $referral_id2, $parentamount, $parentamount ) );
## HERE you Create/update your custom post meta data to avoid repetition
update_post_meta( $order_id, '_referral_done', 'yes' )
}
}
}
add_action( 'woocommerce_thankyou', 'parent_referral_for_all', 10, 1 );
I hope this will help.
You can check for the existence of the _thankyou_action_done post_meta key like so:
<?php
/**
* Allow code execution only once when the hook is fired.
*
* #param int $order_id The Woocommerce order ID.
* #return void
*/
function so41284646_trigger_thankyou_once( $order_id ) {
if ( ! get_post_meta( $order_id, '_thankyou_action_done', true ) ) {
// Your code here.
}
}
add_action( 'woocommerce_thankyou', 'so41284646_trigger_thankyou_once', 10, 1 );

wp_schedule_event is planned but function not firing

I have some trouble with the wp_schedule_event function and I was not able to find any solution here so far.
The event is triggert, atleast I see the next planned when calling wp_next_scheduled( 'update_expired_events_hourly' ), but when the time is reached, nothing is happening.
Also the function is working fine when I call it manually. I tried to fire the event by calling /wp-cron.php?doing_wp_cron, but that had no effect either.
The following code is in my themes functions.php
Any suggestions how to fix that?
Thank you in advance!
add_action( 'wp', 'expired_activation' );
add_action( 'update_expired_events_hourly', 'update_expired_events' );
function expired_activation(){
if ( !wp_next_scheduled( 'update_expired_events_hourly' ) ) {
wp_schedule_event(time(), 'hourly', 'update_expired_events_hourly');
}
}
function update_expired_events(){
// events query
$args = array(
'post_type' => 'espresso_events',
'post_status' => array('publish', 'pending', 'draft', 'auto-draft', 'future', 'private', 'inherit', 'trash'),
'post_per_page' => -1,
'tax_query' => array(
array(
'taxonomy' => 'espresso_event_categories',
'field' => 'slug',
'terms' => 'ee_exp', // the expired category slug
'operator' => 'NOT IN',
),
)
);
$events = new WP_QUERY($args);
$today = strtotime(current_time( "Y-m-d H:i:s", $gmt =0 )); // get current date
if ( $events->have_posts() ){
while ( $events->have_posts() ){
$events->the_post();
//get event end date
$event_end = strtotime( espresso_event_end_date( 'Y-m-d', 'H:i:s', false, false ) ); // get event end date
if( $event_end - (60*60*24) < $today ){
wp_set_object_terms( get_the_ID(), 'ee_exp', 'espresso_event_categories', true); // Add post to expired category
}
}
}
}
I forgot to mention, that define('DISABLE_WP_CRON'); is set to false in my config.php
---------------------- EDIT ----------------------
The code above is working. Apparently the only thing not working was firing the event via URL (/wp-cron.php?doing_wp_cron). Sorry for that and thx for your help!
I think you shouldn't disable wp_cron I suggest you use wp_schedule_single_event it worked well for me
wp_schedule_single_event( time() + 3600, 'update_expired_events_hourly' );

Categories