I want to enable or disable Shipping Methods, according to user role.
I have tested various different codes and approaches, none of which have worked so far.
The current code: (source: stacklink )
add_filter( 'woocommerce_package_rates', 'hide_specific_shipping_method_based_on_user_role', 30, 2 );
function hide_shipping_method_based_on_user_role( $rates, $package ) {
$shipping_id = 'shipmondo:3';
foreach( $rates as $rate_key => $rate ){
if( $rate->method_id === $shipping_id ){
if( current_user_can( 'b2b' ) || ! is_user_logged_in()){
unset($rates[$rate_key]);
break;
}
}
}
return $rates;
}
(Have tested the original snippet as well, without changes, accept for the user role )
Does not work for me. I tested it with 'local_pickup' as well, It does work sometimes, but seems to be very sensitive to browser cache and session. Also what I need, is 3 methods which are called under the same name, but with a child number separating them: shipmondo:3, shipmondo:4, etc. (found in browser inspection under "Value") There is also something called ID, which looks like: 'shipping_method_0_shipmondo3' Dont know if I could use that instead, but it is kind of hard to figure out, when the code isn't updating correctly. The snippet is from 2018, so it could be an outdated thing, but the newer snippets I have found, are based on the same principale, and does not look to be doing it much different.
Also, why would the "|| ! is_user_logged_in()" be necessary? I only need to disable 2 out of 3 methods for a wholesale user, need not to effect any other roles, nor guests. Been fighting with this for days now.
Also, any advice on forcing Wordpress and Woocommerce to update, and not swim around in cache?
Thanks in advance.
You are confusing shipping method "Method Id" and shipping method "Rate Id". Also your code can be simplified. Try the following instead:
add_filter( 'woocommerce_package_rates', 'hide_specific_shipping_method_based_on_user_role', 100, 2 );
function hide_specific_shipping_method_based_on_user_role( $rates, $package ) {
// Here define the shipping rate ID to hide
$targeted_rate_id = 'shipmondo:3'; // The shipping rate ID to hide
$targeted_user_roles = array('b2b'); // The user roles to target (array)
$current_user = wp_get_current_user();
$matched_roles = array_intersect($targeted_user_roles, $current_user->roles);
if( ! empty($matched_roles) && isset($rates[$targeted_rate_id]) ) {
unset($rates[$targeted_rate_id]);
}
return $rates;
}
Code goes in functions.php file of the active child theme (or active theme). It should work.
Don't forget to empty your cart, to clear shipping cached data.
Related
I have been looking at lots of snippets to enable a shipping method for a specific user role only. So anyone who is NOT part of this user role should not see it, including guests who are not logged in.
The snippets like below don't achieve this and I am struggling to reverse the logic. I want to avoid having to manually input every shipping method to the snippet, which is obviously not future proof due to when new shipping methods are added.
add_filter( 'woocommerce_package_rates', 'hide_specific_shipping_method_based_on_user_role', 100, 2 );
function hide_specific_shipping_method_based_on_user_role( $rates, $package ) {
// Here define the shipping rate ID to hide
$targeted_rate_id = '15'; // The shipping rate ID to hide
$targeted_user_roles = array('clubadmin'); // The user roles to target (array)
$current_user = wp_get_current_user();
$matched_roles = array_intersect($targeted_user_roles, $current_user->roles);
if( ! empty($matched_roles) && isset($rates[$targeted_rate_id]) ) {
unset($rates[$targeted_rate_id]);
}
return $rates;
}
Original Post: Woocommerce Shipping method based on user role
Author: LoicTheAztec
By default, this shipping method should be disabled, unless the user fulfills a certain user role
In the $rate_ids array you add 1 or more IDs for the respective shipping methods
So you get:
function filter_woocommerce_package_rates( $rates, $package ) {
// Set the rate IDs in the array
$rate_ids = array( 'local_pickup:1', 'free_shipping:2' );
// NOT the required user role, remove shipping method(s)
if ( ! current_user_can( 'administrator' ) ) {
// Loop trough
foreach ( $rates as $rate_id => $rate ) {
// Checks if a value exists in an array
if ( in_array( $rate_id, $rate_ids ) ) {
unset( $rates[$rate_id] );
}
}
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'filter_woocommerce_package_rates', 10, 2 );
Note: to find the correct $rate_ids you can use the second part from this answer - (The "for debugging purposes" part)
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.
I need to hide a shipping method depending on if a discount code is used or not.
But I don't find how to hide a shipping method in the order confirmation page.
I saw that answer but it does nothing, I tried this code to verify:
add_filter( 'woocommerce_package_rates', 'custom_shipping_rates', 100, 2 );
function custom_shipping_rates( $rates, $package ) {
exit();
}
Which should block page display but the page is well loaded.
EDIT: The filter is never called because wordpress uses stored rates in class-wc-shipping.php:
if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) )
{
...
// Filter the calculated rates.
$package['rates'] = apply_filters( 'woocommerce_package_rates',
$package['rates'], $package );
...
}
else
{
$package['rates'] = $stored_rates['rates'];
}
That code should actually work unless you have an old wc version. You can verify if that hook exist by searching for it in /plugins/woocommerce code base. If you don't find it, then something is wrong with the updates.
If you find it and your code still doesn't work, then the only other reason is that you placed that snipped of code in a point that never get fired or you simple come too late.
Could you please add where and how you placed that code so that we can see better what is going on?
Edit:
if you look above the snipped you copied you will see:
// Get rates stored in the WC session data for this package.
$wc_session_key = 'shipping_for_package_' . $package_key ;
$stored_rates = WC()->session->get( $wc_session_key );
which makes me think that is a session-related thing. So in order to fully test you need to either open the website with another browser, empty your browser cache + cookie or use an anonymous browser session
There's a trick to disable the usage of cache, I limited it to the "update order review" action that way:
if (preg_match('#update_order_review#i',$_SERVER['REQUEST_URI']))
{
add_filter('transient_shipping-transient-version', function($value, $name)
{
return false;
}, 10, 2);
}
add_filter( 'woocommerce_package_rates', 'custom_shipping_rates', 100, 2 );
function custom_shipping_rates( $rates, $package )
{
//get coupons
$reduc=WC()->cart->get_coupons();
$hidemr=isset($reduc['thediscountcode']);
if ($hidemr)
{
foreach($rates as $k=>$v)
{
if (preg_match('#MethodLabelToHide#i',$v->get_label()))
unset($rates[$k]);
}
}
return $rates;
}
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_%')" );
}