Woocommerce product with custom price based on custom fields [duplicate] - woocommerce

In Woocommerce, I used jQuery to calculate a custom price on a single product pages, and now need to pass this value to the cart.
The desired behavior is to pass the new price retrieved from the hidden field to the cart item price.
Here is my actual code:
// Hidden input field in single product page
add_action( 'woocommerce_before_add_to_cart_button', 'custom_hidden_product_field', 11, 0 );
function custom_hidden_product_field() {
echo '<input type="hidden" id="hidden_field" name="custom_price" class="custom_price" value="">';
}
// The code to pass this data to the cart:
add_action( 'woocommerce_add_cart_item_data', 'save_custom_fields_data_to_cart', 10, 2 );
function save_custom_fields_data_to_cart( $cart_item_data, $product_id ) {
if( ! empty( $_REQUEST['custom_price'] ) ) {
// Set the custom data in the cart item
$cart_item_data['custom_data']['custom_price'] = $_REQUEST['custom_price'];
$data = array( 'custom_price' => $_REQUEST['custom_price'] );
// below statement make sure every add to cart action as unique line item
$cart_item_data['custom_data']['unique_key'] = md5( microtime().rand() );
WC()->session->set( 'custom_data', $data );
}
return $cart_item_data;
}
And check both $data and $cart_item_data to see that they both return the custom_price data that is calculated on the page.
However, I go to view cart, and the value of the line item is still 0.
I set a var equal to the WC()->session->set( 'custom_data', $data ); and then var_dump to check it, but this returns NULL which might just be what it returns, I'm not entirely sure because I've never used it.
I should also add that I have the regular_price in the product backend set to 0. When I erase this (and leave it blank) I get back the error:
Warning: A non-numeric value encountered in
C:\xampp\htdocs\my-transfer-source\wp-content\plugins\woocommerce\includes\class-wc-discounts.php on line 85
I'm wondering if I've missed something here, and if someone could lend some light onto this? Thanks

Update 2021 - Handling custom price item in mini cart
First for testing purpose we add a price in the hidden input field as you don't give the code that calculate the price:
// Add a hidden input field (With a value of 20 for testing purpose)
add_action( 'woocommerce_before_add_to_cart_button', 'custom_hidden_product_field', 11 );
function custom_hidden_product_field() {
echo '<input type="hidden" id="hidden_field" name="custom_price" class="custom_price" value="20">'; // Price is 20 for testing
}
Then you will use the following to change the cart item price (WC_Session is not needed):
// Save custom calculated price as custom cart item data
add_filter( 'woocommerce_add_cart_item_data', 'save_custom_fields_data_to_cart', 10, 2 );
function save_custom_fields_data_to_cart( $cart_item_data, $product_id ) {
if( isset( $_POST['custom_price'] ) && ! empty( $_POST['custom_price'] ) ) {
// Set the custom data in the cart item
$cart_item_data['custom_price'] = (float) sanitize_text_field( $_POST['custom_price'] );
// Make each item as a unique separated cart item
$cart_item_data['unique_key'] = md5( microtime().rand() );
}
return $cart_item_data;
}
// For mini cart
add_action( 'woocommerce_cart_item_price', 'filter_cart_item_price', 10, 2 );
function filter_cart_item_price( $price, $cart_item ) {
if ( isset($cart_item['custom_price']) ) {
$args = array( 'price' => floatval( $cart_item['custom_price'] ) );
if ( WC()->cart->display_prices_including_tax() ) {
$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;
}
// Updating cart item price
add_action( 'woocommerce_before_calculate_totals', 'change_cart_item_price', 30, 1 );
function change_cart_item_price( $cart ) {
if ( ( is_admin() && ! defined( 'DOING_AJAX' ) ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item ) {
// Set the new price
if( isset($cart_item['custom_price']) ){
$cart_item['data']->set_price($cart_item['custom_price']);
}
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.

Related

Saving custom WooCommerce cart data to order product item meta

I'm attempting to add meta data from the cart meta into the products in an order. So far I've been successful in adding a custom value to the products at the cart level (proved by outputting the values inline on the cart table), but once the final checkout has been done its not saving anywhere.
Adding to the cart (confirmed working):
function se_wc_add_product_order_type_cart( $cart_item, $product_id ) {
$product_order_type = $_POST['product_order_type'] ?? '';
if ( $product_order_type ) {
$cart_item['product_order_type'] = sanitize_text_field( $product_order_type );
}
return $cart_item;
}
add_filter( 'woocommerce_add_cart_item_data', 'se_wc_add_product_order_type_cart', 10, 2 );
Adding to the order from the cart (not working):
function se_wc_product_add_on_order_item_meta( $item, $cart_item_key, $values, $order ) {
$product_order_type = $values['product_order_type'] ?? '';
if ( ! empty( $product_order_type ) ) {
$item->add_meta_data( 'product_order_type', $product_order_type );
}
}
add_action( 'woocommerce_checkout_create_order_line_item', ' se_wc_product_add_on_order_item_meta', 10, 4 );
There's nothing being saved to the database (assuming its supposed to go in the wp_woocommerce_order_itemmeta table), and doing a var_dump on the meta_data field from the woocommerce_order_item_get_formatted_meta_data hook is empty - presumably again where it would show up.
If it matters, the product is a variable product and the site is a multisite.
You can use below code to add item meta to order meta:
function se_wc_product_add_on_order_item_meta( $item_id, $values ) {
$product_order_type = $values['product_order_type'];
if ( ! empty( $product_order_type ) ) {
wc_add_order_item_meta($item_id,'product_order_type',$product_order_type);
}
}
add_action( 'woocommerce_new_order_item', ' se_wc_product_add_on_order_item_meta', 1, 2 );
Thanks.

Auto update product custom field based on WooCommerce product stock

I have an ACF field "DeliverySpeed" attached to Products with the values 'Fast' and 'Slow'
I would like to update this field to change to value 'Slow' every time the product stock is zero or less (product on backorder)
I am still learning PHP so far this is what I got to but I am sure different things are missing, I just don't know in which direction to go:
based on acf update field documentation
function automatically_change_delivery( ) {
global $product;
$fieldkey = "DeliverySpeed";
$valueslow = "Slow";
if($product->get_stock_quantity()<0) { update_field( $field_key, $valueslow, $post_id );
} }
Thank you in advance for the attention and advice.
The following code will update automatically your product custom field once order has reduced product stock levels. So when product has stock the custom field value will be 'Fast' otherwise 'Slow'.
The code:
add_action( 'woocommerce_payment_complete', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
add_action( 'woocommerce_order_status_completed', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
add_action( 'woocommerce_order_status_processing', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
add_action( 'woocommerce_order_status_on-hold', 'update_product_custom_field_after_reduced_stock_levels', 20, 2 );
function update_product_custom_field_( $order_id, $order = '' ) {
// Continue only when order has reduced product stock levels
if ( wc_string_to_bool( get_post_meta( $order_id, '_order_stock_reduced', true ) ) )
return $order_id; // Exit
if( ! $order || ! is_a( $order, 'WC_Order') ) {
$order = wc_get_order( $order_id ); // Get the WC_Order object if it's empty
}
$field_key = 'DeliverySpeed';
// Loop through order items
foreach ( $order->get_items() as $item ) {
$product = $cart_item['data'];
$product_id = $product->get_id();
$stock_qty = $product->get_stock_quantity();
$field_value = get_field( $field_key, $product_id ); // Get ACF field value
if ( $stock_qty <= 0 && $field_value === 'Fast' ) {
update_field( $field_key, 'Slow', $product_id );
}
elseif ( $stock_qty > 0 && $field_value === 'Slow' ) {
update_field( $field_key, 'Fast', $product_id );
}
}
}
Code goes in functions.php file of the active child theme (or active theme). It should works.

Add "sub-total" to my-account/orders table

Trying to add the order subtotal in woocommerce my-account/orders table but I can't seem to get it to display. Currently it adds the column and the label but its not displaying the orders sub total. I am currently using the code below :
add_filter( 'woocommerce_account_orders_columns',
'add_account_orders_column', 10, 1 );
function add_account_orders_column( $columns ){
$columns['item_subtotal_tax_excl'] = __( 'Sub-total', 'woocommerce' );
return $columns;
}
add_action( 'woocommerce_my_account_my_orders_column_custom-column',
'add_account_orders_column_rows' );
function add_account_orders_column_rows( $order ) {
// Example with a custom field
if ( $value = $order->get_meta( 'item_subtotal_tax_excl' ) ) {
echo esc_html( $value );
}
}
Subtotal like in cart doesn't exist in WooCommerce Orders meta data, so you need to get it and calculate it from order items:
add_filter( 'woocommerce_account_orders_columns', 'add_account_orders_column', 10, 1 );
function add_account_orders_column( $columns ){
$columns['item_subtotal_tax_excl'] = __( 'Sub-total', 'woocommerce' );
return $columns;
}
add_action( 'woocommerce_my_account_my_orders_column_custom-column', 'display_account_orders_column_rows_value' );
function display_account_orders_column_rows_value( $order ) {
$subtotal = 0; // Initializing
// Loop through order items (line items)
foreach ( $order->get_items() as $item ) {
// Sum item subtotal excluding taxes and not discounted
$subtotal += $item->get_subtotal();
}
echo $subtotal;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Related:
Get Order items and WC_Order_Item_Product in WooCommerce 3
How to get WooCommerce order details
Get the metadata of an order item in woocommerce 3
Edit: the following code worked for me: (answer provided by helgatheviking on woocommerce slack)
//show sub total in order page
add_filter( 'woocommerce_account_orders_columns',
'add_account_orders_column', 10, 1 );
function add_account_orders_column( $columns ){
$columns['item_subtotal_tax_excl'] = __( 'Sub-Total',
'woocommerce' );
return $columns;
}
add_action(
'woocommerce_my_account_my_orders_column_item_subtotal_tax_excl',
'add_account_orders_column_rows' );
function add_account_orders_column_rows( $order ) {
// Example with a custom field
if ( $value = $order->get_subtotal() ) {
echo esc_html( $value );
}
}

Changing product name on checkout and order on WooCommerce with WPML

I am using WooCommerce with WPML plugin. I want to implement a feature on checkout when a customer under certain conditions can have an upgrade of his product but keeping the old product price.
The products are variable with many variable attributes.
So, more specifically, what I want is if a customer has selected a specific product variation with x price on checkout (under a certain condition) I could change his cart item with another product's variation but keep the x price.
What I tried first is to change only the name of the product using woocommerce_order_item_name hook but the change doesn't follow on the order. This is important because some order data are then sent to an API.
Afterwards I used "Changing WooCommerce cart item names" answer code, which worked perfectly for my purpose until I installed WPML. For some reason the WC_Cart method set_name() doesn't work with WPML. I opened a support thread but they still can't find a solution.
Can anyone suggest any other solution?
Update
I have tried an approach where I remove the product item on cart and then I add the one I need. After I use set_price() to change the price of the newly added item. The removal/addition seems to be working but the price is not changed on one language and it is not applied on both languages after placing order.
This is the code I use:
function berrytaxiplon_change_product_name( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Get an instance of the WC_Product object
$product = $cart_item['data'];
// Get the product name (Added Woocommerce 3+ compatibility)
$product_id = method_exists( $product, 'get_parent_id' ) ? $product->get_parent_id() : $product->post->post_parent;
if ( ICL_LANGUAGE_CODE == 'en') {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 12) {
$new_product = wc_get_product( 82 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(82, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 82, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( $cart_item['s-fare'] );
}
}
} else {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 282) {
$new_product = wc_get_product( 303 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(303, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 303, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( $cart_item['s-fare']);
}
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'berrytaxiplon_change_product_name', 10, 1 );
Any idea why the set_price() method is not applied?
Update 2
WPMl uses 'woocommerce_before_calculate_totals' and overrides the action added on functions.php
WPML support provided a solution using 3 filters:
https://wpml.org/forums/topic/cant-use-set_name-method-for-the-product-object-on-checkout/#post-3977153
So this is a code that I am using in one of my projects to add a product variation to cart based off of some filters and the selected product:
$product = new WC_Product($product_id); //The main product whose variation has to be added
$product_name = $product->get_name(); //Name of the main product
$quantity = sanitize_text_field($cData['quantity']); //You can set this to 1
$variation_id = sanitize_text_field($cData['variation_id']); //I had the variation ID from filters
$variation = array(
'pa_duration' => sanitize_text_field($cData['duration']) //The variation slug was also available for me.
);
$cart_item_data = array('custom_price' => sanitize_text_field($custom_price));
$cart = WC()->cart->add_to_cart( (int)$product_id, (int)$quantity, (int)$variation_id, $variation, $cart_item_data ); //This will add products to cart but with the actual price of the variation being added and meta data holding the custom price.
WC()->cart->calculate_totals();
WC()->cart->set_session();
WC()->cart->maybe_set_cart_cookies();
Then you need to do a check on before cart totals are calculated and set the price to custom price like this:
function woocommerce_custom_price_to_cart_item( $cart_object ) {
if( !WC()->session->__isset( "reload_checkout" )) {
foreach ( $cart_object->cart_contents as $key => $value ) {
if( isset( $value["custom_price"] ) ) {
$value['data']->set_price($value["custom_price"]);
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'woocommerce_custom_price_to_cart_item', 99 );
The code provided from Faham is very helpful but the page-template that leads to checkout is already over-complicated so I focused to use his logic on the 'woocommerce_before_calculate_totals' hook I am trying all along.
So instead of trying to change the name I remove the item and add the new one. Then calling a new loop I set the price to be of the item that was removed.
function berrytaxiplon_change_product_name( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Loop through cart items
foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
// Get an instance of the WC_Product object
$product = $cart_item['data'];
// Get the product name (Added Woocommerce 3+ compatibility)
$product_id = method_exists( $product, 'get_parent_id' ) ? $product->get_parent_id() : $product->post->post_parent;
if ( ICL_LANGUAGE_CODE == 'en') {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 12) {
// SET THE NEW NAME
$new_product = wc_get_product( 82 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(82, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 82, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( get_post_meta( $cart_item['variation_id'], '_price', true ) );
}
}
} else {
if (isset($cart_item['s-member-level']) && $cart_item['s-member-level'] == 3 && $product_id == 282) {
// SET THE NEW NAME
$new_product = wc_get_product( 303 );
$atrributes = $product->get_attributes('view');
foreach ($atrributes as $atrribute_key => $atrribute_value) {
$new_attributes['attribute_' . $atrribute_key] = strtolower($atrribute_value);
}
$new_variation_id = find_matching_product_variation_id(303, $new_attributes);
$cart->remove_cart_item( $cart_item_key );
$cart->add_to_cart( 303, 1, $new_variation_id, $new_attributes, $cart_item );
foreach ( WC()->cart->get_cart() as $new_item ) {
$new_item['data']->set_price( get_post_meta( $cart_item['variation_id'], '_price', true ) );
}
}
}
}
}
add_action( 'woocommerce_before_calculate_totals', 'berrytaxiplon_change_product_name', 10, 1 );
I use the function below to match the attributes taken from the question WooCommerce: Get Product Variation ID from Matching Attributes
function find_matching_product_variation_id($product_id, $attributes)
{
return (new \WC_Product_Data_Store_CPT())->find_matching_product_variation(
new \WC_Product($product_id),
$attributes
);
}
I am a little skeptical using add_to_cart() and a second foreach() inside the $cart_item. But I tested and it seems to work without errors.
Update
Actually there is an issue with this code (or with WPML again). It seems that set_price() is not applied on the secondary language. Yet if I reload the checkout page an send the data again the new price is applied.

How to add a custom text box value to cart session array in Woocommerce

We need to get the text input from Add to cart page in cart_session and display it in the view cart page and save those in database too. I tried to add it in:
$this->cart_contents[$cart_item_key] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array('' => '','custom_text' => $_POST['custom_txt']) ), $cart_item_key );
It is saving the custom_txt one time and it clears it when the next product is added to cart. I think we need to do something to for save those in session. Please help us.
I assume you have one (or more) fields in "Single Product" page and you want to insert the values from that (or those) field(s) inside the cart. In my one project I used the following code that might help you. You need to customize as your need.
// Add to cart
add_filter( 'woocommerce_add_cart_item', 'woocommerce_add_cart_item_custom' );
// Add item data to the cart
add_filter( 'woocommerce_add_cart_item_data', 'woocommerce_add_cart_item_data_custom', 10, 2 );
// Load cart data per page load
add_filter( 'woocommerce_get_cart_item_from_session', 'woocommerce_get_cart_item_from_session_custom', 20, 2 );
// Get item data to display
add_filter( 'woocommerce_get_item_data', 'woocommerce_get_item_data_custom', 10, 2 );
// Validate when adding to cart
add_filter( 'woocommerce_add_to_cart_validation', 'woocommerce_add_to_cart_validation_custom', 10, 3 );
// Add meta to order
add_action( 'woocommerce_add_order_item_meta', 'woocommerce_add_order_item_meta_custom', 10, 2 );
// order again
add_filter( 'woocommerce_order_again_cart_item_data', 'woocommerce_order_again_cart_item_data', 10, 3 );
// add to cart
function woocommerce_add_cart_item_custom( $cart_item ) {
// operation done while item is added to cart.
return $cart_item;
}
// get cart from session
function woocommerce_get_cart_item_from_session_custom( $cart_item, $values ) {
if (!empty($values['cusotm_field'])) :
$cart_item['cusotm_field'] = $values['cusotm_field'];
$cart_item = woocommerce_add_cart_item_custom( $cart_item );
endif;
return $cart_item;
}
// get item data
function other_options_get_item_data( $other_data, $cart_item ) {
if (!empty($cart_item['custom_option'])) :
$other_data = array(
'name' => $custom,
'value' => $value,
'display' => $custom .' : '.$value
);
endif;
return $other_data;
}
function woocommerce_add_cart_item_data_custom($cart_item_meta, $product_id){
global $woocommerce;
if(empty($cart_item_meta['custom_field']))
$cart_item_meta['custom_field'] = array();
$cart_item_meta['custom_field'] = esc_attr($_POST[sanitize_title('name_of_field')]);
return $cart_item_meta;
}
// add to order meta
function woocommerce_add_order_item_meta_custom( $item_id, $values ) {
if ( ! empty( $values['custom_option'] ) ) {
woocommerce_add_order_item_meta( $item_id, $option_name, $option_value );
}
}
// validation
function woocommerce_add_to_cart_validation_custom($passed, $product_id, $qty){
global $woocommerce;
$option = ''; // your custom field's name
if( isset($_POST[sanitize_title($option)]) && $_POST[sanitize_title($option)] == '' )
$passed = false;
if (!$passed)
$woocommerce->add_error( sprintf( __('"%s" is a required field.', 'woocommerce'), $option) );
return $passed;
}
function woocommerce_order_again_cart_item_data($cart_item_meta, $product, $order){
global $woocommerce;
// Disable validation
remove_filter( 'woocommerce_add_to_cart_validation', array( $this, 'validate_add_cart_item' ), 10, 3 );
if(empty($cart_item_meta['custom_field']))
$cart_item_meta['custom_field'] = array();
$cart_item_meta['_other_options']= esc_attr($_POST[sanitize_title('field_name')]);
return $cart_item_meta;
}
This is a very general code. you need to modify that best suites you need.
Thanks.

Resources