What's the difference between price, sale_price and regular_price?

Viewed 2284

A few months ago I thought I had understood the product's meta _price, _regular_price and _sale_price.

I seem to remember that while doing some tests _price stored the "price the user sees", then if it matched with _sale_price it'd show the "SALE" tag or the percentage discount and use the _regular_price to show the old price and calculate the percentage.

But right now I'm doing some tests and it seems like _price isn't really used?

Am I misremembering or did it used to work as I said and got changed?


// Set Everything to 5$
$product->set_price(5);
$product->set_regular_price(5);
$product->set_sale_price(5);

$product->save();

All prices equal

// Set Price to 9$
$product->set_price(9);

// Set Sale Price to 2$
$product->set_sale_price(2);
$product->save();

All prices different

// Set Sale Price to ''
$product->set_sale_price('');
$product->save();

Empty Sale Price

// Set Sale Price back to 2
$product->set_sale_price(2);
// Set Price to ''
$product->set_price('');
$product->save();

Empty Price, Sale Price 2

What is the need for 2 meta that seem to contain the same? (_price,_regular_price)

What am I missing?

1 Answers

I checked the woocommerce source, and it took time ;-) It boils down how your theme handles the price - on an archive or as single product. Dig into your theme and you find out.

With my theme I use on https://golfball.pro the archive and the single product call the get_price_html() method of the WC_Product Class:

public function get_price_html( $deprecated = '' ) {
        if ( '' === $this->get_price() ) {
            $price = apply_filters( 'woocommerce_empty_price_html', '', $this );
        } elseif ( $this->is_on_sale() ) {
            $price = wc_format_sale_price( wc_get_price_to_display( $this, array( 'price' => $this->get_regular_price() ) ), wc_get_price_to_display( $this ) ) . $this->get_price_suffix();
        } else {
            $price = wc_price( wc_get_price_to_display( $this ) ) . $this->get_price_suffix();
        }

        return apply_filters( 'woocommerce_get_price_html', $price, $this );
}

What happens is the following - the code checks the prices, which are stored in the table wp_postmeta:

  1. _price gets retrieved from wp_postmeta
  2. if the value does not exist, no price is shown but a filter is called, where you could add your own price logic. If you dont do it the price remains empty.
  3. If the key _price exists for the product, it checks if the product is on sale, and if yes use two prices - the _regular_price and surprise _price (for the sale price)
  4. if the product is not on sale the _price field is used.

So... the logic does not happen here at retrieval of the values, it happens at save the updates in the WC_Product_Data_Store_CPT class, the handle_updated_props method starting from line 650:

if ( in_array( 'date_on_sale_from', $this->updated_props, true ) || in_array( 'date_on_sale_to', $this->updated_props, true ) || in_array( 'regular_price', $this->updated_props, true ) || in_array( 'sale_price', $this->updated_props, true ) || in_array( 'product_type', $this->updated_props, true ) ) {
    if ( $product->is_on_sale( 'edit' ) ) {
        update_post_meta( $product->get_id(), '_price', $product->get_sale_price( 'edit' ) );
        $product->set_price( $product->get_sale_price( 'edit' ) );
    } else {
        update_post_meta( $product->get_id(), '_price', $product->get_regular_price( 'edit' ) );
        $product->set_price( $product->get_regular_price( 'edit' ) );
    }
}

This is the code in WC_Product_Data_Store_CPT that handles the save and has the logic for _price, _regular_price and _sale_price; this snippet gets executed before a save. So if the _regular_price is updated or the _sale_price is updated, this will trigger an update:

  1. If there is an update in SaleTo or SaleFrom Date, regular or sale price or pruduct_type
  2. Update the _price

Long story short, _price is always updated. In your case I would check where your theme is getting the price from, maybe not from get_price_html and therefore the behavior is different.

Related