phpwordpresswoocommerceordersdate-range

Get User total amount spent from a defined date range in WooCommerce


I've been trying to get my head around some code I've been modifying to fit my purpose and needs. In a nutshell, I want to display the total amount of money spent for a user on the frontend (Based on a date range the user selects through using a date picker).

Here's my code so far:

   function display_total_amount_spent( $user_id=null ) {
    if ( empty($user_id) ){
        $user_id = get_current_user_id();
    }
    
    $today_year = date( 'Y' );
    $today_month = date( 'm' );
    $day = date( 'd' );
    if ($today_month == '01') {
        $month = '12';
        $year = $today_year - 1;
    } else{
        $month = $today_month - 1;
        $month = sprintf("%02d", $month);
        $year = $today_year - 1;
    }

    $end_date = 'user_selected_date_period';
    $single = true;
    $fetch_end_date = get_user_meta($user_id, $end_date, $single);
    $convert_ed_to_time = strtotime($fetch_end_date);

    $now = strtotime('now');
    $gap_days = $convert_ed_to_time;
    $gap_days_in_seconds = 60*60*24*$gap_days;
    $gap_time = $now - $gap_days_in_seconds;

 $args = array(
    'post_type'   => 'shop_order',
    'post_status' => array( 'wc-completed', 'wc-processing' ),
    'numberposts' => -1,
    'meta_key'    => '_customer_user',
    'meta_value'  => $user_id,
    'date_query' => array(
        'relation' => 'OR',
        array(
            'year' => $today_year,
            'month' => $today_month,
        ),
        array(
            'year' => $year,
            'month' => $month,
        ),
    ),
);

$customer_orders = get_posts( $args );
$count = 0;
$total = 0;
$no_orders_message = __('No orders found the date period', 'hello-world');
if (!empty($customer_orders)) {
    $customer_orders_date = array();
    
    foreach ( $customer_orders as $customer_order ){
        
        $customer_order_date = strtotime($customer_order->post_date);
       
        if ( $customer_order_date > $gap_time ) {
            $customer_order_date;
            $order = new WC_Order( $customer_order->ID );
            $order_items = $order->get_items();
            $total += $order->get_total();
            
            foreach ( $order_items as $order_item ){
               $count++;
            }
        }
    }
    return floatval( preg_replace( '#[^\d.]#', '', $total, $count ) );    
} else {
    return $no_orders_message;         
}
}
add_shortcode( 'user_amount_spent', 'display_total_amount_spent' );

With the above code, I'm getting the No Orders for Date Period message, whereas in reality, I have placed some test orders through which have been marked against the "completed" status, but the query isn't picking up those orders. Why this isn't working?

Any pointers or guidance would be so much appreciated.


Solution

  • From WooCommerce version 8.2, High-Performance Order Storage (HPOS) is enabled by default and uses custom database tables for better performances, so WordPress Post and Postmeta functions could not work anymore with WooCommerce Orders.

    You need to replace WordPress get_posts() with WooCommerce wc_get_orders(), to query orders.

    Now you need to display 2 date fields and use Javascript + Ajax to query orders from the date fields, to get the amount spent.

    Here is the complete code

    // Shortcode function (+Javascript with Ajax)
    add_shortcode( 'user_range_spent', 'shortcode_user_date_range_total_spent' );
    function shortcode_user_date_range_total_spent( $atts ) {
        extract( shortcode_atts( array(
            'user_id'   => get_current_user_id(),
        ), $atts, 'user_range_spent' ) );
    
        // Check that user ID is defined
        if ( ! $user_id ) {
            return; // Exit
        }
    
        // Enqueing Javascript (Jquery + Ajax)
        wc_enqueue_js("$('.date-range-fields button#submit_date').on('click', function(){
            const dateFrom = $('.date-range-fields #from_date').val();
            const dateTo = $('.date-range-fields #to_date').val();
            console.log(dateFrom+' ... '+dateTo);
            
            if( dateFrom && dateTo ) {
                $('.user-total-spent').block({message: null, overlayCSS:{background:\"#fff\",opacity: .6}});
                $.ajax({
                    type: 'POST',
                    url: '" . admin_url('/admin-ajax.php') . "',
                    data: {
                        'action': 'user_total_spent',
                        'user_id': {$user_id},
                        'date_from': dateFrom,
                        'date_to': dateTo
                    },
                    success: function (response) {
                        $('.user-total-spent').unblock();
                        $('.user-total-spent > .response-message').html(response);
                    }
                });
            } else {
                $('.user-total-spent > .response-message').html('" . esc_html__('Select a correct date range please.') . "');
            }
        });");
    
        // Get the start date from user metadata (defining the correct meta key)
        $date_from = get_user_meta($user_id, 'meta_key', true) ?: '';
        
        // Format the date as "YYYY-MM-DD" (optional, can be removed if not needed)
        $date_from = $date_from ? date('Y-m-d', strtotime( $date_from ) ) : '';
    
        // HTML (date range fields)
        return '<div class="user-total-spent">
            <div class="date-range-fields">
                <span id="from_date_field" style="max-width:200px;display:inline-block;">
                    <label for="from_date">' . esc_html__('From date', 'woocommerce') . ': </label> 
                    <input type="date" class="input-text " name="from_date" id="from_date" value="' . $date_from . '">
                </span>
                <span id="to_date_field" style="max-width:200px;display:inline-block;">
                    <label for="to_date">' . esc_html__('To date', 'woocommerce') . ': </label> 
                    <input type="date" class="input-text " name="to_date" id="to_date" value="">
                </span>
                <span id="submit_date_field" style="display:inline-block;">
                    <button type="button" class="submit_date" name="submit_date" id="submit_date">' . esc_html__('Submit', 'woocommerce') . '</button>
                </span>
            </div><br>
            <div class="response-message"></div>
        </div>';
    }
    
    
    // PHP AJAX Receiver: Process the ajax request
    add_action('wp_ajax_user_total_spent', 'get_user_total_spent_from_date_range');
    function get_user_total_spent_from_date_range() {
        if ( isset($_POST['user_id'], $_POST['date_from'], $_POST['date_to']) && $_POST['user_id'] > 0 ) {
            $from_date = $_POST['date_from']; 
            $to_date   = $_POST['date_to'];
    
            // The WC_Order_Query
            $customer_orders = wc_get_orders( array(
                'type'          => 'shop_order',
                'status'        => wc_get_is_paid_statuses(),
                'limit'         => -1,
                'customer'      => intval($_POST['user_id']),
                'date_created'  => "{$from_date}...{$to_date}", // Date range
            ) );
    
            $total_spent = 0; // Initialize
    
            if ( $customer_orders ) {
                // Loop through orders
                foreach ( $customer_orders as $order ) {
                    $total_spent += $order->get_total();
                }
            }
            $html = $total_spent > 0 ? 
                sprintf( esc_html__('Total spent: %s', 'woocommerce'), wc_price($total_spent) ) : 
                esc_html__('No orders were found for this date range', 'woocommerce');
        } else {
            $html = esc_html__('Try again please…', 'woocommerce'); // Error
        }
        wp_die($html); // Send back the HTML and end the request silently.
    }
    

    Code goes in functions.php file of your child theme (or in a plugin). Tested and works.

    SHORTCODE USAGE:

    This shortcode has one optional arguments: The user ID (the current user ID by default)

    You will get something like:

    enter image description here