I need to change the default coupon label that WooCommerce is adding to the cart and checkout table.
This can be done with:
add_filter( 'woocommerce_cart_totals_coupon_label', 'my_function', 99, 2 );
function my_function( $label, $coupon ) {
return 'Discount';
}
But I need different names for the coupons. I need coupon 1 to be 'Discount', and all other coupons should be displayed as 'Coupon' (without the actual coupon name), like in this image.
You can use $coupon->get_code() to get the coupon code from the coupon object, which is passed as the 2nd argument to the callback function.
So you get:
function filter_woocommerce_cart_totals_coupon_label( $label, $coupon ) {
// Compare
if ( $coupon->get_code() == 'coupon 1' ) {
$label = __( 'Discount', 'woocommerce' );
} else {
$label = __( 'Coupon', 'woocommerce' );
}
return $label;
}
add_filter( 'woocommerce_cart_totals_coupon_label', 'filter_woocommerce_cart_totals_coupon_label', 10, 2 );
Related
so I'm looking to create/show a couple of custom fields on the checkout page of my WooCommerce page as long as a product with a certain category is in the cart. The values of these fields are only necessary for me to access in the order on the backend afterwards, and does not need to be added to the order e-mail confirmation to the customer.
Any pointers? If it helps things, I'm using ACF on my website.
Thanks in advance!
You need to do the following:
Detect if the existing products in cart is in your category
Add the fields on checkout if it matches your condition.
Validate and save the data
Display it on the backend.
This is already outlined on the docs, and you might want to read it:
https://woocommerce.com/document/tutorial-customising-checkout-fields-using-actions-and-filters/#adding-a-custom-special-field
Here is an example function to detect if the cart contains a certain product within a defined category
// functions.php
function cat_in_cart( $cat_slug ) {
$cat_in_cart = false;
$cart = WC()->cart->get_cart();
if ( !$cart ) {
return $cat_in_cart;
}
foreach( $cart as $cart_item_key => $cart_item ) {
if ( has_term( $cat_slug, 'product_cat', $cart_item['product_id'] )) {
$cat_in_cart = true;
break;
}
}
return $cat_in_cart;
}
To add a field on checkout (Link to docs):
// functions.php
/**
* Add the field to the checkout
*/
add_action( 'woocommerce_after_order_notes', 'my_custom_checkout_field' );
function my_custom_checkout_field( $checkout ) {
if ( cat_in_cart( 'your_category_slug' ) ) {
echo '<div id="my_custom_checkout_field"><h2>' . __('My Field') . '</h2>';
woocommerce_form_field( 'my_field_name', array(
'type' => 'text',
'class' => array('my-field-class form-row-wide'),
'label' => __('Fill in this field'),
'placeholder' => __('Enter something'),
), $checkout->get_value( 'my_field_name' ));
echo '</div>';
}
}
After that, save the field on checkout:
add_action('woocommerce_checkout_process', 'my_custom_checkout_field_process');
function my_custom_checkout_field_process() {
// Check if set, if its not set add an error.
if ( ! $_POST['my_field_name'] )
wc_add_notice( __( 'Please enter something into this new shiny field.' ), 'error' );
}
add_action( 'woocommerce_checkout_update_order_meta', 'my_custom_checkout_field_update_order_meta' );
function my_custom_checkout_field_update_order_meta( $order_id ) {
if ( ! empty( $_POST['my_field_name'] ) ) {
update_post_meta( $order_id, 'My Field', sanitize_text_field( $_POST['my_field_name'] ) );
}
}
Lastly, display it on the dashboard:
/**
* Display field value on the order edit page
*/
add_action( 'woocommerce_admin_order_data_after_billing_address', 'my_custom_checkout_field_display_admin_order_meta', 10, 1 );
function my_custom_checkout_field_display_admin_order_meta($order){
echo '<p><strong>'.__('My Field').':</strong> ' . get_post_meta( $order->id, 'My Field', true ) . '</p>';
}
I have created a product on WooCommerce, and added two options on product detail page using the hook woocommerce_before_add_to_cart_button. Now when customers add product to cart from product detail page they have two options their. They can choose one option from these two options.
Then I have stored the user selected value in cart meta using the woocommerce hook woocommerce_add_cart_item_data.
I am using the code from this answer: Save product custom field radio button value in cart and display it on Cart page
This is my code:
// single Product Page options
add_action("woocommerce_before_add_to_cart_button", "options_on_single_product");
function options_on_single_product(){
$dp_product_id = get_the_ID();
$product_url = get_permalink($dp_product_id);
?>
<input type="radio" name="custom_options" checked="checked" value="option1"> option1<br />
<input type="radio" name="custom_options" value="option2"> option2
<?php
}
//Store the custom field
add_filter( 'woocommerce_add_cart_item_data', 'save_custom_data_with_add_to_cart', 10, 2 );
function save_custom_data_with_add_to_cart( $cart_item_meta, $product_id ) {
global $woocommerce;
$cart_item_meta['custom_options'] = $_POST['custom_options'];
return $cart_item_meta;
}
And this is what I have tried:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price', 10, 1);
function add_custom_price( $cart_obj ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
foreach ( $cart_obj->get_cart() as $key => $value ) {
$product_id = $value['product_id'];
$custom_options = $value['custom_options'];
$coupon_code = $value['coupon_code'];
if($custom_options == 'option2')
{
if($coupon_code !='')
{
global $woocommerce;
if ( WC()->cart->has_discount( $coupon_code ) ) return;
(WC()->cart->add_discount( $coupon_code ))
//code for second discount
}
else{
$percentage = get_post_meta( $product_id , 'percentage', true );
//print_r($value);
$old_price = $value['data']->regular_price;
$new_price = ($percentage / 100) * $old_price;
$value['data']->set_price( $new_price );
}
}
}
}
Now what I am trying to get with that last snippet is:
If Option1 is selected by the customer then woocommerce regular process is run.
If Option2 is selected then firstly coupon code applied to cart (if code entered by the customer) and then the price is divide by some percentage (stored in product meta) is applied afterward.
But it’s not working as expected because the changed product price is maid before and coupon discount is applied after on this changed price.
What I would like is that the coupon discount will be applied first on the product regular price and then after change this price with my custom product discount.
Is this possible? How can I achieve that?
Thanks.
This is not really possible … Why? … Because (the logic):
You have the product price
Then the coupon discount is applied to that price (afterwards)
==> if you change the product price, the coupon is will be applied to that changed price
What you can do instead:
You don't change product price
if entered the coupon is applied and …
If "option2" product is added to cart:
Apply a custom discount (a negative fee) based on the product price added after using WC_cart add_fee() method…
For this last case you will have to fine tune your additional discount.
If the coupon has not been applied or it's removed there is no additional discount.
Your custom function will be hooked in woocommerce_cart_calculate_fees action hook instead:
add_action( 'woocommerce_cart_calculate_fees', 'option2_additional_discount', 10, 1 );
function option2_additional_discount( $cart_obj ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$discount = 0;
$applied_coupons = $cart_obj->get_applied_coupons();
foreach ( $cart_obj->get_cart() as $item_values ) {
if( 'option2' == $item_values['custom_options'] && !empty($applied_coupons) ){
$product_id = $item_values['product_id'];
$percentage = get_post_meta( $product_id , 'percentage', true );
$quantity = $item_values['quantity'];
$product_reg_price = $item_values['data']->regular_price;
$line_total = $item_values['line_total'];
$line_subtotal = $item_values['line_subtotal'];
$percentage = 90;
## ----- CALCULATIONS (To Fine tune) ----- ##
$item_discounted_price = ($percentage / 100) * $product_reg_price * $item_values['quantity'];
// Or Besed on line item subtotal
$discounted_price = ($percentage / 100) * $line_subtotal;
$discount += $product_reg_price - $item_discounted_price;
}
}
if($discount != 0)
$cart_obj->add_fee( __( 'Option2 discount', 'woocommerce' ) , - $discount );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
This code is tested and works.
Adding a negative fee using the WC_Cart->add_fee() method wasn't working for me. When i check the WC Cart class, it even states you are not allowed to use a negative ammount.
See the docs.
I did the following:
create a placeholder coupon with a 'secure' code, e.g. custom_discount_fjgndfl28. Set a discount ammount of 0, so when somebody (somehow) uses this coupon outside your program the discount is still 0.
Use filter woocommerce_get_shop_coupon_data, and set all the coupon data you want for that coupon/session.
Hook into woocommerce_before_calculate_totals and set your custom coupon to the cart.
At this point the Cart should calculate everything correctly. And when it becomes an order, it also has the correct discount ammount.
Note: the coupon code is also used as a label in some templates. Use filter woocommerce_cart_totals_coupon_label to change it.
Example functions:
/**
* NOTE: All the hooks and filters below have to be called from your own
* does_it_need_custom_discount() function. I used the 'wp' hook for mine.
* Do not copy/paste this to your functions.php.
**/
add_filter('woocommerce_get_shop_coupon_data', 'addVirtualCoupon', 10, 2);
function addVirtualCoupon($unknown_param, $curr_coupon_code) {
if($curr_coupon_code == 'custom_discount_fjgndfl28') {
// possible types are: 'fixed_cart', 'percent', 'fixed_product' or 'percent_product.
$discount_type = 'fixed_cart';
// how you calculate the ammount and where you get the data from is totally up to you.
$amount = $get_or_calculate_the_coupon_ammount;
if(!$discount_type || !$amount) return false;
$coupon = array(
'id' => 9999999999 . rand(2,9),
'amount' => $amount,
'individual_use' => false,
'product_ids' => array(),
'exclude_product_ids' => array(),
'usage_limit' => '',
'usage_limit_per_user' => '',
'limit_usage_to_x_items' => '',
'usage_count' => '',
'expiry_date' => '',
'apply_before_tax' => 'yes',
'free_shipping' => false,
'product_categories' => array(),
'exclude_product_categories' => array(),
'exclude_sale_items' => false,
'minimum_amount' => '',
'maximum_amount' => '',
'customer_email' => '',
'discount_type' => $discount_type,
);
return $coupon;
}
}
add_action('woocommerce_before_calculate_totals', 'applyFakeCoupons');
function applyFakeCoupons() {
global $woocommerce;
// $woocommerce->cart->remove_coupons(); remove existing coupons if needed.
$woocommerce->cart->applied_coupons[] = $this->coupon_code;
}
add_filter( 'woocommerce_cart_totals_coupon_label', 'cart_totals_coupon_label', 100, 2 );
function cart_totals_coupon_label($label, $coupon) {
if($coupon) {
$code = $coupon->get_code();
if($code == 'custom_discount_fjgndfl28') {
return 'Your custom coupon label';
}
}
return $label;
}
Please Note: i copied these functions out of a class that handles much more, it's only to help you get going.
I want to change the out of stock message in WooCommerce for one category only on the single product page and shop archive pages.
I am using OceanWP theme
This is what I have so far, which works, but I need to add the "if" statement for category.
/**
*This changes the out of stock text on the item in oceanwp theme product gallery
*/
function my_woo_outofstock_text( $text ) {
$text = __( 'Sold', 'oceanwp' );
return $text;
}
add_filter( 'ocean_woo_outofstock_text', 'my_woo_outofstock_text', 20 );
Here is my code attempt, based on this similar question here, but it only works on the single product page. Any advice?
function my_woo_outofstock_text( $text, $product ) {
$specific_categories = array( 'original-paintings' );
if ( ! $product->is_in_stock() && has_term( $specific_categories, 'product_cat', $product->get_id() ) ) {
$text = __( 'Sold', 'oceanwp' );
}
else {
$text = __( 'Unavailable', 'oceanwp' );
}
return $text;
}
add_filter( 'ocean_woo_outofstock_text', 'my_woo_outofstock_text', 20 );
The filter hook you are using only contains 1 argument, so $product is not part of it. If you still want to apply changes based on the $product for the shop archive pages, you should apply this globally.
So you get:
function my_woo_outofstock_text( $text ) {
global $product;
// Add categories. Multiple can be added, separated by a comma
$specific_categories = array( 'original-paintings' );
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
// NOT in stock & has term
if ( ! $product->is_in_stock() && has_term( $specific_categories, 'product_cat', $product->get_id() ) ) {
$text = __( 'Sold', 'oceanwp' );
} else {
$text = __( 'Unavailable', 'oceanwp' );
}
}
return $text;
}
add_filter( 'ocean_woo_outofstock_text', 'my_woo_outofstock_text', 20, 1 );
For the single product page you can use Custom "Out of stock" text based on product category in WooCommerce answer code.
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 );
}
}
What Woocommerce Does is...
When products are Sold Individually, and when the product already exists in the Cart and customer clicks on Add to Cart, Woocommerce shows Error Message "You cannot add another to your cart.....View Cart"
Instead of the above flow I want..
When customer clicks on Add to Cart and if the product already exists in the Cart then woocommerce should redirect to Checkout page straightway.
I think this can be achieved by editing few lines of code in class-wc-cart.php of Woocommerce Plugin.
The is given below:
// Force quantity to 1 if sold individually and check for existing item in cart
if ( $product_data->is_sold_individually() ) {
$quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data );
$in_cart_quantity = $cart_item_key ? $this->cart_contents[ $cart_item_key ]['quantity'] : 0;
if ( $in_cart_quantity > 0 ) {
throw new Exception( sprintf( '%s %s', wc_get_cart_url(), __( 'View Cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_title() ) ) );
}
}
I was just looking into how to do this myself. user1072884's answer is okay, however, it only works for one productID at a time. Not ideal if you have multiple products.
Here's a simplified solution that will work for all products.
Simply add the following code to your child theme functions.php and swap the placeholder text with your checkout page URL.
/** If multiple items are in the cart redirect to the checkout page instead of throwing an error **/
add_filter( 'woocommerce_add_to_cart_validation', 'check_cart' );
function check_cart( $cart_item_data ) {
global $woocommerce;
if ( $woocommerce->cart->cart_contents_count > 0 ) {
$direct_url = home_url( 'xyz' ); // Change the value of your actual redirect url.
wp_redirect( $direct_url );
exit();
}
}
Old thread, but maybe someone else will find this helpful.
Try this it will work:
add_filter( 'woocommerce_add_to_cart_sold_individually_quantity', 'vipcomment_change_quantity_to_zero', 10, 5 );
function vipcomment_change_quantity_to_zero( $one, $quantity, $product_id, $variation_id, $cart_item_data ) {
$your_product_id = 96;
if ( $product_id == $your_product_id ) {
$product_cart_id = WC()->cart->generate_cart_id( $product_id );
$in_cart = WC()->cart->find_product_in_cart( $product_cart_id );
if ( $in_cart ) {
return 0;
} else {
return $quantity;
}
} else {
return $quantity;
}
}
add_filter( 'woocommerce_add_to_cart_sold_individually_found_in_cart', 'vipcomment_is_product_exist_in_cart', 10, 5 );
function vipcomment_is_product_exist_in_cart( $exist, $product_id, $variation_id, $cart_item_data, $cart_id ) {
$your_product_id = 96;
if ( $product_id == $your_product_id ) {
return false;
} else {
return $exist;
}
}
Simplest way would be:
throw new Exception( sprintf( '%s %s',
wc_get_cart_url(), __( 'View cart', 'woocommerce' ),
header( "Location: https://www.example.com/cart/" ) ) );
instead of that error. Be advised: this will redirect the user to the cart page ( where the product should already be in place ).