I ran into an issue in WooCommerce and I'm wondering if anyone else has experienced it too.
I sell certain products that are too fragile to be shipped by UPS/DHL/FedEx. So I have to ship these products via pallet. To solve my problem I created a "request a quote" shipping method that allows my customers to select BACS as the payment method, request a quote as the shipping method and submit their orders. And after I have calculated the shipping costs, I update the order (change the shipping method to N/A) and change the status from "on-hold" to "pending payment" to allow the customer to pay by card if they wish to.
This is where I ran into the issue. I noticed that if I unset several payment gateways and if these certain shipping methods are selected, these payment gateways are not available to the customer in the "order-pay" endpoint (website/my-account/orders/) even if I remove the shipping method from the order.
Is there a way around this?
This is the code I am using to Disable Payment Gateway For Specific Shipping Method.
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways', 10, 1 );
function filter_woocommerce_available_payment_gateways( $available_gateways ) {
$gateways_to_disable = array( 'cardgatecreditcard', 'cardgategiropay', 'cardgateideal', 'cardgatesofortbanking' );
$shipping_methods = array( 'flat_rate', 'request_shipping_quote' );
$disable_gateways = false;
// Check if we need to disable gateways
foreach ( $shipping_methods as $shipping_method ) {
if ( strpos( WC()->session->get( 'chosen_shipping_methods' )[0], $shipping_method ) !== false ) $disable_gateways = true;
}
// If so, disable the gateways
if ( $disable_gateways ) {
foreach ( $available_gateways as $id => $gateway ) {
if ( in_array( $id, $gateways_to_disable ) ) {
unset( $available_gateways[$id] );
}
}
}
return $available_gateways;
}
UPDATE After consulting with a few developers they advised that this code runs every time Payment Gateways are required and suggested that I run this snippet only on the Checkout page.
They suggested adding the following to my code:
if ( is_checkout_pay_page() ) {
// unset Payment Gateways
}
SOLVED Here's my attempt and it works. But not sure if it can we expressed better:
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways', 10, 1 );
function filter_woocommerce_available_payment_gateways( $available_gateways ) {
if ( ! ( is_checkout_pay_page() ) ) {
$gateways_to_disable = array( 'cardgatecreditcard', 'cardgategiropay', 'cardgateideal', 'cardgatesofortbanking' );
$shipping_methods = array( 'flat_rate', 'request_shipping_quote' );
$disable_gateways = false;
// Check if we need to disable gateways
foreach ( $shipping_methods as $shipping_method ) {
if ( strpos( WC()->session->get( 'chosen_shipping_methods' )[0], $shipping_method ) !== false ) $disable_gateways = true;
}
// If so, disable the gateways
if ( $disable_gateways ) {
foreach ( $available_gateways as $id => $gateway ) {
if ( in_array( $id, $gateways_to_disable ) ) {
unset( $available_gateways[$id] );
}
}
}
return $available_gateways;
}
else { return $available_gateways;
}
}
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways', 10, 1 );
function filter_woocommerce_available_payment_gateways( $available_gateways ) {
if ( ! ( is_checkout_pay_page() ) ) {
$gateways_to_disable = array( 'paymentgateway1', 'paymentgateway2', 'paymentgateway3' );
$shipping_methods = array( 'shippingmethod1', 'shippingmethod2', 'shippingmethod3' );
$disable_gateways = false;
// Check if we need to disable gateways
foreach ( $shipping_methods as $shipping_method ) {
if ( strpos( WC()->session->get( 'chosen_shipping_methods' )[0], $shipping_method ) !== false ) $disable_gateways = true;
}
// If so, disable the gateways
if ( $disable_gateways ) {
foreach ( $available_gateways as $id => $gateway ) {
if ( in_array( $id, $gateways_to_disable ) ) {
unset( $available_gateways[$id] );
}
}
}
return $available_gateways;
}
else { return $available_gateways;
}
}
Related
I try to disable some shipping methods based on cart subtotal (after discounts).
// Disable Woocommerce shipping methods based on cart subtot
function wpsh_hide_shipping_based_on_subtotal( $rates, $package ) {
// Retrieve cart subtotal
//$cart_subtotal = $package['contents_cost'];
$cart_subtotal = WC()->cart->get_cart_contents_total();
// Shipping rate to be excluded
$shipping_id1 = 'flat_rate:73';
$shipping_id2 = 'flat_rate:75';
$shipping_id3 = 'local_pickup:69';
$shipping_id4 = 'flat_rate:76';
$shipping_id5 = 'local_pickup:79';
if ( $cart_subtotal < 99 ){
unset( $rates[ $shipping_id4 ] );
unset( $rates[ $shipping_id5 ] );
}
if ( $cart_subtotal >= 99 ){
unset( $rates[ $shipping_id1 ] );
unset( $rates[ $shipping_id2 ] );
unset( $rates[ $shipping_id3 ] );
}
return $rates;
}
add_filter( 'woocommerce_package_rates', 'wpsh_hide_shipping_based_on_subtotal', 10, 2 );
This seems to work, but sometimes, especially with variable products it doesnt hide shipping methods below 99 subtotal. I dont have any clues why. Can you help me?
Try with woocommerce_shipping_packages to hide the shipping method.
add_filter( 'woocommerce_shipping_packages', array( $this, 'hide_shipping_rates_from_packages'), 10, 1 );
public function hide_shipping_rates_from_packages( $packags ) {
if ( !empty( WC()->cart ) ) {
$cart_subtotal = wc_format_decimal( WC()->cart->get_cart_subtotal( false ) ); // Without include compound taxes.
if ( $cart_subtotal < 80 ) {
foreach( $packags as $index => $package ) {
unset($packags[$index]['rates']['free_shipping:1']);
}
}
}
return $packags;
}
For recurring carts, use the woocommerce_package_rates filter to hide shipping methods. This filter affects the session-stored package details and this filter is not triggered when rates are stored in the WC session data for this package.
add_filter( 'woocommerce_package_rates', array( $this, 'hide_shipping_rates_from_package_rates'), 10, 2 );
public function hide_shipping_rates_from_package_rates( $package_rates, $package ) {
if ( !empty( WC()->cart ) ) {
$cart_subtotal = wc_format_decimal( WC()->cart->get_cart_subtotal( false ) ); // Without include compound taxes.
if ( $cart_subtotal < 80 ) {
unset($packag_rates['free_shipping:1']);
}
}
return $packag_rates;
}
Following Disable Payment Gateway For Specific Shipping Method On Checkout Only answer to my previous question, which:
Disables Payment Gateways ('cardgatecreditcard', 'cardgategiropay', 'cardgateideal', 'cardgatesofortbanking')
When Specific Shipping Methods are Selected ('flat_rate', 'request_shipping_quote')
However, it seems that the last part is causing a conflict with the WooCommerce Home page:
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways', 10, 1 );
function filter_woocommerce_available_payment_gateways( $available_gateways ) {
$gateways_to_disable = array( 'cardgatecreditcard', 'cardgategiropay', 'cardgateideal', 'cardgatesofortbanking' );
$shipping_methods = array( 'flat_rate', 'request_shipping_quote' );
$disable_gateways = false;
// Check if we need to disable gateways
foreach ( $shipping_methods as $shipping_method ) {
if ( strpos( WC()->session->get( 'chosen_shipping_methods' )[0], $shipping_method ) !== false ) $disable_gateways = true;
}
// If so, disable the gateways
if ( $disable_gateways ) {
foreach ( $available_gateways as $id => $gateway ) {
if ( in_array( $id, $gateways_to_disable ) ) {
unset( $available_gateways[$id] );
}
}
}
return $available_gateways;
}
How do I go about troubleshooting this further?
This is the Fatal Error:
Fatal error: Uncaught Error: Call to a member function get() on null in /home/customer/www/multitrance.com/public_html/wp-content/themes/oceanwp-child/functions.php:304
Stack trace:
#0 /home/customer/www/multitrance.com/public_html/wp-includes/class-wp-hook.php(287): filter_woocommerce_available_payment_gateways(Array)
#1 /home/customer/www/multitrance.com/public_html/wp-includes/plugin.php(212): WP_Hook->apply_filters(Array, Array)
#2 /home/customer/www/multitrance.com/public_html/wp-content/plugins/woocommerce/includes/class-wc-payment-gateways.php(160): apply_filters('woocommerce_ava...', Array)
#3 /home/customer/www/multitrance.com/public_html/wp-content/plugins/woocommerce/packages/woocommerce-admin/src/Features/OnboardingTasks.php(92): WC_Payment_Gateways->get_available_payment_gateways()
#4 /home/customer/www/multitrance.com/public_html/wp-content/plugins/woocommerce/packages/woocommerce-admin/src/Features/OnboardingTasks.php(149): Automattic\WooCommerce\Admin\Features\OnboardingTasks::get_settings()
#5 /home/custo in /home/customer/www/multitrance.com/public_html/wp-content/themes/oceanwp-child/functions.php on line 304
Line 304:
if ( strpos( WC()->session->get( 'chosen_shipping_methods' )[0], $shipping_method ) !== false ) $disable_gateways = true;
You need to restrict your code to only checkout page:
add_filter( 'woocommerce_available_payment_gateways', 'filter_woocommerce_available_payment_gateways' );
function filter_woocommerce_available_payment_gateways( $available_gateways ) {
if( is_checkout() && ! is_wc_endpoint_url() ) {
$gateways_to_disable = array( 'cardgatecreditcard', 'cardgategiropay', 'cardgateideal', 'cardgatesofortbanking' );
$shipping_methods = array( 'flat_rate', 'request_shipping_quote' );
$chosen_shipping = WC()->session->get( 'chosen_shipping_methods' )[0];
$disable_gateways = false;
// Check if we need to disable gateways
foreach ( $shipping_methods as $shipping_method ) {
if ( strpos( $chosen_shipping, $shipping_method ) !== false ) {
$disable_gateways = true;
}
}
// If so, disable the gateways
if ( $disable_gateways ) {
foreach ( $available_gateways as $payment_id => $gateway ) {
if ( in_array( $payment_id, $gateways_to_disable ) ) {
unset( $available_gateways[$payment_id] );
}
}
}
}
return $available_gateways;
}
It should work without making trouble in home page
I've used the following code which works fine, it makes all orders default status to be on hold.
/**
* Auto Complete all WooCommerce orders.
*/
add_action( 'woocommerce_thankyou', 'custom_woocommerce_auto_complete_order' );
function custom_woocommerce_auto_complete_order( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
$order->update_status( 'on-hold' );
}
But I need to combine this with shipping/billing address. i.e. execute this code Only if the order's shipping or billing address is NOT UK or France. Customers from all other countries will be placed on hold as per this code, while UK & France orders get the default order status set by the payment geteway's settings.
Any help will be much appreciated.
Try the following to exclude France and UK billing and shipping countries:
add_action( 'woocommerce_thankyou', 'country_based_auto_on_hold_orders' );
function country_based_auto_on_hold_orders( $order_id ) {
if ( ! $order_id ) {
return;
}
$order = wc_get_order( $order_id );
if ( ! ( in_array( $order->get_billing_country(), ['UK', 'FR'] ) || in_array( $order->get_shipping_country(), ['UK', 'FR'] ) ) ) {
$order->update_status( 'on-hold' );
}
}
Now using woocommerce_thankyou hook, is the old way. To avoid multiple email notifications on status changes, use the following instead:
For all payment gateways except Cash on delivery (COD):
add_action( 'woocommerce_payment_complete_order_status', 'wc_auto_on_old_paid_order', 10, 3 );
function wc_auto_on_old_paid_order( $status, $order_id, $order ) {
if ( ! ( in_array( $order->get_billing_country(), ['UK', 'FR'] ) || in_array( $order->get_shipping_country(), ['UK', 'FR'] ) ) ) {
$status = 'on-hold'
}
return $status;
}
For Cash on delivery (COD) payments:
add_action( 'woocommerce_cod_process_payment_order_status', 'wc_auto_complete_cod_order', 10, 2 );
function wc_auto_complete_cod_order( $status, $order ) {
if ( ! ( in_array( $order->get_billing_country(), ['UK', 'FR'] ) || in_array( $order->get_shipping_country(), ['UK', 'FR'] ) ) ) {
$status = 'on-hold'
}
return $status;
}
Related: WooCommerce: Auto complete paid orders
I need to disable certain payment methods based on the shipping class of items added in cart.
I have tried the following code:
add_filter( 'woocommerce_available_payment_gateways', 'unset_gateway_by_sclass' );
function unset_gateway_by_sclass( $available_gateways ) {
if ( is_admin() ) return $available_gateways;
$unset = false;
$category_ids = array( no-cod );
foreach ( WC()->cart->get_cart_contents() as $key => $values ) {
$terms = get_the_terms( $values['product_id'], 'shipping_class' );
foreach ( $terms as $term ) {
if ( in_array( $term->term_id, $category_ids ) ) {
$unset = true;
break;
}
}
}
if ( $unset == true ) unset( $available_gateways['cod'] );
return $available_gateways;
}
This is not working, where am I going wrong in this?
Figured it out
add_filter( 'woocommerce_available_payment_gateways', 'sclass_func', 10, 2 );
function sclass_func( $available_gateways ) {
$shipping_class_target = 1157;
$in_cart = false;
foreach( WC()->cart->cart_contents as $key => $values ) {
if( $values[ 'data' ]->get_shipping_class_id() == $shipping_class_target ) {
$in_cart = true;
break;
}
}
if( $in_cart ) {
unset($available_gateways['cod']);
}
return $available_gateways;
}
I am trying to learn some more advanced WordPress, I have used and adapted a code snippet but cannot understand some of it's origins. The snippet is for WooCommerce and hides other shipping methods when 'free shipping' is available.
It looks like a loop using a variable called $rates but I can't find anything in the Woo Dev Docs about this variable. I thought it may have been an instance of a class but again can't find any info that may help. Based on the snippet below, could someone tell me where $rates has originated from, what it is and where it would have been declared please?
add_filter( 'woocommerce_package_rates', 'bbloomer_unset_shipping_when_free_is_available_all_zones', 10, 2 );
function bbloomer_unset_shipping_when_free_is_available_all_zones( $rates, $package ) {
$all_free_rates = array();
foreach ( $rates as $rate_id => $rate ) {
if ( 'free_shipping' === $rate->method_id ) {
$all_free_rates[ $rate_id ] = $rate;
break;
}
}
if ( empty( $all_free_rates )) {
return $rates;
} else {
return $all_free_rates;
}
}
$rates is generated from function calculate_shipping_for_package() inside woocommerce/includes/class-wc-shipping.php. You can see the filter woocommerce_package_rates in below function
public function calculate_shipping_for_package( $package = array(), $package_key = 0 ) {
// If shipping is disabled or the package is invalid, return false.
if ( ! $this->enabled || empty( $package ) ) {
return false;
}
$package['rates'] = array();
// If the package is not shippable, e.g. trying to ship to an invalid country, do not calculate rates.
if ( $this->is_package_shippable( $package ) ) {
// Check if we need to recalculate shipping for this package.
$package_to_hash = $package;
// Remove data objects so hashes are consistent.
foreach ( $package_to_hash['contents'] as $item_id => $item ) {
unset( $package_to_hash['contents'][ $item_id ]['data'] );
}
$package_hash = 'wc_ship_' . md5( wp_json_encode( $package_to_hash ) . WC_Cache_Helper::get_transient_version( 'shipping' ) );
$session_key = 'shipping_for_package_' . $package_key;
$stored_rates = WC()->session->get( $session_key );
if ( ! is_array( $stored_rates ) || $package_hash !== $stored_rates['package_hash'] || 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ) {
foreach ( $this->load_shipping_methods( $package ) as $shipping_method ) {
if ( ! $shipping_method->supports( 'shipping-zones' ) || $shipping_method->get_instance_id() ) {
$package['rates'] = $package['rates'] + $shipping_method->get_rates_for_package( $package ); // + instead of array_merge maintains numeric keys
}
}
// Filter the calculated rates.
$package['rates'] = apply_filters( 'woocommerce_package_rates', $package['rates'], $package );
// Store in session to avoid recalculation.
WC()->session->set(
$session_key, array(
'package_hash' => $package_hash,
'rates' => $package['rates'],
)
);
} else {
$package['rates'] = $stored_rates['rates'];
}
}
return $package;
}