1

I want to create several shipping rates in WooCommerce, based on a different fixed cost per product, which I'm saving as meta values. I'd like to offer a different selection of rates for each of my shipping zones: for example, in the UK I'd like to have UK First and UK Second, each of which has a different cost for each product.

I've created the first of my shipping methods, and I can add it to a shipping zone. But when I try to check out in that zone, the cart says No shipping options were found. Can anyone spot what I'm doing wrong?

  • I've turned on debug mode and confirmed that I really am checking out in the UK zone.
  • I've also tried just adding the rate and returning from the top of calculate_shipping:
$this->add_rate(
    $rate = array(
        'id'    => $this->id,
        'label' => $this->title,
        'cost'  => 101,
    )
);
if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {

    function shipping_method_uk_first_init() {

        class Shipping_Method_UK_First extends \WC_Shipping_Method {

            private string $meta_key = 'shipping_uk_1st';

            public function __construct() {
                $this->id                 = 'shipping_method_uk_first';
                $this->method_title       = __( 'UK First' );
                $this->method_description = __( 'Royal Mail first class' );
                $this->enabled            = 'yes';
                $this->title              = 'UK First Class';
                $this->supports           = array(
                    'shipping-zones',
                    'instance-settings',
                );
                $this->init();
            }

            function init() {
                $this->init_form_fields();
                $this->init_settings();

                add_action(
                    'woocommerce_update_options_shipping_' . $this->id,
                    array(
                        $this,
                        'process_admin_options',
                    )
                );
            }

            public function calculate_shipping( $package = array() ) {
                $cost = 0;

                foreach ( $package['contents'] as $item_id => $values ) {
                    $product                      = $values['data'];
                    $product_shipping_method_cost = $product->get_meta( $this->meta_key );
                    $cost += floatval( $product_shipping_method_cost );
                }

                $rate = array(
                    'id'    => $this->id,
                    'label' => $this->title,
                    'cost'  => $cost,
                );

                $this->add_rate( $rate );
            }
        }

    }

    add_action( 'woocommerce_shipping_init', 'shipping_method_uk_first_init' );

    function add_shipping_method_uk_first( $methods ) {
        $methods['shipping_method_uk_first'] = 'Shipping_Method_UK_First';

        return $methods;
    }

    add_filter( 'woocommerce_shipping_methods', 'add_shipping_method_uk_first' );

}
LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
And Finally
  • 5,602
  • 14
  • 70
  • 110

1 Answers1

2

There are some mistakes and missing things. I have also added a product custom field to product shipping options. Try the following:

if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) {

    function shipping_method_uk_first_init() {

        class Shipping_Method_UK_First extends \WC_Shipping_Method {

            public function __construct( $instance_id = 0 ) {
                $this->id                 = 'shipping_method_uk_first';
                $this->instance_id        = absint( $instance_id );
                $this->method_title       = __( 'UK First' );
                $this->method_description = __( 'Royal Mail first class' );
                $this->enabled            = 'yes';
                $this->meta_key           = 'shipping_uk_1st'; // <= HERE define the related product meta key
                $this->title              = __('UK First Class' );
                $this->supports           = array(
                    'shipping-zones',
                    'instance-settings',
                );
                $this->init();
            }

            function init() {
                $this->init_form_fields();
                $this->init_settings();

                add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
            }

            public function calculate_shipping( $package = array() ) {
                $cost = 0; // Initializing

                foreach ( $package['contents'] as $item_key => $item ) {
                    // Get the parent variable product for product variation items
                    $product = $item['variation_id'] > 0 ? wc_get_product( $item['product_id']) : $item['data'];

                    $cost += floatval( $product->get_meta( $this->meta_key ) );
                }

                $rate = array(
                    'id'    => $this->id,
                    'label' => $this->title,
                    'cost'  => $cost,
                );

                $this->add_rate( $rate );
            }
        }

    }

    add_action( 'woocommerce_shipping_init', 'shipping_method_uk_first_init' );

    function add_shipping_method_uk_first( $methods ) {
        $methods['shipping_method_uk_first'] = 'Shipping_Method_UK_First';

        return $methods;
    }

    add_filter( 'woocommerce_shipping_methods', 'add_shipping_method_uk_first' );

    // Add a custom field to product shipping options
    function add_custom_field_product_options_shipping() {
        global $product_object;

        echo '</div><div class="options_group">'; // New option group

        woocommerce_wp_text_input( array(
            'id'          => 'shipping_uk_1st',
            'label'       => __( 'UK First shipping cost', 'woocommerce' ),
            'placeholder' => '',
            'desc_tip'    => 'true',
            'description' => __( 'Enter the UK First shipping cost value here.', 'woocommerce' ),
            'value'       => (float) $product_object->get_meta( 'shipping_uk_1st' ),
        ) );
    }

    // Save product custom field shipping option value
    function save_custom_field_product_options_shipping( $product ) {
        if ( isset($_POST['shipping_uk_1st']) ) {
            $product->update_meta_data( 'shipping_uk_1st', (float) sanitize_text_field($_POST['shipping_uk_1st']) );
        }
    }

    add_action( 'woocommerce_product_options_shipping', 'add_custom_field_product_options_shipping', 5 );
    add_action( 'woocommerce_admin_process_product_object', 'save_custom_field_product_options_shipping' );
}

Code goes in functions.php file of the active child theme (or active theme). Tested and works.

enter image description here

enter image description here

LoicTheAztec
  • 229,944
  • 23
  • 356
  • 399
  • ❤️ Thanks so much @loictheaztec, works a treat! And thanks for the added extra custom field in the shipping options. As far as I can see, the critical change is to pass `$instance_id` to the constructor and set it as a property. That was missing from the examples I was imitating – maybe they related to older WC versions. I'm suprised the `$meta_key` property needs to be set in there – I'd have thought setting it when declaring it had the same effect. The variation ID bit wasn't essential in my particular case, because my test products are simple, but it's good to know. Big thanks! – And Finally Apr 21 '21 at 17:42