1

I have 500+ simple products in my woocommerce store. I am storing the cost of a "color" option as a custom field in each product (avp-ral-paint-price), and have a color attribute with the 30+ color options. I don't want to create variable products. All colors are the same cost for a specific product.

The following code (hooks and functions) creates a pulldown menu with the color options, gives an error if no option chosen, and puts the color as a line item in the cart, associated with the product added when hitting the add to cart function. It also shows above the add to cart button the cost of the color options.

What I can't figure out is how to give the line item with the color option a price that adds into the cart, changing the cart total. I would prefer to have the base price of the product displayed in the cart followed by the color price, but I could live with the price added to the product price first. I haven't found how to do either.

It seems I just need the right array key for cart_item_data, but nothing I've tried has worked.

// Display Select field before add to cart button
add_action( 'woocommerce_before_add_to_cart_button', 'colors_before_add_to_cart_button', 0 );
function colors_before_add_to_cart_button() {
    global $product;

    // Only on simple products
    if( ! $product->is_type('simple') ) return;

    // When product colors are available
    if ( $colors = $product->get_attribute( 'color' ) ) :
    $colors = explode(", ",$colors);

    $required = '&nbsp;<abbr class="required" title="required">*</abbr></label>';
    echo 'Standard Powder Coat: $'. get_post_meta(get_the_ID(), 'avp-ral-paint-price', true) .'.00<br>';
    echo '<p class="form-row form-row-wide" id="product-color-field">
    <label for="product-color">' . __('Standard Colors:') . $required . '</label>
    <select class="product-color" name="product-color" id="product-color">
        <option value="">' . __("Choose your color") . '</option>';

    foreach( $colors as $color ){
        echo '<option value="' . $color . '">' . $color . '</option>';
    }

    echo '</select>
    </p><br>';


    endif;
}

add_filter( 'woocommerce_add_to_cart_validation', 'filter_add_to_cart_validation', 10, 4 );
function filter_add_to_cart_validation( $passed, $product_id, $quantity, $variation_id = 0 ) {
    $product = wc_get_product($product_id);

    // Only on simple products and When product colors are available
    if( ! $product->is_type('simple') && ! $product->get_attribute( 'color' ) )
        return $passed;

    if( isset( $_POST['product-color'] ) && empty( $_POST['product-color'] ) ) {
        wc_add_notice( __("Choose A Color (Mill for No Paint)", "woocommerce" ), 'error' );
        $passed = false;
    }
    return $passed;
}


add_filter( 'woocommerce_add_cart_item_data', 'add_color_to_cart_item_data', 10, 3 );
function add_color_to_cart_item_data($cart_item_data, $product_id, $variation_id )
{
    global $product;
    if( isset( $_POST['product-color'] ) )
    {   
        $cart_item_data['product-color'] = esc_attr( $_POST['product-color'] );
    }
    return $cart_item_data;
}

add_filter( 'woocommerce_get_item_data', 'display_color_on_cart_item', 10, 2 );
function display_color_on_cart_item( $cart_item_data, $cart_item ) {
    if ( isset( $cart_item['product-color'] ) ){
        $cart_item_data[] = array(
            'name' => __('Color'),
            'value' => $cart_item['product-color'],
        );
    }
    return $cart_item_data;
}

  • Each product has a single price for any colors, which I'm storing in a custom field, avp-ral-paint-price. Most product option plugins (including "Extra Product Options" by Theme Complete, which I've been using) assume that product options will have a set price (or percentage) across all products. My products are aluminum roof vents & fans, and the color option is powder-coating. The price of the same set of colors is different because it depends on the size of the vent. Technically there are "premium" colors that cost twice the normal color, but I just need the one number for each product. – Edward S Bernstein May 10 at 18:05
  • I am trying to recreate the site without the plugins, so there are no plugins involved with this question. Other than the code shown, it is all vanilla woocommerce. The single product custom field in question is "avp-ral-paint-price", which is the integer dollar amount that adding paint of any color would cost. In the function "colors_before_add_to_cart_button()" I access it in the line: echo 'Standard Powder Coat: $'. get_post_meta(get_the_ID(), 'avp-ral-paint-price', true) .'.00<br>'; So somewhere I need to add this value as the cost of this line item into the cart. – Edward S Bernstein May 10 at 19:31
  • So you are storing the color additional cost in a custom field… no more complicated that that. So you want to have this price to be added to the cart item subtotal and reflected in the totals… – LoicTheAztec May 10 at 19:40
  • Yes! That's it. And I get the color added as a line item to the cart, but I haven't been able to find the right key to add that cost to the array I'm sending to the cart. – Edward S Bernstein May 10 at 19:45
  • @fuxia Please, why always removing the php tag when I add it as a related tag (as it is involved in this question/answer for real)? Some explanation please will be nice (thank you). – LoicTheAztec May 10 at 22:50
1

The following revisited code will change cart item subtotal to the calculated sum of product + color option and will reflect this change to cart totals. It will save the option as order item meta data, when order is placed.

The code:

// Display Select field before add to cart button
add_action( 'woocommerce_before_add_to_cart_button', 'colors_options_before_add_to_cart_button' );
function colors_options_before_add_to_cart_button() {
    global $product;

    // Only on simple products
    if( ! $product->is_type('simple') ) return;

    $colors = $product->get_attribute( 'color' );

    // When product colors are available
    if ( ! empty($colors) && $color_price = $product->get_meta('avp-ral-paint-price') ) :

    $options  = array( '' => __("Choose your color") );

    foreach ( (array) explode( ',', $colors ) as $color ) {
        $options[$color] = $color;
    }

    echo '<p><strong>' . __("Standard Powder Coat") . ':</strong> ' . wc_price( $color_price ) .'</p>';

    woocommerce_form_field( 'color', array(
        'type'     => 'select',
        'class'    => array('form-row-wide product-color'),
        'label'    => __('Standard Colors'),
        'options'  => $options,
        'required' => true,
    ), '' );

    echo '<br>';
    endif;
}

// Color option validation
add_filter( 'woocommerce_add_to_cart_validation', 'filter_add_to_cart_validation', 10, 2 );
function filter_add_to_cart_validation( $passed, $product_id ) {
    $product = wc_get_product($product_id);

    if( $product->is_type('simple') && $product->get_attribute('color') &&
    $product->get_meta('avp-ral-paint-price') && isset($_POST['pa-color']) && empty($_POST['pa-color']) ) {
        wc_add_notice( __("Choose a Color (Mill for No Paint)", "woocommerce" ), 'error' );
        $passed = false;
    }
    return $passed;
}

// Add color option as custom cart item data (+ prices)
add_filter( 'woocommerce_add_cart_item_data', 'add_color_option_as_cart_item_data', 10, 2 );
function add_color_option_as_cart_item_data( $cart_item_data, $product_id ) {
    if( isset($_POST['color']) && ! empty($_POST['color']) ) {
        $product = wc_get_product($product_id);

        if( $color_price = $product->get_meta('avp-ral-paint-price') ) {
            $cart_item_data['color']         = esc_attr($_POST['color']);
            $cart_item_data['color-price']   = $color_price;
            $cart_item_data['default-price'] = wc_get_price_to_display($product);
            $cart_item_data['new-price']     = $product->get_price() + $color_price;
        }
    }
    return $cart_item_data;
}

// Display select color option on cart item
add_filter( 'woocommerce_get_item_data', 'display_color_option_on_cart_item', 10, 2 );
function display_color_option_on_cart_item( $cart_item_data, $cart_item ) {
    if ( isset($cart_item['color']) && isset($cart_item['color-price']) ){
        $cart_item_data[] = array(
            'name' => __('Color'),
            'value' => $cart_item['color'] . ' (' . wc_price($cart_item['color-price']) . ')',
        );
    }
    return $cart_item_data;
}

// Display the default product price (instead of the calculated one)
add_filter( 'woocommerce_cart_item_price', 'filter_cart_item_price', 10, 2 );
function filter_cart_item_price( $price, $cart_item ) {
    if ( isset($cart_item['color']) && isset($cart_item['default-price']) ){
        $price = wc_price($cart_item['default-price']);
    }
    return $price;
}

// Customizing cart item price subtotal
add_action( 'woocommerce_before_calculate_totals', 'set_cart_item_calculated_price', 10, 1 );
function set_cart_item_calculated_price( $cart ) {
    if ( is_admin() && ! defined( 'DOING_AJAX' ) )
        return;

    // Required since Woocommerce version 3.2 for cart items properties changes
    if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
        return;

    // Loop through cart items
    foreach ( $cart->get_cart() as $cart_item ) {
        // Set the new calculated price
        if( isset($cart_item['color']) && isset($cart_item['new-price']) ) {
            $cart_item['data']->set_price( $cart_item['new-price'] );
        }
    }
}

// Save save color option as order item meta data (and display it everywhere)
add_filter( 'woocommerce_checkout_create_order_line_item', 'save_color_option_as_order_item_meta_data', 10, 4 );
function save_color_option_as_order_item_meta_data( $item, $cart_item_key, $cart_item, $order ) {
    if( isset($cart_item['color']) && isset($cart_item['color-price']) ) {
        $item->update_meta_data( 'pa_color', $cart_item['color'] . ' (' . wc_price($cart_item['color-price']) . ')' );
    }
    return $item;
}

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


On single Product page:

enter image description here


On cart page (and checkout):

enter image description here


On Orders pages (and email notifications):

enter image description here

  • Thank you so much! I left Friday before I got a chance to try this out, so I'm getting to it now. Very Grateful! – Edward S Bernstein May 13 at 13:18
  • Yes, accepted answer. Thank you again! – Edward S Bernstein May 13 at 20:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.