Excluir los productos virtuales del importe mínimo para activar el envío gratis en WooCommerce

Comparte este post

En WooCommerce podemos fijar un importe mínimo de pedido para activar el Envío Gratuito, y que además, oculte el resto de métodos de envío cuando el gratuito está disponible, lo vimos en este post: WooCommerce – Activar el envío gratuito automático según el importe del carrito.

La cuestión es que los productos denominados virtuales, que no requieren de envío, sí cuentan para el importe mínimo que hace activar el Envío Gratuito. Esto ocasiona que si hemos establecido el importe mínimo en 50€, un cliente que compra un producto virtual valorado en 45€ y un producto físico valorado en 5€, ese producto físico sería enviado de forma gratuita según la configuración de WooCommerce.

Estuve buscando información al respecto y di con este post de 2017 en Stack Overflow: Exclude WooCommerce virtual product from counting towards free delivery

Un usuario compartió este filtro:

add_filter('woocommerce_package_rates', 'custom_free_shipping_option', 15, 2 );
function custom_free_shipping_option($rates, $package){

    // HERE set the "minimum order amount" for free shipping
    $limit = 50;

    $free_total = 0;

    // Get the cart content total excluding virtual products
    foreach( WC()->cart->get_cart() as $cart_item )
        if( ! $cart_item['data']->is_virtual( ) )
            $free_total += $cart_item['line_total'];

    // Set the cost to 0 based on specific cart content total
    if( $free_total > $limit )
        foreach ( $rates as $rate_key => $rate )
            if( 'pakkelabels_shipping_gls' === $rate->method_id )
                $rates[$rate->id]->cost = 0;

    return $rates;
}

Según el código, tenemos que especificar manualmente el límite de importe para que se active el Envío Gratuito, en la variable $limit. Seguramente habrá alguna forma de tomar dinámicamente el valor que hemos configurado en el método Envío Gratuito.

Probé este código en una instalación de pruebas y mi decepción es que parece no funcionar. Según el código, el producto Álbum no debería contar para el importe mínimo del Envío Gratuito, pero vemos que si cuenta.

Además tenemos el otro inconveniente que muestra tanto el envío gratuito como el envío de pago, cuando solo debería mostrar el Envío Gratuito si este está disponible.

Pero el código me dió una idea y lo adapte de la siguiente forma:

Primero, con el bucle Foreach, recorremos el carrito sumando el importe de los artículos, pero excluyendo aquellos que sean virtuales. El importe resultante se guarda en la variable $free_total. Hasta aquí, el ejemplo funciona perfecto.

// Get the cart content total excluding virtual products
    foreach( WC()->cart->get_cart() as $cart_item )
        if( ! $cart_item['data']->is_virtual( ) )
            $free_total += $cart_item['line_total'];

Ahora comprobamos si este importe es superior al importe mínimo establecido en la variable $limit, si se cumple, buscamos los métodos de envío hasta que coincida con el Envío Gratuito, cuando coincida paramos el bucle con break y este será el único a mostrar:

//If the total reaches the minimum order, only the free shipping is shown
    if( $free_total > $limit ){
        $free = array();
        foreach ( $rates as $rate_id => $rate ) {
            if ( 'free_shipping' === $rate->method_id ) {
                $free[ $rate_id ] = $rate;
                break;
            }
        }
        return ! empty( $free ) ? $free : $rates;

Y si no se cumple, es decir, si no llega al importe mínimo de pedido, recorremos los métodos de envío QUE NO SEAN el envío gratuito, para no mostrarlos como opción de envío:

}
    else{
        //If it does not reach the minimum order, the shipments that are NOT free are shown
        $free = array();
        foreach ( $rates as $rate_id => $rate ) {
            if ( 'free_shipping' != $rate->method_id ) {
                $free[ $rate_id ] = $rate;
            }
        }
        return ! empty( $free ) ? $free : $rates;   
    }

Aquí el codigo del snippet completo:

function custom_free_shipping_option($rates, $package){
    // HERE set the "minimum order amount" for free shipping
    $limit = 50;
    $free_total = 0;

    // Get the cart content total excluding virtual products
    foreach( WC()->cart->get_cart() as $cart_item )
        if( ! $cart_item['data']->is_virtual( ) )
            $free_total += $cart_item['line_total'];

    //If the total reaches the minimum order, only the free shipping is shown
    if( $free_total > $limit ){
        $free = array();
        foreach ( $rates as $rate_id => $rate ) {
            if ( 'free_shipping' === $rate->method_id ) {
                $free[ $rate_id ] = $rate;
                break;
            }
        }
        return ! empty( $free ) ? $free : $rates;   
    }
    else{
        //If it does not reach the minimum order, the shipments that are NOT free are shown
        $free = array();
        foreach ( $rates as $rate_id => $rate ) {
            if ( 'free_shipping' != $rate->method_id ) {
                $free[ $rate_id ] = $rate;
            }
        }
        return ! empty( $free ) ? $free : $rates;   
    }
}
add_filter('woocommerce_package_rates', 'custom_free_shipping_option', 15, 2 );

Ahora volvemos al ejemplo y comprobamos que funciona:

En este caso, el importe de los productos físicos no llega al mínimo de 50€ que haría saltar el Envío Gratuito, por lo que se cobran gastos de envío aunque el importe total sí supere los 50€.

Si añadimos más unidades del producto físico hasta superar la barrera de los 50€, ahora si salta el Envío Gratuito, y además se ocultan el resto de métodos de envío de pago:

En mi ejemplo solo tengo configurados 2 métodos de envío, el importe fijo de 5€ y el envío gratuito. No lo he probado con otros métodos de envío (combinando clases, etc). Pero en teoría debería funcionar correctamente.

Si tienes algún problema, déjamelo en la caja de comentarios.

Nos vemos en el siguiente post 😉

Deja el primer comentario