Changing WooCommerce cart price after applied coupon code - php

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.

Related

How to change shipping method title based on cart amount?

I applied a 5% discount to the Woocommerce cart, based on cart amount and shipping method, which seems to be working fine. But I cannot manage to change the shipping method title when the rule applies, i need to add a message "5% discount" to all local-pickup methods.
I found a workarround using conditional shipping, but that won´t work in this website, I need to use the same method, but adding a message to the title.
Any ideas?
This is my code:
function local_pickup_discount( $cart ) {
$chosen_methods = WC()->session->get( 'chosen_shipping_methods' );
$carrito_total = WC()->cart->get_cart_contents_total();
$chosen_shipping_no_ajax = $chosen_methods[0];
if ( 0 === strpos( $chosen_shipping_no_ajax, 'local_pickup' ) && $carrito_total < 60000) {
$discount = $cart->subtotal * 0.05; // 5% discount applied here
$cart->add_fee( __( 'Descuento por retiro en sucursal', 'yourtext-domain' ) , -$discount ); // Fee descripton
}
}
add_action( 'woocommerce_cart_calculate_fees', 'local_pickup_discount');
Thanks in advance, i know this must be a very simple thing to do, but i am not used to work with php.
You have to use this hook to rename your label woocommerce_cart_shipping_method_full_label. Inside you can apply any conditional logic you want and output label with custom one.
Note: This label is used in both cart and checkout. So if you want it only on checkout add condition is_checkout() or is_cart() if you want to limit it.
function wc_change_local_pickup_label( $label, $method ) {
$carrito_total = WC()->cart->get_cart_contents_total();
if ( $carrito_total < 60000 && 'local_pickup' === $method->method_id) {
$label = "Local pickup 5% discount";
}
return $label;
}
add_filter( 'woocommerce_cart_shipping_method_full_label', 'wc_change_local_pickup_label', 10, 2 );

Remove fee from Woocommerce order using hook before inserting to database

Is there any way to remove a fee from an Woocommerce order before saving it to the database?
I have tried the following hooks, to no success
woocommerce_before_save_order_items
woocommerce_calculate_totals
wp_insert_post_data
I also tried to edit the fee total as below, but the fee still gets saved to the database
add_action( 'woocommerce_review_order_before_payment', 'cst_my_hook_1', 10, 3);
function cst_my_hook_1() {
WC()->cart->set_fee_total(0);
}
I am sharing a screenshot to make my requirements more clear. Woocommerce cart class (class-wc-cart.php) contains a public function to add fees, so I think there should be ways to remove it too.
I used the hook "woocommerce_cart_calculate_fees" to add the fee shown in the screenshot. Now I want to remove it before saving to DB.
I am using Wordpress 5.7.1, Woocommerce 5.2.1
To disable fees from order once checking out, use this simple following hooked function, that will clean all fees from orders and email notifications:
add_action( 'woocommerce_checkout_order_created', 'order_created_disable_fees' );
function order_created_disable_fees( $order ) {
$targeted_item_name = __( "Total Tax Payment", "woocommerce" );
foreach( $order->get_items( 'fee' ) as $item_id => $item ) {
if( $targeted_item_name === $item['name'] ) {
$order->remove_item($item_id);
}
}
$order->calculate_totals();
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
Now if you want to remove the fees, but keep original total order amount, use the following:
add_action( 'woocommerce_checkout_order_created', 'order_created_disable_fees' );
function order_created_disable_fees( $order ) {
$targeted_item_name = __( "Total Tax Payment", "woocommerce" );
foreach( $order->get_items( 'fee' ) as $item_id => $item ) {
if( $targeted_item_name === $item['name'] ) {
$order->remove_item($item_id);
}
}
$order->save();
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.
If you want to remove a fee from an order before saving it in the database you can use the woocommerce_create_order hook that fires before the order is created within the create_order method of the WC_Checkout class.
Based on this answer:
Remove applied specific fee from Cart programmatically
you will be able to remove the fee from the cart.
However, you will also have to recalculate the totals and taxes (it works whether the fee is taxable or not, and even with multiple tax classes).
REMOVE A FEE FROM CART AND RECALCULATE TOTALS
// removes a specific fee by name before creating the order and recalculates the totals
add_filter( 'woocommerce_create_order', 'remove_specific_fee_from_cart_before_creating_order', 10, 2 );
function remove_specific_fee_from_cart_before_creating_order( $order_id = null, $order ) {
// get the fees added to the cart
$fees = WC()->cart->get_fees();
// initialize taxes (if the fee is not taxable)
$fee_tax = 0;
$fee_tax_data = array();
foreach ( $fees as $key => $fee ) {
// replace "Total Tax Payment" with the name of the fee you added to the cart
if ( $fee->name == 'Total Tax Payment' ) {
// gets the data to recalculate the cart total
$fee_amount = $fee->amount;
if ( $fee->taxable ) {
$fee_tax = $fee->tax;
$fee_tax_data = $fee->tax_data;
}
// removes the fee
unset( $fees[$key] );
break;
}
}
// updates the cart fees
WC()->cart->fees_api()->set_fees( $fees );
// gets the current values of the cart to be recalculated
$cart_total = WC()->cart->get_total(''); // returns the float value instead of HTML code
$cart_total_tax = WC()->cart->get_total_tax();
$cart_fee_taxes = WC()->cart->get_fee_taxes();
// if the fee is taxable
// recalculates the taxes by removing the taxes of the removed fee
if ( ! empty( $fee_tax_data ) ) {
foreach ( $cart_fee_taxes as $fee_tax_key => $fee_tax ) {
if ( array_key_exists( $fee_tax_key, $fee_tax_data ) ) {
$cart_fee_taxes[$fee_tax_key] = $fee_tax - $fee_tax_data[$fee_tax_key];
}
}
}
// updates the cart totals
WC()->cart->set_total( $cart_total - $fee_amount - $fee_tax );
WC()->cart->set_total_tax( $cart_total_tax - $fee_tax );
WC()->cart->set_fee_taxes( $cart_fee_taxes );
return $order_id;
}
REMOVES A FEE FROM CART WITHOUT RECALCULATING THE TOTALS
// removes a specific fee by name before creating the order
add_filter( 'woocommerce_create_order', 'remove_specific_fee_from_cart_before_creating_order', 10, 2 );
function remove_specific_fee_from_cart_before_creating_order( $order_id = null, $order ) {
// get the fees added to the cart
$fees = WC()->cart->get_fees();
foreach ( $fees as $key => $fee ) {
// replace "Total Tax Payment" with the name of the fee you added to the cart
if ( $fee->name == 'Total Tax Payment' ) {
// removes the fee
unset( $fees[$key] );
break;
}
}
// updates the cart fees
WC()->cart->fees_api()->set_fees( $fees );
return $order_id;
}
The code has been tested and works. Add it to your active theme's functions.php.

GET and set a custom product price on add to cart via URL in WooCommerce

I am trying to pass a parameter to a Woocommerce site using a URL - for instance:
site/?custom_p=77 will be the URL.
here is my code
add_filter('woocommerce_product_get_price', 'custom_price', 99, 2 );
add_filter('woocommerce_product_get_regular_price', 'custom_price', 99, 2 );
function custom_price( $cprice, $product ) {
// Delete product cached price (if needed)
wc_delete_product_transients($product->get_id());
// Get the data from the GET request
$custom_p=$_GET['custom_p'];
return ($custom_p) ;
}
URL add to cart site/?add-to-cart=455&custom_p=77
The problem when I declare the number 77 directly it works $custom_p=77 but $_GET['custom_p'] declaration gives as a result zero in cart.
Your GET variables get lost once URL changes, so all product prices get empty because also you are not targeting a the product that has been added to cart.
In this case you need to enable and use a WooCommerce Session variable to store the product ID and the custom price, when a custom_p GET variable is detected. Then you can use that WooCommerce Session variable to change the product price.
First we detect and store the necessary data in a WC_Session variable:
// get and set the custom product price in WC_Session
add_action( 'init', 'get_custom_product_price_set_to_session' );
function get_custom_product_price_set_to_session() {
// Check that there is a 'custom_p' GET variable
if( isset($_GET['add-to-cart']) && isset($_GET['custom_p'])
&& $_GET['custom_p'] > 0 && $_GET['add-to-cart'] > 0 ) {
// Enable customer WC_Session (needed on first add to cart)
if ( ! WC()->session->has_session() ) {
WC()->session->set_customer_session_cookie( true );
}
// Set the product_id and the custom price in WC_Session variable
WC()->session->set('custom_p', [
'id' => (int) wc_clean($_GET['add-to-cart']),
'price' => (float) wc_clean($_GET['custom_p']),
]);
}
}
Then there is 2 ways to change the price of the product added to cart (choose only one)
Option 1 - Changing the product price directly:
// Change product price from WC_Session data
add_filter('woocommerce_product_get_price', 'custom_product_price', 900, 2 );
add_filter('woocommerce_product_get_regular_price', 'custom_product_price', 900, 2 );
add_filter('woocommerce_product_variation_get_price', 'custom_product_price', 900, 2 );
add_filter('woocommerce_product_variation_get_regular_price', 'custom_product_price', 900, 2 );
function custom_product_price( $price, $product ) {
if ( ( $data = WC()->session->get('custom_p') ) && $product->get_id() == $data['id'] ) {
$price = $data['price'];
}
return $price;
}
Option 2 - Change the cart item price:
// Change cart item price from WC_Session data
add_action( 'woocommerce_before_calculate_totals', 'custom_cart_item_price', 20, 1 );
function custom_cart_item_price( $cart ){
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// Must be required since Woocommerce version 3.2 for cart items properties changes
if ( did_action( 'woocommerce_before_calculate_totals' ) >= 2 )
return;
// Looo through our specific cart item keys
foreach ( $cart->get_cart() as $cart_item ) {
// Get custom product price for the current item
if ( ( $data = WC()->session->get('custom_p') ) && $cart_item['data']->get_id() == $data['id'] ) {
// Set the new product price
$cart_item['data']->set_price( $data['price'] );
}
}
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
USAGE URL variables example: www.example.com/?add-to-cart=37&custom_p=75
Option 1 gives a fatal error on WC admin. To fix that, I had to add !is_admin() to the if clause so it should look like this:
add_filter('woocommerce_product_get_price', 'custom_product_price', 900, 2 );
add_filter('woocommerce_product_get_regular_price', 'custom_product_price', 900, 2 );
add_filter('woocommerce_product_variation_get_price', 'custom_product_price', 900, 2 );
add_filter('woocommerce_product_variation_get_regular_price', 'custom_product_price', 900, 2 );
function custom_product_price( $price, $product ) {
if ( ( !is_admin() && $data = WC()->session->get('custom_p') ) && $product->get_id() == $data['id'] ) {
$price = $data['price'];
}
return $price;
}
Nice, it works even when dreamweaver says there is a syntax error in the first Part where the WC_Session variable is set:
// Set the product_id and the custom price in WC_Session variable
WC()->session->set('custom_p', [
'id' => (int) wc_clean($_GET['add-to-cart']),
'price' => (float) wc_clean($_GET['custom_p']),
]);

Woocommerce Subscriptions - Remove the Tax from signup fee

I need to remove the tax from the signup fee, I can't see a way of having a tax on the subscription and not the signup fee.
I've tried using the filter: woocommerce_subscriptions_product_price_string. I can only manage to turn on or off the signup fee.
How can I remove tax from the signup fee?
This is the right hook to use, but it's not easy to get it working for that. So Here it's a custom function hooked in woocommerce_subscriptions_product_price_string that will replace the displayed signup fee price "including taxes" by the signup fee price "excluding taxes".
I have separated in a function that you can reuse, the prices signup fee. It will return an array of prices (the price with / without taxes and the tax ).
Here is that code:
function get_price_sign_up_fee( $product ){
$price_incl_tax = wcs_get_price_including_tax( $product, array( 'price' => WC_Subscriptions_Product::get_sign_up_fee( $product ) ) );
$price_excl_tax = wcs_get_price_excluding_tax( $product, array( 'price' => WC_Subscriptions_Product::get_sign_up_fee( $product ) ) );
$price_tax = $price_incl_tax - $price_excl_tax;
return array( 'incl_tax' => $price_incl_tax, 'excl_tax' => $price_excl_tax, 'tax' => $price_tax );
}
add_filter( 'woocommerce_subscriptions_product_price_string', 'subscr_product_price_string', 10, 3 );
function subscr_product_price_string( $subscription_string, $product, $include ){
//print_pr($subscription_string);
if ( $include['sign_up_fee'] && WC_Subscriptions_Product::get_sign_up_fee( $product ) > 0 ) {
$sign_up_fee = get_price_sign_up_fee( $product );
// Replacing price to excluding taxes one
$subscription_string = str_replace( wc_price($sign_up_fee['incl_tax']), wc_price($sign_up_fee['excl_tax']), $subscription_string );
}
return $subscription_string;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works.
But in cart items the signup fee remain with taxes… but this is another thing (or another question)… So you will need to find the way to make changes on it too…

Add custom tax value at woocommerce checkout

I want to add custom % value at woocommerce checkout page, but I want to show it only for Swiss country and hide it for others. Now I have the right code working for that, but the problems is that Im not able to show it when user choose switzerland. Here is a code so please help me see what Im doing wrong here
//Add tax for CH country
add_action( 'woocommerce_cart_calculate_fees','woocommerce_custom_surcharge' );
function woocommerce_custom_surcharge() {
global $woocommerce;
if ( WC()->customer->get_shipping_country('CH') )
return;
$percentage = 0.08;
$taxes = array_sum($woocommerce->cart->taxes);
$surcharge = ( $woocommerce->cart->cart_contents_total + $woocommerce->cart->shipping_total ) * $percentage;
// Make sure that you return false here. We can't double tax people!
$woocommerce->cart->add_fee( 'TAX', $surcharge, false, '' );
}
Im sure Im doing wrong inside here:
if ( WC()->customer->get_shipping_country('CH') )
thanks for help
The WC_Customer get_shipping_country() doesn't accept any country code as you are getting a country code. So you need to set it differently in your code condition.
Also as your hooked function has already the WC_Cart object as argument, you don't need global $woocommerce and $woocommerce->cart…
So your revisited code should be:
// Add tax for Swiss country
add_action( 'woocommerce_cart_calculate_fees','custom_tax_surcharge_for_swiss', 10, 1 );
function custom_tax_surcharge_for_swiss( $cart ) {
if ( is_admin() && ! defined('DOING_AJAX') ) return;
// Only for Swiss country (if not we exit)
if ( 'CH' != WC()->customer->get_shipping_country() ) return;
$percent = 8;
# $taxes = array_sum( $cart->taxes ); // <=== This is not used in your function
// Calculation
$surcharge = ( $cart->cart_contents_total + $cart->shipping_total ) * $percent / 100;
// Add the fee (tax third argument disabled: false)
$cart->add_fee( __( 'TAX', 'woocommerce')." ($percent%)", $surcharge, false );
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works… you will get something like:
But for taxes, you should better use default WooCommerce tax feature in Settings > Tax (tab), where con can set the tax rates for each country…

Categories