Woocommerce – Create Free shipping for single item while using UPS extension

I am using the UPS extension in woocommerce. I need to offer free shipping on some items, bypassing the UPS calculator. It seems one good way to do this is to mark the item as “virtuaL,” but then the item doesn’t require shipping. I need to require shipping for this item. I have been able to customize the checkout page and force the shipping fields to show on the virtual items, but then they don’t store in the actual orders.

Anybody know how I can create a custom function or modify the woocommerce files to force the checkout process to require shipping on a virtual item, all while bypassing the UPS calculator? (other items need to be calculated for shipping)

Related posts

Leave a Reply

4 comments

  1. I just ran into this issue, maybe this will help someone out. For all of the products that I wanted free shipping, I made virtual products.

    Then I went to Woocommerce > Shipping Options Tab, check the box that says “Collect shipping address even when not required” which is in the “Shipping Destination section”.

  2. I needed exactly this as well, and have a workable solution:

    First go into your functions.php in "Appearance"->"Editor"->"Theme Functions", and add the following code (I highly recommend using a “child theme” first, so you are not modifying your theme’s functions.php…or you could alternatively create a custom plugin).

    /* Puts items with "free-shipping" shipping class into a different shipping package. */
    
        function free_woocommerce_cart_shipping_packages( $packages ) {
    
            // Reset the packages
            $packages = array();
    
            // Free items
            $freeshipping_items = array();
            $regular_items      = array();
    
            // Sort free from regular (replace "free-shipping" below with the shipping class slug you actually use).
            foreach( WC()->cart->get_cart() as $item ) {
                if( $item['data']->needs_shipping() ) {
                    if( $item['data']->get_shipping_class() == 'free-shipping' ) {
                        $freeshipping_items[] = $item;
                    }
                    else {
                        $regular_items[] = $item;
                    }
                }
            }
    
            // Put inside packages:
            if( $freeshipping_items ) {
                $packages[] = array(
                    'ship_via'        => array( 'flat_rate' ),
                    'contents'        => $freeshipping_items,
                    'contents_cost'   => array_sum(wp_list_pluck($freeshipping_items, 'line_total')),
                    'applied_coupons' => WC()->cart->applied_coupons,
                    'destination'     => array(
                        'country'   => WC()->customer->get_shipping_country(),
                        'state'     => WC()->customer->get_shipping_state(),
                        'postcode'  => WC()->customer->get_shipping_postcode(),
                        'city'      => WC()->customer->get_shipping_city(),
                        'address'   => WC()->customer->get_shipping_address(),
                        'address_2' => WC()->customer->get_shipping_address_2()
                    )
                );
            }
            if( $regular_items ) {
                $packages[] = array(
                 // 'ship_via'        => array( 'usps' ),
                    'contents'        => $regular_items,
                    'contents_cost'   => array_sum(wp_list_pluck($regular_items, 'line_total')),
                    'applied_coupons' => WC()->cart->applied_coupons,
                    'destination'     => array(
                        'country'   => WC()->customer->get_shipping_country(),
                        'state'     => WC()->customer->get_shipping_state(),
                        'postcode'  => WC()->customer->get_shipping_postcode(),
                        'city'      => WC()->customer->get_shipping_city(),
                        'address'   => WC()->customer->get_shipping_address(),
                        'address_2' => WC()->customer->get_shipping_address_2()
                    )
                );
            }
    
            return $packages;
        }
    
        add_filter('woocommerce_cart_shipping_packages', 'free_woocommerce_cart_shipping_packages');
    

    Second, go into the "Products"->"Shipping Classes" and create a Shipping Class called “Free Shipping” with a slug called “free-shipping” (the slug name is what we will tie our custom function to). Then “Add New Shipping Class“.

    Then in your "WooCommerce"->"Settings"->"Shipping"->"Flat Rate" enable “Flat Rate” shipping, and under the “Shipping Class Costs” section put a value of “0.00” next to the “Free Shipping” class we just created. In my case, I also have “Calculation Type” set to “Per Class“. Then “Save changes“.

    Then finally in "WooCommerce"->"Settings"->"Shipping"->"Shipping Options" in the “Shipping Methods” section, make sure “Flat Rate” has a higher “Selection Priority” (in the column of that name) than whatever other shipping method(s) you are using, and that it is the last one on the list (you can drag to reorder using the menu icon on the left of each option). “Save Changes“.

    Then edit one of your products that you wish to have free shipping, go to the “Product Data"->"Shipping” section and change the shipping class to “Free Shipping” (the new class you created). Then “Update” that product to save the changes. Then also add it to your cart, and also add some other item to your cart that has regular non-free shipping.

    Test.

    You should now see that free shipping items get their own shipping “package” row (“Shipping #1” and “Shipping #2“) separate from the items that should still charge for shipping, even when you have some of each kind in the shopping cart.

    Alternatively, you could also try the full paid plugin called “Per Product Shipping“.

    Or this free plugin may work as well (no promises).

  3. Another way that I believe may help you to make an item have “free” shipping (and a way I have used in the past), is to create a coupon that will discount the product (don’t mark it as “virtual”) to that of it’s price minus shipping.

    So say for example your product is $100 and shipping is $10 (total of $110), you then create a coupon specifically for that for product(s) that will give it a $10 discount making your product & shipping total $100.

    I assume you already know how to create coupons in woocommerce (if you need some refreshing on how to do so this page will help). To make the coupons apply to a specific products which you want to have free shipping, go to Usage Restriction->Products and specify the specific product you want to have “free” (or in this case, discounted) shipping.

    To save the user the trouble of entering this coupon code upon checkout, you can add the following code to functions.php:

    //Check for specific products then discount to adjust for 'free' shipping.
    
    function my_free_shipping(){
    //variables.
    global $woocommerce;
    $coupon_code = 'free-shiping';
    $products= array('1', '2'); //products (ID's) to have discounted shipping.
    
    //get the cart contents.
    $cart_item = $woocommerce->cart->get_cart();
    //loop through the cart items looking for the products in $products.
        foreach ($cart_item as $key => $item){
            //check if the cart items match to any of those in the array.
            if(in_array($item['product_id'], $products)){       
                //apply discount.
                $woocommerce->cart->add_discount(sanitize_text_field($coupon_code));
            }
        }
    } 
    
    add_action('init', 'my_free_shipping');   
    

    Please note that I have not tested this code with the UPS extension yet, but feel free to modify and play around with it.

    I hope this post is/was of some help to you!