Search code examples
phpwoocommercecartcheckout

Custom dynamic item pricing gets reset on Woocommerce Checkout


I am having a problem getting my price updates to stick. More or less this is a sample of what I am using to update the prices:

    add_action( 'woocommerce_before_calculate_totals', 'cst_product_quantity_discounter', 10 );
function cst_product_quantity_discounter( $cart_object ) {

    if ( is_admin() && ! defined( 'DOING_AJAX' ) ){
        return;
    }

    if( ! isset( WC()->session->reload_checkout ) ) {

        // declare woocommerce global
        global $woocommerce;      

        // array of ID's to match that represent each item

        $special_ids_array = [
            0 => array( "id" => 15372, "product" => "TAF", "single_bag_price" => 42.99, "3_multiplier" => 0.82181902768 ),
            1 => array( "id" => 14285, "product" => "POW", "single_bag_price" => 29.99, "3_multiplier" => 0.8890741358 )
        ];

        // gather cart object
        $wc_cart = WC()->cart->get_cart();

        foreach( $wc_cart as $cart_item ){

            // gather relevant data for testing and comparisons
            $product_id = $cart_item['product_id'];
            $product_key = $cart_item['key'];
            $pack_size_attribute = $cart_item['variation']['attribute_pa_sample-check']; // returns: "full-pack" || "sample" || null
            $item_quantity = $cart_item['quantity'];

            foreach ( $special_ids_array as $id_test_2 ) {
                if ( $product_id === $id_test_2["id"] && $pack_size_attribute !== "sample" ){
                    foreach ( $cart_object->cart_contents as $key => $value ) {
                        // if the key matches we will set the price of the correct product
                        if ( $key === $product_key ){
                            $discounted_price = 10;
                            $value['data']->set_price($discounted_price);
                        }
                    }
                }
            } 
        }
    }
}

There is some other stuff to get the values seen but that works. this will update on my test cart page and everything looks fine.

My issue is when I go to my test checkout page the prices update briefly, then they are overwritten by the original price. I am missing some update thats being run on checkout and woocommerce_before_calculate_totals hook does not seem to be making a permanent change on the checkout page.

Where do I need to hook my function so the changes persist whatever loads are occurring on checkout that overwrite the initial successful price change?


Solution

  • There is some strange things in your code:

    • The $cart_object (that is the WC_Cart object) is already an argument in the hooked function (it replaces WC()->cart or outdated $woocommerce->cart )
    • So WC()->cart->get_cart() and $cart_object->cart_contents are exactly the same thing. The best is using $cart_object->get_cart() in your foreach loops.
    • You don't need global $woocommerce; as it's already included in WC() (the global Woocommerce object so already in $cart_object)
    • reload_checkout doesn't exist in WC_Sessions or WC_Session_Handler classes (so your condition is always true) and anyway this is not useful for this hook.
    • For info $cart_item['data'] is an instance of the WC_Product object for this cart item.
    • You are have 2 cart loops one in the other and you can make everything in one (it will avoid problems).
    • $cart_item['key'] and the $key (in the cart loop) are always the same thing. You will not really need to use it in this case…
    • You have removed completely the prices calculations in this new edit of your question and the pricing is not dynamic anymore (based on the product quantity like before).

    So I have tried to add back a kind of dynamic price calculation in more real conditions, but in a different way based on your $special_ids_array array keys/values.

    The calculated dynamic price discount start with a quantity of 3 by item that match the conditions (Product Ids et "pack size" attribute).

    So All this simplifies and compact a lot this revisited code:

    add_action( 'woocommerce_before_calculate_totals', 'custom_product_quantity_discounter', 10, 1 );
    function custom_product_quantity_discounter( $cart ) {
        if ( is_admin() && ! defined( 'DOING_AJAX' ) )
            return;
    
        if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
            return;
    
        // array of ID's to match that represent each item
        $special_ids = array(
            0 => array( "id" => 15372, "product" => "TAF", "single_bag_price" => 42.99, "3_multiplier" => 0.82181902768 ),
            1 => array( "id" => 14285, "product" => "POW", "single_bag_price" => 29.99, "3_multiplier" => 0.8890741358 ),
        );
    
        // Loop through the cart items
        foreach( $cart->get_cart() as $cart_item ){
            $product_id  = $cart_item['product_id'];
            $quantity    = $cart_item['quantity'];
            $pack_size   = $cart_item['variation']['attribute_pa_sample-check']; // returns "full-pack", "sample" or null
            foreach ( $special_ids as $special_id ){
                if( $special_id['id'] == $product_id && $pack_size !== 'sample' && $quantity >= 3 ){
                    $discounted_price = $special_id['single_bag_price'] * $special_id['3_multiplier']; // calculation (fake and custom)
                    $cart_item['data']->set_price($discounted_price); // set the calculated price
                }
            }
        }
    }
    

    Code goes in function.php file of the active child theme (or active theme).

    Tested and works.

    The dynamic calculated pricing based on cart item quantities and custom calculation persist in checkout even when reloading the page multiple times.