phpwordpresswoocommerceproduct

Allow customer to define the price on specific WooCommerce product


I would like to have a specific WooCommerce product where customers can put in their own price/value for a giftcard. I have found a very nice script that almost does the trick if you create a simple product with price value 0, but there are three issues I can't fix:

  1. With this script the customer is able to order the product on the category page with price 0.
  2. The customer is able to also put in text values instead of only numeric values in the price field.
  3. The price 0 is visible on the product page and it would be beter to hide it. I use css code to hide it, but that isn't a very neat way I think.

.single-product.postid-241982 div.product p.price {display: none;}

add_action( 'woocommerce_before_add_to_cart_button', 'bbloomer_product_price_input', 9 );
  
function bbloomer_product_price_input() {
   global $product;
   if ( 241982 !== $product->get_id() ) return;
   woocommerce_form_field( 'set_price', array(
      'type' => 'text',
      'required' => true,
      'label' => 'Set price ' . get_woocommerce_currency_symbol(),
   ));
}
  
add_filter( 'woocommerce_add_to_cart_validation', 'bbloomer_product_add_on_validation', 9999, 3 );
  
function bbloomer_product_add_on_validation( $passed, $product_id, $qty ) {
   if ( isset( $_POST['set_price'] ) && sanitize_text_field( $_POST['set_price'] ) == '' ) {
      wc_add_notice( 'Set price is a required field', 'error' );
      $passed = false;
   }
   return $passed;
}
  
add_filter( 'woocommerce_add_cart_item_data', 'bbloomer_product_add_on_cart_item_data', 9999, 2 );
  
function bbloomer_product_add_on_cart_item_data( $cart_item, $product_id ) {
   if ( 241982 !== $product_id ) return $cart_item;    
   $cart_item['set_price'] = sanitize_text_field( $_POST['set_price'] );
   return $cart_item;
}
 
add_action( 'woocommerce_before_calculate_totals', 'bbloomer_alter_price_cart', 9999 );
  
function bbloomer_alter_price_cart( $cart ) {
   if ( is_admin() && ! defined( 'DOING_AJAX' ) ) return;
   if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 ) return;
   foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
      $product = $cart_item['data'];
      if ( 241982 !== $product->get_id() ) continue;
      $cart_item['data']->set_price( $cart_item['set_price'] );
   } 
}

Solution

  • There are some mistakes and missing things in your code. Try the following instead:

    // Remove specific product displayed price on archive pages
    add_action( 'woocommerce_after_shop_loop_item_title', 'remove_loop_specific_product_displayed_price', 1 );
    function remove_loop_specific_product_displayed_price(){
        global $product;
    
        // Only for specific product ID
        if ( 241982 == $product->get_id() ) {
            // Remove price
            remove_action('woocommerce_after_shop_loop_item_title', 'woocommerce_template_loop_price', 10 );
        }
    }
    
    // Remove specific product displayed price in single product
    add_action( 'woocommerce_single_product_summary', 'remove_specific_product_displayed_price', 1 );
    function remove_specific_product_displayed_price() {
        global $product;
    
        // Only for specific product ID
        if ( 241982 == $product->get_id() ) {
            // Remove price
            remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_price', 10 );
        }
    }
    
    // Replace loop add to cart button with a link to the product page
    add_filter( 'woocommerce_loop_add_to_cart_link', 'replace_specific_product_add_to_cart_link', 100, 3 );
    function replace_specific_product_add_to_cart_link( $button, $product, $args ) {
        // Only for specific product ID
        if ( 241982 == $product->get_id() ) {
            $button = sprintf( '<a href="%s" class="%s" %s>%s</a>',
                esc_url( $product->get_permalink() ),
                esc_attr( 'button' ),
                isset( $args['attributes'] ) ? wc_implode_html_attributes( $args['attributes'] ) : '',
                esc_html__( 'Set a price', 'woocommerce' )
            );
        }
        return $button;
    }
    
    // Display price input field on specific single product
    add_action( 'woocommerce_before_add_to_cart_button', 'display_product_price_input_field', 9 );
    function display_product_price_input_field() {
        global $product;
    
        // Only for specific product ID
        if ( 241982 == $product->get_id() ) {
            woocommerce_form_field( 'custom_price', array(
                'type'      => 'text',
                'label'     => sprintf(__('Set price (%s)', 'woocommerce'), get_woocommerce_currency_symbol()),
                'required'  => true,
            ));
        }
    }
       
    // Add to cart field validation (Accept only a numerical value greater than zero)
    add_filter( 'woocommerce_add_to_cart_validation', 'sov_product_add_on_validation' );
    function sov_product_add_on_validation( $passed ) {
        if ( isset($_POST['custom_price']) ) {
            // Replace "," with "." for float numbers
            $custom_price = str_replace(',', '.', esc_attr($_POST['custom_price'])); 
            
            if ( empty($custom_price) || ! is_numeric($custom_price) || ! ($custom_price > 0) ) {
                wc_add_notice( 'Set price  is a required field (only accepts a numerical value greater than zero)', 'error' );
                $passed = false;
            }
        }
        return $passed;
    }
    
    // Add custom cart item data
    add_filter( 'woocommerce_add_cart_item_data', 'filter_add_cart_item_data', 10, 2 );
    function filter_add_cart_item_data( $cart_item_data, $product_id ) { 
        if ( isset($_POST['custom_price']) ) {
            // Replace "," with "." for float numbers
            $custom_price = str_replace(',', '.', esc_attr($_POST['custom_price']));
    
            $cart_item_data['custom_price'] = floatval($custom_price);
            $cart_item_data['unique_key']   = md5(microtime().rand());
        }
        return $cart_item_data;
    }
    
    // Display the correct cart item custom price html
    add_filter( 'woocommerce_cart_item_price', 'filter_cart_displayed_price', 10, 2 );
    function filter_cart_displayed_price($price, $cart_item) {
        if ( isset($cart_item['custom_price']) ) {
            $args = array('price' => floatval($cart_item['custom_price']));
    
            if ('incl' === get_option('woocommerce_tax_display_cart')) {
                $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;
    }
    
    // Set cart item custom price
    add_action( 'woocommerce_before_calculate_totals', 'set_price_before_calculate_totals' );   
    function set_price_before_calculate_totals( $cart ) {
        if ( is_admin() && ! defined( 'DOING_AJAX' ) ) 
            return;
    
        foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
            if ( isset($cart_item['custom_price']) ) {
                $cart_item['data']->set_price( $cart_item['custom_price'] );
            } 
        }
    }
    

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

    Note: Some themes make their own customizations, so something different could be needed to remove the displayed price in WooCommerce archives and single product pages.