Search code examples
phpwordpresswoocommerceproduct-variations

Add and display unit price field to WooCommerce product variations


In WooCommerce, for variable products, I want to add a field that shows the price of one unit of the product to the user. For example, I have a product that is priced at $1000 for a pack of 100, but I want to add a field to show the user the price per piece (one unit). Therefore, the price entered for the unit price field should be shown to the user instead of the variation price, but in my code it does not show the price on the product page.

Here is my code:

// Add custom field to product variations
function add_price_per_unit_field_variation($loop, $variation_data, $variation) {
    woocommerce_wp_text_input(array(
        'id'          => '_price_per_unit[' . $variation->ID . ']',
        'label'       => __('Price per unit for Variation ' . $loop, 'woocommerce'),
        'placeholder' => '',
        'desc_tip'    => 'true',
        'description' => __('Enter the price per unit for this variation.', 'woocommerce'),
        'value'       => get_post_meta($variation->ID, '_price_per_unit', true),
    ));
}
add_action('woocommerce_variation_options_pricing', 'add_price_per_unit_field_variation', 10, 3);

// Save custom field for product variations
function save_price_per_unit_field_variation($variation_id, $i) {
    $price_per_unit = $_POST['_price_per_unit'][$variation_id];
    update_post_meta($variation_id, '_price_per_unit', esc_attr($price_per_unit));
}
add_action('woocommerce_save_product_variation', 'save_price_per_unit_field_variation', 10, 2);

// Replace variation price with price per unit on product page
function display_price_per_unit_single($price, $product) {
    if ($product->is_type('variable')) {
        $price_per_unit = get_post_meta($product->get_id(), '_price_per_unit', true);

        if (!empty($price_per_unit)) {
            return wc_price($price_per_unit) . ' per unit';
        }
    }
    return $price;
}
add_filter('woocommerce_variable_sale_price_html', 'display_price_per_unit_single', 10, 2);
add_filter('woocommerce_variable_price_html', 'display_price_per_unit_single', 10, 2);

Solution

  • I have completely revised your code using more recent code and hooks, replacing the displayed price on the selected variation, by the price per unit when set:

    // Display custom field on product variations options pricing
    add_action('woocommerce_variation_options_pricing', 'display_variation_price_per_unit_field', 10, 3);
    function display_variation_price_per_unit_field($loop, $variation_data, $variation) {
        $variation_object = wc_get_product($variation->ID);
    
        woocommerce_wp_text_input( array(
            'id'            => "_price_per_unit[{$loop}]",
            'label'         => sprintf( __('Price per unit (%s)', 'woocommerce'), get_woocommerce_currency_symbol() ),
            'wrapper_class' => 'form-row form-row-first',
            'placeholder'   => '',
            'desc_tip'      => 'true',
            'description'   => __('Enter the price per unit for this variation.', 'woocommerce'),
            'value'         => wc_format_localized_price( $variation_object->get_meta( '_price_per_unit' ) ),
        ) );
    }
    
    // Save custom field value from product variations options pricing
    add_action( 'woocommerce_admin_process_variation_object', 'save_variation_price_per_unit_value', 20, 2 );
    function save_variation_price_per_unit_value( $variation, $i ) {
        $price_per_unit = isset($_POST['_price_per_unit'][$i]) ? wc_clean( wp_unslash($_POST['_price_per_unit'][$i])) : null;
        
        $variation->update_meta_data('_price_per_unit', $price_per_unit);
    }
    
    // Display the formatted price per unit on selected variation
    add_filter( 'woocommerce_available_variation', 'display_selected_variation_price_per_unit', 10, 3 );
    function display_selected_variation_price_per_unit( $variation_data, $product, $variation ) {
        $price_per_unit = $variation->get_meta('_price_per_unit');
        
        if ( $price_per_unit != null ) {
            $price_to_display = wc_get_price_to_display( $variation, array( 'price' => floatval($price_per_unit) ) );
            $variation_data['price_html'] = wc_price( $price_to_display );
        }
        return $variation_data;
    }
    

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


    Addition:

    The selected variation price per unit replace the product variation price range.

    You will replace the last function from my previous code with:

    // Add the variation formatted price per unit to the form data
    add_filter( 'woocommerce_available_variation', 'available_variation_formatted_price_per_unit', 10, 3 );
    function available_variation_formatted_price_per_unit( $variation_data, $product, $variation ) {
        $price_per_unit = $variation->get_meta('_price_per_unit');
        
        if ( $price_per_unit != null ) {
            $variation_data['display_price_pu'] = wc_get_price_to_display( $variation, array( 'price' => floatval($price_per_unit) ) );
            $variation_data['price_pu_html']    = wc_price( $variation_data['display_price_pu'] );
        }
        return $variation_data;
    }
    

    Then you will also add the following code:

    add_action( 'woocommerce_after_variations_form', 'woocommerce_after_variations_form_js', 10 );
    function woocommerce_after_variations_form_js() {
        global $product; ?>
        <script>
        jQuery( function($){
            const originalPrice = $('p.price').html()
            $('form.cart').on('show_variation', function(event, data) {
                if ( data.price_pu_html !== undefined ) {
                    $('p.price').html( data.price_pu_html );
                } else {
                    $('p.price').html( originalPrice );
                }
            }).on('hide_variation', function(){
                $('p.price').html( originalPrice );
            });
        });
        </script>
        <?php
    }
    

    Tested and works.

    enter image description here