phpwordpresswoocommercecartinput-field

Update WooCommerce product price based on a custom input field


I added a custom number input field to my single product pages. The regular Price gets multiplied by an inputted integer value. For example, if the Base Price is 10$ and the user puts 2 as value in the custom input field, the new Price become 20$.

Updating the Price is working fine, but when the user gets to the Cart and updates the quantity of the product within the cart, the price gets recalculated. e.g. The new calculated Price was 20$ (example above) and the user changes the quantity from 1 to 5 in the cart, the price gets recalculated by the custom integer field again, so the 20$ gets multiplied by 2 again and is 40$ now.

But I don't get why this is happening...

This is my current code:

add_action( 'woocommerce_after_add_to_cart_quantity', 'add_custom_input_number_field', 0 );
function add_custom_input_number_field() {
    echo '<div class="custom-input-number-field">';
    echo '<label>' . __( 'Custom Input Number', 'woocommerce' ) . '</label>';
    echo '<input type="number" required="true" placeholder="max. 30.500" class="custom-input-number" name="custom_input_number" min="1" step="0.001" />';
    echo '</div>';
}

add_filter( 'woocommerce_get_price_html', 'update_product_price_based_on_custom_input_on_product_page', 9999, 2 );
function update_product_price_based_on_custom_input_on_product_page( $price, $product ) {
    if ( is_product() && isset( $_POST['custom_input_number'] ) ) {
        $custom_input_value = (float) $_POST['custom_input_number'];
        $product_price = $product->get_price();
        $new_price = $product_price * $custom_input_value;
        $price = wc_price( $new_price ) . $product->get_price_suffix();
    }
    return $price;
}

add_filter( 'woocommerce_add_cart_item_data', 'save_custom_input_number_field_data', 10, 2 );
function save_custom_input_number_field_data( $cart_item_data, $product_id ) {
    if ( isset( $_POST['custom_input_number'] ) ) {
        $cart_item_data['custom_input_number'] = wc_clean( $_POST['custom_input_number'] );
        $cart_item_data['custom_input_number'] = (float) $cart_item_data['custom_input_number'];
    }
    return $cart_item_data;
}

add_action( 'woocommerce_before_calculate_totals', 'update_product_price_based_on_custom_input_in_cart', 10, 1 );
function update_product_price_based_on_custom_input_in_cart( $cart ) {
    if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
        return;
    }
 
    foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
        if ( isset( $cart_item['custom_input_number'] ) ) {
            $custom_input_value = $cart_item['custom_input_number'];
            $product = $cart_item['data'];
            $product_price = $product->get_price();
            $new_price = $product_price * $custom_input_value;
            $product->set_price( $new_price );
        }
    }
}

I tried different approaches. The best one was just block Woo from updating the Price again after it was added once, but i also want to work with a Tiered pricing for some products so this isn´t an option.

I also tried different hooks (add_to_cart, woocommerce_before_calculate_totals etc.)


Solution

  • There are some mistakes in your code and some wrong hook usage.

    Try the following instead, that will solve the issue you are facing:

    // Additional custom input field in single product
    add_action( 'woocommerce_after_add_to_cart_quantity', 'add_custom_input_number_field', 0 );
    function add_custom_input_number_field() {
        echo '<div class="custom-input-number-field">
        <label>' . __( 'Custom Input Number', 'woocommerce' ) . '</label>
        <input type="number" required="true" placeholder="max. 30.500" class="custom-input-number" name="custom_number" min="1" step="0.001" />
        </div>';
    }
    
    // Save custom input field value as custom cart item data
    add_filter( 'woocommerce_add_cart_item_data', 'save_custom_input_number_field_data', 10, 2 );
    function save_custom_input_number_field_data( $item_data, $product_id ) {
        if (isset( $_POST['custom_number']) ) {
            $item_data['custom_number'] = floatval($_POST['custom_number']);
        }
        return $item_data;
    }
    
    // Change and set cart item custom calculated price
    add_action( 'woocommerce_before_calculate_totals', 'update_product_price_based_on_custom_input_in_cart', 10, 1 );
    function update_product_price_based_on_custom_input_in_cart( $cart ) {
        if ( is_admin() && ! defined( 'DOING_AJAX' ) )
            return;
        
        foreach ( $cart->get_cart() as $item ) {
            if ( isset($item['custom_number']) && $item['custom_number'] ) {
                $item['data']->set_price( $item['data']->get_price() * $item['custom_number'] );
            }
        }
    }
    
    // Cart and mini cart displayed calculated price
    add_filter( 'woocommerce_cart_item_price', 'filter_cart_item_price', 10, 2 );
    function filter_cart_item_price( $price_html, $cart_item ) {
    
        if( isset($cart_item['custom_number']) && $cart_item['custom_number'] ) {
            $product = wc_get_product( $cart_item['variation_id'] > 0 ? $cart_item['variation_id'] : $cart_item['product_id'] );
            $args    = array('price' => ($product->get_price() * $cart_item['custom_number']));
    
            if ( WC()->cart->display_prices_including_tax() ) {
                $product_price = wc_get_price_including_tax( $cart_item['data'], $args );
            } else {
                $product_price = wc_get_price_excluding_tax( $cart_item['data'], $args );
            }
            return wc_price( $product_price );
        }
        return $price_html;
    }
    

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