On Woocommerce, I have enabled 2 shipping methods: Free shipping or Flat rate. I have enabled 2 payment methods: Bank transfer (bacs) and PayPal (paypal).
What I want to achieve:
If a customer selects PayPal as payment type he should be forced to select "Flat rate" as shipping method. "Free shipping" should be either hidden or greyed out or something like that.
If bank transfer is chosen then both shipping methods should be available.
Any help is appreciated.
If anyone is interested, I found a solution:
function alter_payment_gateways( $list ){
// Retrieve chosen shipping options from all possible packages
$chosen_rates = ( isset( WC()->session ) ) ? WC()->session->get( 'chosen_shipping_methods' ) : array();
if( in_array( 'free_shipping:1', $chosen_rates ) ) {
$array_diff = array('WC_Gateway_Paypal');
$list = array_diff( $list, $array_diff );
}
return $list;
}
add_action('woocommerce_payment_gateways', 'alter_payment_gateways');
This code will deactivate PayPal if a customer selects free shipping.
Update 2: The following code will disable "free_shipping" shipping method (method ID) when "paypal" is the chosen payment method:
add_filter( 'woocommerce_package_rates', 'shipping_methods_based_on_chosen_payment', 100, 2 );
function shipping_methods_based_on_chosen_payment( $rates, $package ) {
// Checking if "paypal" is the chosen payment method
if ( WC()->session->get( 'chosen_payment_method' ) === 'paypal' ) {
// Loop through shipping methods rates
foreach( $rates as $rate_key => $rate ){
if ( 'free_shipping' === $rate->method_id ) {
unset($rates[$rate_key]); // Remove 'Free shipping'shipping method
}
}
}
return $rates;
}
// Enabling, disabling and refreshing session shipping methods data
add_action( 'woocommerce_checkout_update_order_review', 'refresh_shipping_methods', 10, 1 );
function refresh_shipping_methods( $post_data ){
$bool = true;
if ( WC()->session->get('chosen_payment_method' ) ) $bool = false;
// Mandatory to make it work with shipping methods
foreach ( WC()->cart->get_shipping_packages() as $package_key => $package ){
WC()->session->set( 'shipping_for_package_' . $package_key, $bool );
}
WC()->cart->calculate_shipping();
}
// Jquery script for checkout page
add_action('wp_footer', 'refresh_checkout_on_payment_method_change' );
function refresh_checkout_on_payment_method_change() {
// Only checkout page
if( is_checkout() && ! is_wc_endpoint_url() ):
?>
<script type="text/javascript">
jQuery(function($){
// On shipping method change
$('form.checkout').on( 'change', 'input[name^="payment_method"]', function(){
$('body').trigger('update_checkout'); // Trigger Ajax checkout refresh
});
})
</script>
<?php
endif;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
To get the related shipping methods rate IDs, something like flat_rate:12, inspect with your browser code inspector each related radio button attribute name like:
Note: Since WooCommerce new versions changes, sorry, the code doesn't work anymore.
Related
I need to disable specific shipping method if user selected payment "Cash on Delivery". The problem is that the following code works only if I reset
WooCommerce transients each time and refresh. It doesn't work on user selection back and forth.
add_filter( 'woocommerce_package_rates', 'alter_shipping_methods', 100 );
function alter_shipping_methods( $rates ) {
$chosen_gateway = WC()->session->chosen_payment_method;
// If payment is Cash on delivery remove specific shipping
if($chosen_gateway == 'cod') {
foreach ( $rates as $rate_id => $rate ) {
if ( $rate->label === 'Hrvatska pošta' ) {
unset( $rates[ $rate_id ] );
}
}
}
return $rates;
}
I do have this code which should trigger and I see the output in console when I click around options.
jQuery(document.body).on('change', 'input[name="payment_method"]', function() {
console.log('Payment method changed');
jQuery('body').trigger('update_checkout');
});
I have tried with this, it doesn't work:
function action_woocommerce_checkout_update_order_review($array, $int) {
WC()->cart->calculate_shipping();
return;
}
add_action('woocommerce_checkout_update_order_review', 'action_woocommerce_checkout_update_order_review', 10, 2);
And I have also tried custom AJAX call which calls a PHP function and inside this filter, no result:
add_filter( 'woocommerce_package_rates', 'alter_shipping_methods', 100 );
What should I try next?
Updated on March 2019
For COD payment gateways, you can just add in its settings the "Flat rate" shipping methods that you want to enable for it, like:
For Cod and other methods or for others payment gateways, here is the complete working way to disable a specific shipping method(s) for specific payment gateway(s).
You will have to set in the first function the shipping method Id that you wish to hide.
The code:
add_action( 'woocommerce_package_rates','show_hide_shipping_methods', 10, 2 );
function show_hide_shipping_methods( $rates, $package ) {
// HERE Define your targeted shipping method ID
$payment_method = 'cod';
$chosen_payment_method = WC()->session->get('chosen_payment_method');
if( $payment_method == $chosen_payment_method ){
unset($rates['flat_rate:12']);
}
return $rates;
}
add_action( 'woocommerce_review_order_before_payment', 'payment_methods_trigger_update_checkout' );
function payment_methods_trigger_update_checkout(){
// jQuery code
?>
<script type="text/javascript">
(function($){
$( 'form.checkout' ).on( 'change blur', 'input[name^="payment_method"]', function() {
setTimeout(function(){
$(document.body).trigger('update_checkout');
}, 250 );
});
})(jQuery);
</script>
<?php
}
add_action( 'woocommerce_checkout_update_order_review', 'refresh_shipping_methods' );
function refresh_shipping_methods( $post_data ){
// HERE Define your targeted shipping method ID
$payment_method = 'cod';
$bool = true;
if ( WC()->session->get('chosen_payment_method') === $payment_method )
$bool = false;
// Mandatory to make it work with shipping methods
foreach ( WC()->cart->get_shipping_packages() as $package_key => $package ){
WC()->session->set( 'shipping_for_package_' . $package_key, $bool );
}
WC()->cart->calculate_shipping();
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
To be able to get the correct shipping method ID you can use your browser inspector, this way:
You may need to empty cart before testing this code.
I need to disable specific shipping method if user selected payment "Cash on Delivery". The problem is that the following code works only if I reset
WooCommerce transients each time and refresh. It doesn't work on user selection back and forth.
add_filter( 'woocommerce_package_rates', 'alter_shipping_methods', 100 );
function alter_shipping_methods( $rates ) {
$chosen_gateway = WC()->session->chosen_payment_method;
// If payment is Cash on delivery remove specific shipping
if($chosen_gateway == 'cod') {
foreach ( $rates as $rate_id => $rate ) {
if ( $rate->label === 'Hrvatska pošta' ) {
unset( $rates[ $rate_id ] );
}
}
}
return $rates;
}
I do have this code which should trigger and I see the output in console when I click around options.
jQuery(document.body).on('change', 'input[name="payment_method"]', function() {
console.log('Payment method changed');
jQuery('body').trigger('update_checkout');
});
I have tried with this, it doesn't work:
function action_woocommerce_checkout_update_order_review($array, $int) {
WC()->cart->calculate_shipping();
return;
}
add_action('woocommerce_checkout_update_order_review', 'action_woocommerce_checkout_update_order_review', 10, 2);
And I have also tried custom AJAX call which calls a PHP function and inside this filter, no result:
add_filter( 'woocommerce_package_rates', 'alter_shipping_methods', 100 );
What should I try next?
Updated on March 2019
For COD payment gateways, you can just add in its settings the "Flat rate" shipping methods that you want to enable for it, like:
For Cod and other methods or for others payment gateways, here is the complete working way to disable a specific shipping method(s) for specific payment gateway(s).
You will have to set in the first function the shipping method Id that you wish to hide.
The code:
add_action( 'woocommerce_package_rates','show_hide_shipping_methods', 10, 2 );
function show_hide_shipping_methods( $rates, $package ) {
// HERE Define your targeted shipping method ID
$payment_method = 'cod';
$chosen_payment_method = WC()->session->get('chosen_payment_method');
if( $payment_method == $chosen_payment_method ){
unset($rates['flat_rate:12']);
}
return $rates;
}
add_action( 'woocommerce_review_order_before_payment', 'payment_methods_trigger_update_checkout' );
function payment_methods_trigger_update_checkout(){
// jQuery code
?>
<script type="text/javascript">
(function($){
$( 'form.checkout' ).on( 'change blur', 'input[name^="payment_method"]', function() {
setTimeout(function(){
$(document.body).trigger('update_checkout');
}, 250 );
});
})(jQuery);
</script>
<?php
}
add_action( 'woocommerce_checkout_update_order_review', 'refresh_shipping_methods' );
function refresh_shipping_methods( $post_data ){
// HERE Define your targeted shipping method ID
$payment_method = 'cod';
$bool = true;
if ( WC()->session->get('chosen_payment_method') === $payment_method )
$bool = false;
// Mandatory to make it work with shipping methods
foreach ( WC()->cart->get_shipping_packages() as $package_key => $package ){
WC()->session->set( 'shipping_for_package_' . $package_key, $bool );
}
WC()->cart->calculate_shipping();
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
To be able to get the correct shipping method ID you can use your browser inspector, this way:
You may need to empty cart before testing this code.
Essentially I'm trying to make the flat rate method Id flat_rate:7 disabled when there is cart items that have the shipping class "Roller" (ID 92).
This is the code I tried:
add_filter('woocommerce_package_rates', 'wf_hide_shipping_method_based_on_shipping_class', 10, 2);
function wf_hide_shipping_method_based_on_shipping_class($available_shipping_methods, $package)
{
$hide_when_shipping_class_exist = array(
92 => array(
'flat_rate:7'
)
);
$shipping_class_in_cart = array();
foreach(WC()->cart->cart_contents as $key => $values) {
$shipping_class_in_cart[] = $values['data']->get_shipping_class_id();
}
foreach($hide_when_shipping_class_exist as $class_id => $methods) {
if(in_array($class_id, $shipping_class_in_cart)){
foreach($methods as & $current_method) {
unset($available_shipping_methods[$current_method]);
}
}
}
return $available_shipping_methods;
}
Shipping class ID 92 is the shipping class and I want to hide flat_rate:7 for it.
My Site is this: http://www.minimoto.me/
WordPress: 4.8.4
WooCommerce: 3.1.1
Any help will be greatly appreciated.
Update 2019: You should try instead this shorter, compact and effective way:
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// HERE define your shipping class to find
$class = 92;
// HERE define the shipping method to hide
$method_key_id = 'flat_rate:7';
// Checking in cart items
foreach( $package['contents'] as $item ){
// If we find the shipping class
if( $item['data']->get_shipping_class_id() == $class ){
unset($rates[$method_key_id]); // Remove the targeted method
break; // Stop the loop
}
}
return $rates;
}
Code goes in function.php file of your active child theme (or theme) or also in any plugin file.
Tested and works.
Sometimes, you should may be need to refresh shipping methods going to shipping areas, then disable / save and re-enable / save your "flat rates" shipping methods.
Related: Hide shipping methods for specific shipping classes in WooCommerce
To find the shipping methods IDs and the shipping classes IDs see below…
Update for many different shipping methods (related to your comments):
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// HERE define your shipping class to find
$class = 92;
// HERE define the shipping methods you want to hide
$method_key_ids = array('flat_rate:7', 'local_pickup:3');
// Checking in cart items
foreach( $package['contents'] as $item ) {
// If we find the shipping class
if( $item['data']->get_shipping_class_id() == $class ){
foreach( $method_key_ids as $method_key_id ){
unset($rates[$method_key_id]); // Remove the targeted methods
}
break; // Stop the loop
}
}
return $rates;
}
Tested and works…
Finding the shipping class ID.
In the database under wp_terms table:
Search for a term name or a term slug and you will get the term ID (the shipping class ID).
On Woocommerce shipping settings editing a "Flat rate", with your browser html inspector tool, inspect a shipping Class rate field like:
In the imput name attribute you have woocommerce_flat_rate_class_cost_64. So 64 is the ID for the shipping class.
Get the shipping method rate ID:
To get the related shipping methods rate IDs, something like flat_rate:12, inspect with your browser code inspector each related radio button attribute value like:
By tweaking LoicTheAztec's code (cheers), I was able to unset a shipping method for each package based on the shipping class of its contents, rather than the cart as a whole. Perhaps it will help someone else too:
// UNSET A SHIPPING METHOD FOR PACKAGE BASED ON THE SHIPPING CLASS(es) OF ITS CONTENTS
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
foreach( $package['contents'] as $package_item ){ // Look at the shipping class of each item in package
$product_id = $package_item['product_id']; // Grab product_id
$_product = wc_get_product( $product_id ); // Get product info using that id
if( $_product->get_shipping_class_id() != 371 ){ // If we DON'T find this shipping class ID
unset($rates['wbs:9:dae98e94_free_ups_ground']); // Then remove this shipping method
break; // Stop the loop, since we've already removed the shipping method from this package
}
}
return $rates;
}
This code allows me to unset my 'Free UPS Ground' shipping if the package contains anything but 'Standard' items (shipping_class_id 371 in my case).
The scenario from the original post (disable method x if shipping class y) would work like this:
// UNSET A SHIPPING METHOD FOR PACKAGE BASED ON THE SHIPPING CLASS(es) OF ITS CONTENTS
add_filter( 'woocommerce_package_rates', 'hide_shipping_method_based_on_shipping_class', 10, 2 );
function hide_shipping_method_based_on_shipping_class( $rates, $package )
{
if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
return;
}
foreach( $package['contents'] as $package_item ){ // Look at the shipping class of each item in package
$product_id = $package_item['product_id']; // Grab product_id
$_product = wc_get_product( $product_id ); // Get product info using that id
if( $_product->get_shipping_class_id() == 92 ){ // If we DO find this shipping class ID
unset($rates['flat_rate:7']); // Then remove this shipping method
break; // Stop the loop, since we've already removed the shipping method from this package
}
}
return $rates;
}
How to disable BACS payment method for local delivery shipping method?
I have included the code below to my functions.php file, but it doesn't work.
Maybe someone could help me with this.
function my_custom_available_payment_gateways( $gateways ) {
$chosen_shipping_rates = WC()->session->get( 'chosen_shipping_methods' );
// When 'local delivery' has been chosen as shipping rate
if ( in_array( 'local_delivery', $chosen_shipping_rates ) ) :
// Remove bank transfer payment gateway
unset( $gateways['bacs'] );
endif;
return $gateways;
}
add_filter( 'woocommerce_available_payment_gateways', 'my_custom_available_payment_gateways' );
You are not far. To make your code working you need to manipulate the data in the array of chosen shipping methods to get only the slugs in a foreach loop.
Here is the code:
add_filter( 'woocommerce_available_payment_gateways', 'unset_bacs_for_local_delivery' );
function unset_bacs_for_local_delivery( $gateways ) {
// Not in backend (admin)
if( is_admin() )
return $gateways;
// Initialising variables
$chosen_shipping_method_ids = array();
$chosen_shipping_methods = (array) WC()->session->get( 'chosen_shipping_methods' );
// Iterating and manipulating the "chosen shipping methods" to get the SLUG
foreach( $chosen_hipping_methods as $shipping_method_rate_id ) :
$shipping_method_array = explode(':', $shipping_method_rate_id);
$chosen_shipping_method_ids[] = $shipping_method_array[0];
endforeach;
//When 'local delivery' has been chosen as shipping method
if ( in_array( 'local_delivery', $chosen_shipping_method_ids ) ) :
// Remove bank transfer payment gateway
unset( $gateways['bacs'] );
endif;
return $gateways;
}
This code is tested and is fully functional.
Code goes in functions.php file of your active child theme (or theme). Or also in any plugin php files.
In WooCommerce I need to apply a custom handling fee for a specific payment gateway. I have this piece of code from here: How to Add Handling Fee to WooCommerce Checkout.
This is my code:
add_action( 'woocommerce_cart_calculate_fees','endo_handling_fee' );
function endo_handling_fee() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$fee = 5.00;
$woocommerce->cart->add_fee( 'Handling', $fee, true, 'standard' );
}
This function add a fee to all transactions.
Is it possible to tweak this function and make it apply for specific payment method only ?
The other problem is that I want this fee to be applied on cart. Is it possible?
I will welcome any alternative method as well. I know about the similar "Payment Gateway Based Fees" woo plugin, but I can't afford it.
2021 UPDATE
Note: All payment methods are only available on Checkout page.
The following code will add conditionally a specific fee based on the chosen payment method:
// Add a custom fee (fixed or based cart subtotal percentage) by payment
add_action( 'woocommerce_cart_calculate_fees', 'custom_handling_fee' );
function custom_handling_fee ( $cart ) {
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
$chosen_payment_id = WC()->session->get('chosen_payment_method');
if ( empty( $chosen_payment_id ) )
return;
$subtotal = $cart->subtotal;
// SETTINGS: Here set in the array the (payment Id) / (fee cost) pairs
$targeted_payment_ids = array(
'cod' => 8, // Fixed fee
'paypal' => 5 * $subtotal / 100, // Percentage fee
);
// Loop through defined payment Ids array
foreach ( $targeted_payment_ids as $payment_id => $fee_cost ) {
if ( $chosen_payment_id === $payment_id ) {
$cart->add_fee( __('Handling fee', 'woocommerce'), $fee_cost, true );
}
}
}
You will need the following to refresh checkout on payment method change, to get it work:
// jQuery - Update checkout on payment method change
add_action( 'woocommerce_checkout_init', 'payment_methods_refresh_checkout' );
function payment_methods_refresh_checkout() {
wc_enqueue_js( "jQuery( function($){
$('form.checkout').on('change', 'input[name=payment_method]', function(){
$(document.body).trigger('update_checkout');
});
});");
}
Code goes in functions.php file of your active child theme (or active theme). tested and works.
How to find a specific payment method ID in WooCommerce Checkout page?
The following will display on checkout payment methods the payment Id just for admins:
add_filter( 'woocommerce_gateway_title', 'display_payment_method_id_for_admins_on_checkout', 100, 2 );
function display_payment_method_id_for_admins_on_checkout( $title, $payment_id ){
if( is_checkout() && ( current_user_can( 'administrator') || current_user_can( 'shop_manager') ) ) {
$title .= ' <code style="border:solid 1px #ccc;padding:2px 5px;color:red;">' . $payment_id . '</code>';
}
return $title;
}
Code goes in functions.php file of your active child theme (or active theme). Once used, remove it.
Similar answer:
Add a custom fee for a specific payment gateway in Woocommerce
Add a fee based on shipping method and payment method in Woocommerce
Percentage discount based on user role and payment method in Woocommerce
First of all there are some key things we need to understand:
We are going to use the only filter hook which is woocommerce_cart_calculate_fees
In order to get user selected payment method we must retrieve it from user sessions using this method WC()->session->get( 'chosen_payment_method' )
calculate_fees() and calculate_totals() methods are not necessary.
We are going to add the fee using cart method WC()->cart->add_fee() which accepts two parameters – the first one, is the fee description, the second one – fee amount.
And one more thing – we will need a payment method slug, the easiest way to get it described in this tutorial https://rudrastyh.com/woocommerce/add-id-column-to-payment-methods-table.html
Let's go now:
add_action( 'woocommerce_cart_calculate_fees', 'rudr_paypal_fee', 25 );
function rudr_paypal_fee() {
if( 'paypal' == WC()->session->get( 'chosen_payment_method' ) ) {
WC()->cart->add_fee( 'PayPal fee', 2 ); // let's add a fee for paypal
}
}
It would be also great to refresh the checkout every time payment gateway is changed, it is easy to do using this code:
jQuery( function( $ ) {
$( 'form.checkout' ).on( 'change', 'input[name^="payment_method"]', function() {
$( 'body' ).trigger( 'update_checkout' );
});
});
That's it. Everything is described in details here as well: https://rudrastyh.com/woocommerce/charge-additional-fees-based-on-payment-gateway.html
For anyone else looking to do this, I wanted to add a fee for Bank Transfers (BACS), here is the method I used:
//Hook the order creation since it is called during the checkout process:
add_filter('woocommerce_create_order', 'my_handle_bacs', 10, 2);
function my_handle_bacs($order_id, $checkout){
//Get the payment method from the $_POST
$payment_method = isset( $_POST['payment_method'] ) ? wc_clean( $_POST['payment_method'] ) : '';
//Make sure it's the right payment method
if($payment_method == "bacs"){
//Use the cart API to add recalculate fees and totals, and hook the action to add our fee
add_action('woocommerce_cart_calculate_fees', 'my_add_bacs_fee');
WC()->cart->calculate_fees();
WC()->cart->calculate_totals();
}
//This filter is for creating your own orders, we don't want to do that so return the $order_id untouched
return $order_id;
}
function my_add_bacs_fee($cart){
//Add the appropriate fee to the cart
$cart->add_fee("Bank Transfer Fee", 40);
}
add_action( 'woocommerce_cart_calculate_fees','cod_fee' );
function cod_fee() {
global $woocommerce;
if ( is_admin() && ! defined( 'DOING_AJAX' ) )
return;
// get your payment method
$chosen_gateway = WC()->session->chosen_payment_method;
//echo $chosen_gateway;
$fee = 5;
if ( $chosen_gateway == 'cod' ) { //test with cash on delivery method
WC()->cart->add_fee( 'delivery fee', $fee, false, '' );
}
}