In my WooCommerce store, I have 15 products that should have over 2K variations each (WooCommerce allowing to create 50 variation max), so I used a product extra options plugin and created some inputs to retrieve the customers inputs.
The price will be based on Rate ranges, so for example:
Here is the product input fields HTML:
<table class="thwepo-extra-options thwepo_simple" cellspacing="0">
<tbody>
<tr class="">
<td class="label leftside">
<label class="label-tag">Door height (mm)</label>
<abbr class="required" title="Required">*</abbr>
</td>
<td class="value leftside">
<input type="text" id="height884" name="height" placeholder="Height" value="" class="thwepof-input-field validate-required">
</td>
</tr>
<tr class="">
<td class="label leftside">
<label class="label-tag">Door width (mm)</label>
<abbr class="required" title="Required">*</abbr>
</td>
<td class="value leftside">
<input type="text" id="width784" name="width" placeholder="Width" value="" class="thwepof-input-field validate-required" maxlength="1800">
</td>
</tr>
<tr class="">
<td class="label leftside">
<label class="label-tag">Thickness (mm)</label>
<abbr class="required" title="Required">*</abbr>
</td>
<td class="value leftside">
<input type="text" id="thickness334" name="thickness" placeholder="Thickness" value="" class="thwepof-input-field validate-required">
</td>
</tr>
<tr class="">
<td class="label leftside">
<label class="label-tag">Material Type</label>
<abbr class="required" title="Required">*</abbr>
</td>
<td class="value leftside">
<select id="type_de_bois772" name="type_de_bois" placeholder="Agglo" value="Agglo" class="thwepof-input-field validate-required">
<option value="Agglo">Chipboard</option>
<option value="MDF">Medium (MDF)</option>
</select>
</td>
</tr>
</tbody>
</table>
And here is My PHP code:
add_action('woocommerce_before_calculate_totals', 'update_product_price_based_on_weight');
function update_product_price_based_on_weight($cart) {
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
// Get product object
$product = $cart_item['data'];
// Check if product is the one we want to modify
if ($product->get_name() === 'SERVO-DRIVE for AVENTOS HF') {
// Calculate weight based on inputs
$height = $_POST['height']; // Assuming the form submits this data
$width = $_POST['width'];
$thickness = $_POST['thickness'];
$material_type = $_POST['type_de_bois'];
// Calculate density based on material type
$density = ($material_type === 'MDF') ? 760 : 680;
// Calculate weight
$weight = ($height * $width * $thickness * $density) / 1000000000;
$force = $weight * $height;
// Adjust price based on weight
if ($force >= 2600 && $force <= 5349) {
$product->set_price(1000); // Adjust price for CASE 1
} elseif ($force >= 5350 && $force <= 8999) {
$product->set_price(2000); // Adjust price for CASE 2
} elseif ($force >= 9000 && $force <= 17250) {
$product->set_price(3000); // Adjust price for CASE 3
}
}
}
But I can't get it working, setting the correct calculated price. What I am doing wrong?
In the following code, based on your last comments, I make the price calculation dynamically, directly on the product page using JavaScript (based on your calculation algorithm). You may have to fine tune your calculation algorithm, as the current calculation doesn't seem accurate.
Note: This doesn't work with Cart and Checkout blocks, It works only with classic WooCommerce cart and checkout (based on templates).
First, in Admin edit product pages, we add a checkbox to enable the dynamic price calculation for a product, and a text field for the rate ranges pricing:
/
, like 2000/3000
).// Admin product edit page (Check box to enable dynamic price)
add_action( 'woocommerce_product_options_pricing', 'enabling_product_dynamic_price_calculation' );
function enabling_product_dynamic_price_calculation() {
global $post, $product_object;
woocommerce_wp_checkbox( array(
'id' => 'dynamic_price',
'label' => __( 'Dynamic price', 'woocommerce' ),
'value' => $product_object->get_meta('dynamic_price') ? 'yes' : 'no',
'description' => __( 'Enable dynamic calculated price based on user input fields.', 'woocommerce' ),
) );
woocommerce_wp_text_input( array(
'id' => 'range_pricing',
'placeholder' => __( 'Example: 2000/3000', 'woocommerce'),
'label' => __( 'Range pricing', 'woocommerce'),
'description' => __( 'Define the 2nd and the 3rd prices for rates ranges separated by "/".', 'woocommerce' ),
'desc_tip' => 'true'
) );
}
// Admin product: Save dynamic price setting option
add_action( 'woocommerce_admin_process_product_object', 'save_product_dynamic_price_setting_option' );
function save_product_dynamic_price_setting_option( $product ) {
$product->update_meta_data( 'dynamic_price', isset($_POST['dynamic_price']) && $_POST['dynamic_price'] === 'yes' ? '1' : '' );
if ( isset($_POST['range_pricing'])) {
$product->update_meta_data( 'range_pricing', sanitize_text_field($_POST['range_pricing']) );
}
}
You will get:
You will need to set in the regular price, the starting (lowest) price amount for this product.
Then on shop and archive pages, for this product, we change add to cart button with a linked button to the product page. Also we change the price display adding "Starting from" prefix to the regular_price:
// Replace loop add to cart button link with a link to the product
add_filter( 'woocommerce_loop_add_to_cart_link', 'replace_product_loop_add_to_cart_link', 100, 3 );
function replace_product_loop_add_to_cart_link( $button, $product, $args ) {
// Only for external products
if ( $product->get_meta('dynamic_price') ) {
$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__( 'Select options', 'woocommerce' )
);
}
return $button;
}
// Prefixing product price
add_filter( 'woocommerce_get_price_html', 'filter_get_price_html_callback', 10, 2 );
function filter_get_price_html_callback( $price_html, $product ){
if( $product->get_meta('dynamic_price') ) {
$price_html = sprintf('Starting from %s', $price_html);
}
return $price_html;
}
You will get:
When all input fields are populated, the price is calculated and displayed (replacing "starting from" default starting price).
// Utility function (your custom input fields settings)
function get_custom_fields_data( $options = false ) {
if ( $options ) {
return array(
'' => __('Select an option'),
'680' => __('Chipboard'),
'760' => __('Medium (MDF)'),
);
}
return array(
'height' => array( 'field' => 'height', 'type' => 'text', 'label' => __('Door height (mm)'), 'name' => __('Height'), ),
'width' => array( 'field' => 'width', 'type' => 'text', 'label' => __('Door width (mm)'), 'name' => __('width'), ),
'thickness' => array( 'field' => 'thickness', 'type' => 'text', 'label' => __('Thickness (mm)'), 'name' => __('Thickness'), ),
'material' => array( 'field' => 'material', 'type' => 'select', 'label' => __('Material Type (mm)'), 'name' => __('Material'), ),
);
}
// Display custom input fields in single product pages
add_action('woocommerce_before_add_to_cart_button', 'action_before_add_to_cart_button', 20);
function action_before_add_to_cart_button() {
global $product;
if ( $product->get_meta('dynamic_price') ) {
echo '<div class="custom-fields">';
// Display the 4 fields
foreach ( get_custom_fields_data() as $key => $values ) {
$args = array(
'type' => $values['type'],
'label' => $values['label'],
'class' => array( 'form-row-wide') ,
'placeholder' => $values['type'] === 'text' ? $values['name'] : '', // Optional
'required' => true,
);
if ( $values['type'] === 'select' ) {
$args['options'] = get_custom_fields_data( true );
}
woocommerce_form_field( $key, $args );
}
// Add a hidden input field for the calculated price
// Insert the Javascript
echo '<input type="hidden" name="price" value="" />
' . product_price_calculation_js( $product ) . '
</div>';
}
}
// Custom function that calculates the product price
function product_price_calculation_js( $product ) {
$zero_price = str_replace('0.00', '<span class="price-amount"></span>', wc_price(0));
$range_prices = explode('/', $product->get_meta('range_pricing'));
ob_start();
?>
<script>
jQuery(function($){
var height = 0, width = 0, thickness = 0, density = 0, price = 0,
originalPrice = $('p.price').html(), zeroPrice = '<?php echo $zero_price; ?>';
// Function that calculates the price
function calculatePrice( height, width, thickness, density, minPrice ) {
var price = 0;
if ( height > 0 && width > 0 && thickness > 0 && density > 0 ) {
const weight = (height * width * thickness * density) / 1000000000,
rate = weight * height;
if ( rate < 5350 ) {
price = <?php echo $product->get_price(); ?>;
} else if (rate >= 5350 && rate < 9000) {
price = <?php echo current($range_prices); ?>;
} else if (rate >= 9000) {
price = <?php echo end($range_prices); ?>;
}
}
return price;
}
$('form.cart').on('change mouseleave', '.custom-fields input[type=text], .custom-fields select', function(){
const fieldValue = $(this).val(), fieldKey = $(this).prop('name');
if ( fieldValue > 0 ) {
if ( fieldKey === 'height' ) {
height = fieldValue;
} else if ( fieldKey === 'width' ) {
width = fieldValue;
} else if ( fieldKey === 'thickness' ) {
thickness = fieldValue;
} else if ( fieldKey === 'material' ) {
density = fieldValue;
}
price = calculatePrice( height, width, thickness, density );
if ( price > 0 ) {
$('.custom-fields input[name=price]').val(price);
$('p.price').html(zeroPrice);
$('p.price span.price-amount').html(parseFloat(price).toFixed(2).replace('.', ','));
}
} else {
price = 0;
$('p.price').html(originalPrice);
$('.custom-fields input[name=price]').val('');
}
});
});
</script>
<?php
return ob_get_clean();
}
// Validate required product input fields
add_filter( 'woocommerce_add_to_cart_validation', 'single_product_custom_fields_validation', 10, 2 );
function single_product_custom_fields_validation( $passed, $product_id ) {
$product = wc_get_product( $product_id );
if ( $product->get_meta('dynamic_price') ) {
foreach ( get_custom_fields_data() as $key => $values ) {
if ( isset($_POST[$key]) && empty($_POST[$key]) ) {
wc_add_notice( sprintf( __('%s is a required field.', 'woocommerce'), '<strong>'.$values['label'].'</strong>'), "error" );
$passed = false;
}
}
}
return $passed;
}
// Save custom fields as custom cart item data
add_filter('woocommerce_add_cart_item_data', 'add_custom_cart_item_data', 20, 2 );
function add_custom_cart_item_data( $cart_item_data, $product_id ) {
// Save inputted fields data
foreach ( get_custom_fields_data() as $key => $values ) {
if ( isset($_POST[$key]) && ! empty($_POST[$key]) ) {
$cart_item_data['custom'][$key] = sanitize_text_field($_POST[$key]);
}
}
// Save calculated price
if ( isset($_POST['price']) && ! empty($_POST['price']) ) {
$cart_item_data['custom']['price'] = sanitize_text_field($_POST['price']);
}
return $cart_item_data;
}
// Display custom fields in Cart and Checkout
add_filter( 'woocommerce_get_item_data', 'display_custom_cart_item_data', 20, 2 );
function display_custom_cart_item_data( $cart_data, $cart_item ) {
$options = get_custom_fields_data(true);
foreach ( get_custom_fields_data() as $key => $values) {
if( isset($cart_item['custom'][$key]) ) {
$cart_data[] = array(
'key' => $values['name'],
'value' => $values['type'] === 'select' ? $options[$cart_item['custom'][$key]] : $cart_item['custom'][$key],
);
}
}
return $cart_data;
}
you will get:
// Cart and mini cart displayed calculated price
add_filter( 'woocommerce_cart_item_price', 'display_cart_item_price_html', 20, 2 );
function display_cart_item_price_html( $price_html, $cart_item ) {
if( isset($item['custom']['price']) ) {
$args = array( 'price' => $cart_item['custom']['price'] );
if ( WC()->cart->display_prices_including_tax() ) {
$price = wc_get_price_including_tax( $cart_item['data'], $args );
} else {
$price = wc_get_price_excluding_tax( $cart_item['data'], $args );
}
return wc_price( $price );
}
return $price_html;
}
// Change and set cart item custom calculated price
add_action('woocommerce_before_calculate_totals', 'set_custom_cart_item_price');
function set_custom_cart_item_price( $cart ) {
if ( is_admin() && !defined('DOING_AJAX') )
return;
foreach ( $cart->get_cart() as $item_key => $item ) {
if( isset($item['custom']['price']) ) {
$item['data']->set_price($item['custom']['price']);
}
}
}
// Save and display custom fields (custom order item metadata)
add_action( 'woocommerce_checkout_create_order_line_item', 'save_order_item_custom_meta_data', 10, 4 );
function save_order_item_custom_meta_data( $item, $cart_item_key, $values, $order ) {
foreach ( get_custom_fields_data() as $key => $data ) {
$options = get_custom_fields_data(true);
if( isset($values['custom'][$key]) ) {
$meta_value = $data['type'] === 'select' ? $options[$values['custom'][$key]] : $values['custom'][$key];
$item->update_meta_data($key, $meta_value);
}
}
}
// Add readable "meta key" label name replacement
add_filter('woocommerce_order_item_display_meta_key', 'filter_wc_order_item_display_meta_key', 10, 3 );
function filter_wc_order_item_display_meta_key( $display_key, $meta, $item ) {
if( $item->get_type() === 'line_item' ) {
foreach ( get_custom_fields_data() as $key => $values ) {
if( $meta->key === $key ) {
$display_key = $values['name'];
}
}
}
return $display_key;
}
Code goes in functions.php file of your child theme (or in a plugin). Tested and works.