i just implemented a custom shipping solution.
It depends on the total price of items in the cart. For example:
if total is < 20 -> display free shipping
if total is >= 20 -> paid delivery
But i have a problem with woocommerce cache...I think that the fact is that woocommerce caches shipping rates, not taking in account order qty change.
So is the problem is with the calculate_shipping_for_package() method?
If I enable shipping debug mode everything works just right, without it got no updates.
I tried to disable cache, without success with.
add_action('woocommerce_checkout_update_order_review', function() {
$packages = WC()->cart->get_shipping_packages();
foreach ($packages as $key => $value) {
$shipping_session = "shipping_for_package_$key";
unset(WC()->session->$shipping_session);
}
}, 10, 2);
So. Do you ever had this kind of problem? How did you solve? Thanks a lot for any help!
Updated: There is some mistakes in your code, instead try the following:
add_action('woocommerce_checkout_update_order_review', 'checkout_update_refresh_shipping_methods', 10, 1);
function checkout_update_refresh_shipping_methods( $post_data ) {
$packages = WC()->cart->get_shipping_packages();
foreach ($packages as $package_key => $package ) {
WC()->session->set( 'shipping_for_package_' . $package_key, false ); // Or true
}
}
Code goes in function.php file of your active child theme (active theme). Tested and works.
But it will refresh shipping methods cache each time on ajax checkout update event.
Related: Custom checkout field and shipping methods ajax interaction in Woocommerce 3
Related
I am trying to remove the free shipping option in Woocommerce when someone uses a specific coupon code. I found this question which is very relevant to my question. The answer bellow seems really close to what I am looking for. I am new to php and trying to figure out where I would add an id for the coupon code I want to exclude.
add_filter( 'woocommerce_shipping_packages', function( $packages ) {
$applied_coupons = WC()->session->get('applied_coupons', array());
if (!empty($applied_coupons)) {
$free_shipping_id = 'free_shipping:2';
unset($packages[0]['rates'][ $free_shipping_id ]);
}
return $packages;
});
This is my first question on stack over flow but this site has helped me so much with so many issues. Forgive me if I am asking too much. Thanks in advance for any help/guidance.
Use instead woocommerce_package_rates filter hook. In the code below you will set the related coupon codes that will hide the free shipping method:
add_filter( 'woocommerce_package_rates', 'hide_free_shipping_method_based_on_coupons', 10, 2 );
function hide_free_shipping_method_based_on_coupons( $rates, $package )
{
$coupon_codes = array('summer'); // <== HERE set your coupon codes
$applied_coupons = WC()->cart->get_applied_coupons(); // Applied coupons
if( empty($applied_coupons) )
return $rates;
// For specific applied coupon codes
if( array_intersect($coupon_codes, $applied_coupons) ) {
foreach ( $rates as $rate_key => $rate ) {
// Targetting "Free shipping"
if ( 'free_shipping' === $rate->method_id ) {
unset($rates[$rate_key]); // hide free shipping method
}
}
}
return $rates;
}
Code goes in functions.php file of your active child theme (or active theme). Tested and works.
Clearing shipping caches:
You will need to empty your cart, to clear cached shipping data
Or In shipping settings, you can disable / save any shipping method, then enable back / save.
On my WooCommerce site I use this function to add a 4€ fee if you select cash on delivery as payment method:
add_action('woocommerce_cart_calculate_fees', 'increase_cod_cost');
function increase_cod_cost() {
if(WC()->session->chosen_payment_method=='cod')
WC()->cart->add_fee(__('COD Fee'), 4);
}
I also use this function to immediately update the checkout whenever you change payment method so that the fee is added when you select cod, and removed when you select any other method:
add_action('woocommerce_review_order_before_payment', 'custom_checkout_update');
function custom_checkout_update() {
echo '
<script type="text/javascript">
(function($){
$(\'form.checkout\').on(\'change\', \'input[name="payment_method"]\', function() {
$(\'body\').trigger(\'update_checkout\');
});
})(jQuery);
</script>
';
}
Both functions work 100%.
Now, Instead of adding a fee, I'd like to properly increase the actual shipping cost, so I've tried this function instead of the first one:
add_filter('woocommerce_package_rates', 'increase_cod_cost_2', 10, 2);
function increase_cod_cost_2($rates, $package) {
if(WC()->session->chosen_payment_method=='cod')
$rates['flat_rate:1']->cost=$rates['flat_rate:1']->cost+4;
return $rates;
}
This function also works, but only if I empty the cart and then add a product again. For some reason it doesn't immediately update the shipping cost whenever I select cod. I really don't understand why the jQuery function would work with the first php function by adding the fee, but not with this one by changing the shipping cost. Can you please help me and tell me what's wrong? Thank you.
Please try this.
add_filter( 'woocommerce_package_rates', 'custom_shipping_costs', 20, 2 );
function custom_shipping_costs( $rates, $package ) {
// New shipping cost (can be calculated)
$new_cost = 1000;
$tax_rate = 0.4;
foreach( $rates as $rate_key => $rate ){
// Excluding free shipping methods
if( $rate->method_id != 'free_shipping'){
// Set rate cost
$rates[$rate_key]->cost = $new_cost;
// Set taxes rate cost (if enabled)
$taxes = array();
foreach ($rates[$rate_key]->taxes as $key => $tax){
if( $rates[$rate_key]->taxes[$key] > 0 )
$taxes[$key] = $new_cost * $tax_rate;
}
$rates[$rate_key]->taxes = $taxes;
}
}
return $rates;
}
I cannot actually help you to fully understand what is going wrong, but I can share you some experience:
Try to include log commands in these cases, it helps you to see when the hook is called, if it is called and depending on your log details if the code went through the if clause or fell into the else case
From experience and what you write, the second hook is not always called, therefore it works when you freshly add a product but not during an shipping method change during checkout.
So I am wildly guessing you probably need another hook like
do_action( 'woocommerce_shipping_method_chosen', $chosen_method );
or something better which triggers whenever woocommerce goes to look for the available shipping methods.
Be careful that the hooks are not called sequentially and you add 4 USD more han 1 time...
PD: I've noticed that the suggested hook is not ideal neither, you could try one of these:
add_action( 'woocommerce_load_shipping_methods', 'action_woocommerce_load_shipping_methods', 10, 1 );
add_filter( 'woocommerce_package_rates', 'woocommerce_package_rates' );
or else check here all the woocommerce hooks:
https://docs.woocommerce.com/wc-apidocs/hook-docs.html
I have a woocommerce website and I have set 2 shipping methods:
Flat Rate
Local pickup
I would like to set the "Flat rate" shipping method as default (selected) in the cart or checkout page.
1) You can use the following code (to set "flat rate" shipping method as default) In cart page:
add_action( 'woocommerce_before_cart', 'set_default_chosen_shipping_method', 5 );
function set_default_chosen_shipping_method(){
//
if( count( WC()->session->get('shipping_for_package_0')['rates'] ) > 0 ){
foreach( WC()->session->get('shipping_for_package_0')['rates'] as $rate_id =>$rate)
if($rate->method_id == 'flat_rate'){
$default_rate_id = array( $rate_id );
break;
}
WC()->session->set('chosen_shipping_methods', $default_rate_id );
}
}
Code goes in function.php file of your active child theme (active theme or in any plugin file).
Tested and Works in WooCommerce 3+
2) You can also reorder the shipping rates in your shipping zones settings (but it doesn't really works as the last chosen shipping method take the hand).
You could use the following code to set 'any' shipping method as default.
function reset_default_shipping_method( $method, $available_methods ) {
$default_method = 'wf_fedex_woocommerce_shipping:FEDEX_GROUND'; //provide the service name here
if( array_key_exists($method, $available_methods ) )
return $default_method;
else
return $method;
}
Let's say, you're using a Carrier shipping plugin like WooCommerce FedEx Shipping Plugin. You can fetch the value Id (shown below) and paste it under the '$default_method' in the above code.
You will have to copy and paste the code in WordPress Dashboard->Appearance–>Editor–>functions.php of your theme.
Hope that helped. :)
copy the value Id from here
I did not want to post here but I could not find the answer I was looking for and I do not have enough reputation to comment on other VERY SIMILAR questions to get my exact answer.
I found an almost perfect answer from this post: WooCommerce: Add product to cart with price override?
using the code:
add_action( 'woocommerce_before_calculate_totals', 'add_custom_price');
function add_custom_price( $cart_object ) {
$custom_price = 10; // This will be your custome price
foreach ( $cart_object->cart_contents as $key => $value ) {
$value['data']->price = $custom_price;
}
}
and it works great...if you set a hard coded custom price.
My question is: How can I set a custom price based on user input?
I have tried every way I can think of to pass information (I even tried using cookies, globals, sessions) and none of them worked how I wanted and all of them were, at BEST, hacks.
The specific product in question is a donation and the customer wants the user to be able to set the donation price (rather than just creating a variable product with set price points).
On the donation page when the user submits the donation form I am adding the donation product to the cart like so:
$donation_success = $woocommerce->cart->add_to_cart($donation_id);
My donation product has a set price of 0.00 so when it is added to the cart it has a price of 0.00 (I don't know if the price is set at this point or later)
I have tried passing in information at this point using the last variable in the add_to_cart method which accepts an array of arguments but I couldn't seem to get that to work either.
I am sure the answer is simple but I have been trying for hours to do this right and I cannot get it to work. I am out of ideas.
The actual code I am using at the moment is slightly different than was suggested by the answerer of the above post but works basically the same:
add_action( 'woocommerce_before_calculate_totals', 'woo_add_donation');
function woo_add_donation() {
global $woocommerce;
$donation = 10;
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if($cart_item['data']->id == 358 || $cart_item['data']->id == 360){
$cart_item['data']->set_price($donation);
}
}
}
Thanks in advance for any helpful advice!
I found a solution which is not elegant but works for my purposes.
I was trying to use cookies before but I didn't set the cookie path so it was only available to the page it was set on.
I am now setting a cookie with the donation price:
setcookie("donation", $_GET['donation'], 0, "/");
Then I am setting the price like so:
add_action( 'woocommerce_before_calculate_totals', 'woo_add_donation');
function woo_add_donation() {
global $woocommerce;
foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
if($cart_item['data']->id == 358 && ! empty($_COOKIE['donation'])){
$cart_item['data']->set_price($_COOKIE['donation']);
}
}
}
I have been looking for exactly the same thing. I found this WooCommerce plugin (not free) for this
name your price plugin
Initially I wasn't sure what search terms to use to find plugins like this but it looks like "WooCommerce name your price" brings up links to other sources of similar plugins.
[this is a comment] Where do you set the cookie? My first guess is that the refreshes in the same page, using the GET method, and provides a PHP code-block with the $_GET['donation'] to set the cookie with.
And then, once the cookie is set, you redirect the page to Woocommerce Cart page to continue the shopping process. If you're doing it easier way, please let me know. Thanks.
Sorry, I couldn't post this as a comment to the selected answer due to the lack of points.
This code will create order with custom price:
$product = wc_get_product($product_id);
$order = wc_create_order();
try {
$order->add_product($product);
//$order->set_customer_id($user->ID);
$order->set_billing_email($customer_email);
} catch (WC_Data_Exception $e) {
wp_send_json_error("Failed to create order");
}
$order->calculate_totals();
try {
$order->set_total($custom_price); // $custom_price should be float value
} catch (WC_Data_Exception $e) {
wp_send_json_error("Failed to change order details");
}
$order->save();
$order->update_status( 'completed' );
I am currently working on an online shop with WooCommerce. I faced the problem that I want to grant a discount to customers who chose a specific shipping method. The discount is 0.50 for every single product. I basically solved this problem with the following code in my "functions.php":
add_action('woocommerce_before_calculate_totals', 'woo_add_cart_fee');
function woo_add_cart_fee() {
global $woocommerce;
$cart = $woocommerce->cart->get_cart();
//Calculating Quantity
foreach ($cart as $cart_val => $cid) {
$qty += $cid['quantity'];
}
if ($woocommerce->cart->shipping_label == "specific shipping method") {
$woo_fee = $qty * (-0.5);
$woo_name = "discount for specific shipping method";
}
$woocommerce->cart->add_fee(__($woo_name, 'woocommerce'), $woo_fee, true);
}
The code technically works, the only problem I have now is that if a customer changes the shipping method i.e. from the "specific shipping method" to a "normal one" (without any discount) or the other way round, it always displays and calculates the discount value from the previously chosen shipping method. In other words it is always one step back and therefore displays the customer the wrong total amount.
Does anyone has an idea to solve this problem?
This solutions is for Woocommerce 2.1.X!
I am not sure if this might help. I was facing a similar problem, where I needed to retrieve the chosen shipping method. In the file \wp-content\plugins\woocommerce\includes\wc-cart-functions.php I found a method called wc_cart_totals_shipping_html().
Within this method there is a check of the current selected shipping method that contains the following code:
$packages = WC()->shipping->get_packages();
foreach ( $packages as $i => $package ) {
$chosen_method = isset( WC()->session->chosen_shipping_methods[ $i ] ) ? WC()->session->chosen_shipping_methods[ $i ] : '';
}
I used this code in my own functions.php to check for the currently selected shipping method and it works. Example:
add_filter( 'woocommerce_billing_fields', 'wc_change_required_fields');
function wc_change_required_fields($address_fields) {
$packages = WC()->shipping->get_packages();
foreach ( $packages as $i => $package ) {
$chosen_method = isset( WC()->session->chosen_shipping_methods[ $i ] ) ? WC()->session->chosen_shipping_methods[ $i ] : '';
}
if ($chosen_method == 'local_delivery') {
$address_fields['billing_address_1']['required'] = true;
// place your changes that depend on the shipping method here...
}
}
Hope that helps!
This is very old, but I ran into this issue myself and had to work out the solution.
Woocommerce stores pre-calculated cart totals in the database, rather than calculating them on the fly. But the shipping method choice is stored as a session variable. So shipping changes are not reflected immediately at checkout without a visit or refresh of a cart page.
With the original posted code, the shipping changes were not reflected because they aren't recalculated and stored. To do this, the function needs to be tricked into thinking it's a cart page first, and then recalculating the totals to be stored.
GLOBAL $woocommerce;
if ( ! defined('WOOCOMMERCE_CART') ) {
define( 'WOOCOMMERCE_CART', true );
}
And then at the end of the function, after all the desired changes have been made refresh and store.
WC()->cart->calculate_totals();
See also CODEX for WC_AJAX::update_shipping_method()
http://docs.woothemes.com/wc-apidocs/source-class-WC_AJAX.html#148-174
Mark's answer worked for me, however I had to delete all transient values prior to running the code. Otherwise, it would simply restore the saved values.
public function clear_shipping_transients() {
global $wpdb;
$wpdb->query( "DELETE FROM `$wpdb->options` WHERE `option_name` LIKE ('_transient_cp_quote_%') OR `option_name` LIKE ('_transient_timeout_cp_quote_%') OR `option_name` LIKE ('_transient_wc_ship_%')" );
}