If you have ever developed something for WordPress, be it a theme or a simple plugin, you are already familiar with the modularity of WordPress. WooCommerce is developed with extendability in mind also.
In this tutorial we will create a simple shipping method for WooCommerce which will calculate the cost of shipping. We will also add some restrictions to our plugin so that our shipping method is available only for certain countries.
To follow this tutorial you will need:
- WordPress
- WooCommerce plugin installed and activated
- Editor of choice
Shipping Rules for Our Shipping Method
Before we begin, we will need to define how our shipping method will calculate the cost and where it can ship.
The cost will be determined by the weight we need to ship and by the zone where we need to ship. A zone is a number which is assigned to countries and to prices. The higher the number, the longer the shipping distance. Our fictional shipping company is shipping only to a few countries:
- USA
- Canada
- Germany
- United Kingdom
- Italy
- Spain
- Croatia
Our shipping company will be from Croatia, so Croatia is in zone 0. Let's define zones for the other countries:
- USA: 3
- Canada: 3
- Germany: 1
- United Kingdom: 2
- Italy: 1
- Spain: 2
Now that we have all the countries available for shipping with the corresponding zone, it's time to define prices.
- Zone 0: $10
- Zone 1: $30
- Zone 2: $50
- Zone 3: $70
I have mentioned also the weight, right? Our shipping company can ship up to 100 kg, and the prices are:
- 0–10 kg: $0
- 11–30 kg: $5
- 31–50 kg: $10
- 51–100 kg: $20
We have everything that we need to create our shipping method. Let's learn a little about the WooCommerce Shipping API.
Introduction to the WooCommerce Shipping Method API
When creating a shipping method, we need to extend the class from the WooCommerce abstract class WC_Shipping_Method
. Defined attributes in this class are:
-
$id
: ID (slug, keyword) of our shipping. Required. -
$number
: Integer ID.
-
$method_title
: Name of our shipping shown in admin.
-
$method_description
: Short description of our shipping shown in admin (Optional).
-
$enabled
: String Boolean ("yes" or "no") that gives the information if our shipping is enabled and can be used or not. -
$title
: Used to display our shipping name on our site.
-
$availability
: Defines if the shipping is available or not.
-
$countries
: Array of countries this method is enabled for. Default value is an empty array.
-
$tax_status
: Default value is taxable. If it is taxable then the tax will be charged.
-
$fee
: Default value is 0. Fees for the method.
-
$minimum_fee
: The minimum fee for the method. Default value is null.
-
$has_settings
: Defines if this method has any settings. Default value is true.
-
$supports
: Array containing features this method supports. Default value is an empty array.
-
$rates
: Array of rates. This must be populated to register shipping costs. Default value is an empty array.
Defined methods in the WC_Shipping_Method
are:
-
is_taxable()
: Returns whether or not we need to calculate tax on top of the shipping rate. -
add_rate( $args = array() )
: Pushes a shipping rate defined in the parameter $args into the attribute$rates
.
-
has_settings()
: Returns the value of the attribute$has_settings
. -
is_available()
: Returns if the shipping is available. If there are countries set in the attribute$countries
and the attribute$availability
is set to values including, specific or excluding, it will return true or false if the country is available for shipping.
-
get_title()
: Returns the title of this shipping.
-
get_fee( $fee, $total )
: Returns the fee value for this shipping based on the parsed$fee
and$total
.
-
supports( $feature )
: Returns if this shipping method is supporting a feature or not.
Since the class WC_Shipping_Method
extends the class WC_Settings_API
, there are more attributes and methods that will not be explained here for the sake of simplicity.
There are also other methods that need to be defined so that the shipping can get or set settings and also calculate the actual cost of the shipping. These methods are:
-
init()
: Creates the form fields and settings (can be named differently, as long as we use the methods inside it and call it in the__constructor
method).
-
calculate_shipping( $package )
: This is the method used to calculate the cost for this shipping. Package is anarray
with the products to ship.
In the calculate_shipping
method, we are adding the rate with the method add_rate
. This method accepts an array with several options:
-
id
: ID of the rate. -
label
: Label for the rate.
-
cost
: Amount of the shipping. It can be a single value or anarray
with costs for each item in the cart.
-
taxes
: It accepts anarray
of taxes or nothing so the tax is calculated by WooCommerce. It can even acceptfalse
if you do not want the tax to be calculated. -
calc_tax
: Acceptsper_order
orper_item
. If you useper_item
, anarray
of costs will need to be provided.
To register the shipping method, we need to add our shipping method in the array
of the registered method by passing the name of our class. We can get access to that array
and send the modified array
back using a WordPress filter that is defined inside the WooCommerce plugin. That filter is called woocommerce_shipping_methods
.
Creating the New Shipping Class
We will create our shipping method as a new plugin that extends the WooCommerce plugin. Create a new folder tutsplus-shipping under wp-content/plugins
. Also, create a file with the same name tutsplus-shipping.php
and add this code:
<?php /** * Plugin Name: TutsPlus Shipping * Plugin URI: http://code.tutsplus.com/tutorials/create-a-custom-shipping-method-for-woocommerce--cms-26098 * Description: Custom Shipping Method for WooCommerce * Version: 1.0.0 * Author: Igor Benić * Author URI: http://www.ibenic.com * License: GPL-3.0+ * License URI: http://www.gnu.org/licenses/gpl-3.0.html * Domain Path: /lang * Text Domain: tutsplus */ if ( ! defined( 'WPINC' ) ) { die; } /* * Check if WooCommerce is active */ if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { function tutsplus_shipping_method() { if ( ! class_exists( 'TutsPlus_Shipping_Method' ) ) { class TutsPlus_Shipping_Method extends WC_Shipping_Method { /** * Constructor for your shipping class * * @access public * @return void */ public function __construct() { $this->id = 'tutsplus'; $this->method_title = __( 'TutsPlus Shipping', 'tutsplus' ); $this->method_description = __( 'Custom Shipping Method for TutsPlus', 'tutsplus' ); $this->init(); $this->enabled = isset( $this->settings['enabled'] ) ? $this->settings['enabled'] : 'yes'; $this->title = isset( $this->settings['title'] ) ? $this->settings['title'] : __( 'TutsPlus Shipping', 'tutsplus' ); } /** * Init your settings * * @access public * @return void */ function init() { // Load the settings API $this->init_form_fields(); $this->init_settings(); // Save settings in admin if you have any defined add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) ); } /** * Define settings field for this shipping * @return void */ function init_form_fields() { // We will add our settings here } /** * This function is used to calculate the shipping cost. Within this function we can check for weights, dimensions and other parameters. * * @access public * @param mixed $package * @return void */ public function calculate_shipping( $package ) { // We will add the cost, rate and logics in here } } } } add_action( 'woocommerce_shipping_init', 'tutsplus_shipping_method' ); function add_tutsplus_shipping_method( $methods ) { $methods[] = 'TutsPlus_Shipping_Method'; return $methods; } add_filter( 'woocommerce_shipping_methods', 'add_tutsplus_shipping_method' ); }
It may seem as if there is a lot to understand, but it is quite simple. First we check if the constant WPINC
is defined because if not, it means that someone is trying to access this file directly or from a location that is not WordPress.
Now that we are sure that this will be accessed from WordPress, we can move on. Before we start creating our shipping method for WooCommerce, we need to be sure that WooCommerce is active. We are checking if the file woocommerce.php
is in the array of active plugins that is saved in the database under the option active_plugins
.
We are then creating the function tutsplus_shipping_method
which we are also adding in the action woocommerce_shipping_init
. The action woocommerce_shipping_init
is the main action for WooCommerce Shippings which includes all the shipping classes before they get instantiated. By using that action we are sure that our shipping method will be included after WooCommerce has been initialised and in the right place for WooCommerce to use it.
The __construct
method of our class will set some general attributes. Some of them can be easily overwritten after the settings are loaded from the database in the method init
. Two other methods are left empty because we will define them later.
Setting Country Availability
Our shipping method is available only in the previously determined list of countries. This will be set before the method init
inside the method __construct
. Add this in the method __construct
:
<?php //... $this->method_description = __( 'Custom Shipping Method for TutsPlus', 'tutsplus' ); // Availability & Countries $this->availability = 'including'; $this->countries = array( 'US', // Unites States of America 'CA', // Canada 'DE', // Germany 'GB', // United Kingdom 'IT', // Italy 'ES', // Spain 'HR' // Croatia ); $this->init(); //...
The attribute availability
is set to 'including'
so that this shipping is only available for the countries included in the attribute countries
. When WooCommerce wants to display the available shippings to our customer, it will check if the shipping country is included in the attribute countries
for this shipping.
Creating Settings
If you look closely to our method __construct
, you can see that we are checking the settings for the enabled
and title
properties. We will create fields that will enable our properties to be changed.
Copy this code and populate our method init_form_fields
:
<?php function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable', 'tutsplus' ), 'type' => 'checkbox', 'description' => __( 'Enable this shipping.', 'tutsplus' ), 'default' => 'yes' ), 'title' => array( 'title' => __( 'Title', 'tutsplus' ), 'type' => 'text', 'description' => __( 'Title to be display on site', 'tutsplus' ), 'default' => __( 'TutsPlus Shipping', 'tutsplus' ) ), ); }
Now you can go to WordPress administration and change those settings at WooCommerce > Settings > Shipping > TutsPlus Shipping.
Try changing the setting Enable and see on the tab Shipping Options if our shipping method is enabled or not.
We have also defined that our shipping method can ship up to 100 kg. What if that rule changes in the near future? We can easily allow that to be edited in the settings. We will add that setting so our method init_form_fields
looks like this now:
<?php function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable', 'tutsplus' ), 'type' => 'checkbox', 'description' => __( 'Enable this shipping.', 'tutsplus' ), 'default' => 'yes' ), 'title' => array( 'title' => __( 'Title', 'tutsplus' ), 'type' => 'text', 'description' => __( 'Title to be display on site', 'tutsplus' ), 'default' => __( 'TutsPlus Shipping', 'tutsplus' ) ), 'weight' => array( 'title' => __( 'Weight (kg)', 'tutsplus' ), 'type' => 'number', 'description' => __( 'Maximum allowed weight', 'tutsplus' ), 'default' => 100 ), ); }
Now we can even set the maximum weight under the settings. We could make our countries, zones and everything else also customisable, but for the sake of simplicity we will skip that part.
Calculating the Shipping Cost
With everything set now, we have one more step before we can use our shipping method when selling our products. We need to calculate the shipping cost based on the products in the visitor's cart.
We are going to update the method calculate_shipping
one step at a time so that you can understand each step. The first step is to get the cost by weight. Add this code to that method:
<?php //... public function calculate_shipping( $package ) { $weight = 0; $cost = 0; $country = $package["destination"]["country"]; foreach ( $package['contents'] as $item_id => $values ) { $_product = $values['data']; $weight = $weight + $_product->get_weight() * $values['quantity']; } $weight = wc_get_weight( $weight, 'kg' ); if( $weight <= 10 ) { $cost = 0; } elseif( $weight <= 30 ) { $cost = 5; } elseif( $weight <= 50 ) { $cost = 10; } else { $cost = 20; } } //...
We have defined a few starting variables: $weight
, $cost
, and $country
. The variable $weight
will hold the total weight from all the products, the variable $cost
will hold the cost of this shipping method, and the variable $country
will hold the ISO code for the selected shipping country.
We are getting the total weight by iterating the cart and adding the weight for each product in the cart to the variable $weight
. Once we have our total weight, we are using the function wc_get_weight
to convert the weight into kilograms since that is the unit in which our shipping method has set the limit.
The last thing here is to get the cost for the calculated weight. If you look closely at the last part, we did not set the limit on the 100 kg as we said. Our shipping cost will show the cost even for a cart with a total weight of 101 kg or more. This part of our shipping limits will be done later in the article as a restriction when processing checkout or updating the order.
Calculating Cost for the Selected Shipping Country
Now that we have the cost based on the weight of the cart, we have to calculate the cost for the selected shipping country. Add this code next:
<?php //... public function calculate_shipping( $package ) { //... $countryZones = array( 'HR' => 0, 'US' => 3, 'GB' => 2, 'CA' => 3, 'ES' => 2, 'DE' => 1, 'IT' => 1 ); $zonePrices = array( 0 => 10, 1 => 30, 2 => 50, 3 => 70 ); $zoneFromCountry = $countryZones[ $country ]; $priceFromZone = $zonePrices[ $zoneFromCountry ]; $cost += $priceFromZone; } //...
The array $countryZones
holds the zones for each country. The second array $zonePrices
holds the prices for each zone. Once we have set both of those arrays, we get the cost by zone like this:
- We pass the ISO country code to the array
$countryZones
to get the zone. - We pass the returned zone to the array
$zonePrices
to get the cost. - We add the returned cost to the variable
$cost
.
Registering the Rate
We calculated the cost by the total weight, and we have also added the cost by the shipping country. The last step here is to register the rate, so add this last part:
<?php //... public function calculate_shipping( $package ) { //... $rate = array( 'id' => $this->id, 'label' => $this->title, 'cost' => $cost ); $this->add_rate( $rate ); } //...
Here is the whole code for this method if you had trouble following it:
<?php //... public function calculate_shipping( $package ) { $weight = 0; $cost = 0; $country = $package["destination"]["country"]; foreach ( $package['contents'] as $item_id => $values ) { $_product = $values['data']; $weight = $weight + $_product->get_weight() * $values['quantity']; } $weight = wc_get_weight( $weight, 'kg' ); if( $weight <= 10 ) { $cost = 0; } elseif( $weight <= 30 ) { $cost = 5; } elseif( $weight <= 50 ) { $cost = 10; } else { $cost = 20; } $countryZones = array( 'HR' => 0, 'US' => 3, 'GB' => 2, 'CA' => 3, 'ES' => 2, 'DE' => 1, 'IT' => 1 ); $zonePrices = array( 0 => 10, 1 => 30, 2 => 50, 3 => 70 ); $zoneFromCountry = $countryZones[ $country ]; $priceFromZone = $zonePrices[ $zoneFromCountry ]; $cost += $priceFromZone; $rate = array( 'id' => $this->id, 'label' => $this->title, 'cost' => $cost ); $this->add_rate( $rate ); } //...
If you try now to view your cart or go to the checkout page and select a country that is available for this shipping, you'll get a shipping cost displayed with this custom shipping method. Here is a picture with a country from zone 3:
Adding Restrictions
Since we have allowed the shipping method to register its rate even if the total weight of our cart is more than our limit, we have to add some restrictions. Our restriction will notify the customer that the order can't ship because of its weight.
Restriction Function
In our function we will look for the shipping method which has been chosen. If the method is our TutsPlus_Shipping_Method
then we will check for its weight limit and the total weight in the cart. If the weight from the cart exceeds the weight limit, we will notify our customer.
After the filter woocommerce_shipping_methods
add this code:
<?php function tutsplus_validate_order( $posted ) { $packages = WC()->shipping->get_packages(); $chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); if( is_array( $chosen_methods ) && in_array( 'tutsplus', $chosen_methods ) ) { foreach ( $packages as $i => $package ) { if ( $chosen_methods[ $i ] != "tutsplus" ) { continue; } $TutsPlus_Shipping_Method = new TutsPlus_Shipping_Method(); $weightLimit = (int) $TutsPlus_Shipping_Method->settings['weight']; $weight = 0; foreach ( $package['contents'] as $item_id => $values ) { $_product = $values['data']; $weight = $weight + $_product->get_weight() * $values['quantity']; } $weight = wc_get_weight( $weight, 'kg' ); if( $weight > $weightLimit ) { $message = sprintf( __( 'Sorry, %d kg exceeds the maximum weight of %d kg for %s', 'tutsplus' ), $weight, $weightLimit, $TutsPlus_Shipping_Method->title ); $messageType = "error"; if( ! wc_has_notice( $message, $messageType ) ) { wc_add_notice( $message, $messageType ); } } } } }
We are getting the packages which are separated by shipping. If all products are using the same shipping, it will return only one package with all the products. After that, we are getting the chosen methods, and then for each package we are checking if that package is shipped by our shipping method.
If the package is to be shipped with our shipping method, we are setting the weight limit from the settings, and after that we are calculating the total weight of our package. If the total weight exceeds our weight limit, it will notify the customer.
Notifying on Order Update
The order update happens each time the customer changes something on the checkout page. We will add the function tutsplus_validate_order
to the action woocommerce_review_order_before_cart_contents
. This action is called after everything has been set so that we can get the chosen shipping methods from session and the packages from shipping. Once the customer changes something, this action will trigger our function and add the notice if necessary.
Add this code after our function tutsplus_validate_order
:
<?php add_action( 'woocommerce_review_order_before_cart_contents', 'tutsplus_validate_order' , 10 );
Notifying on Checkout
When the customer clicks on the button to place the order or buy it, WooCommerce will process all the billing and shipping data alongside the cart contents and the chosen shipping.
If there are any WooCommerce error notices, it will stop the checkout process and display all those error notices to the customer. Since we have created a function that is using the session to get the chosen shipping methods, we have to add this function to an action that will be triggered after the session has been set. The action which will trigger just before WooCommerce checks if there is any error notice is woocommerce_after_checkout_validation
.
Let's add our function to that action:
<?php add_action( 'woocommerce_after_checkout_validation', 'tutsplus_validate_order' , 10 );
Full Code
Here is the full code for this custom shipping method, so if you had any trouble following this tutorial, you can look here:
<?php /** * Plugin Name: TutsPlus Shipping * Plugin URI: http://code.tutsplus.com/tutorials/create-a-custom-shipping-method-for-woocommerce--cms-26098 * Description: Custom Shipping Method for WooCommerce * Version: 1.0.0 * Author: Igor Benić * Author URI: http://www.ibenic.com * License: GPL-3.0+ * License URI: http://www.gnu.org/licenses/gpl-3.0.html * Domain Path: /lang * Text Domain: tutsplus */ if ( ! defined( 'WPINC' ) ) { die; } /* * Check if WooCommerce is active */ if ( in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', get_option( 'active_plugins' ) ) ) ) { function tutsplus_shipping_method() { if ( ! class_exists( 'TutsPlus_Shipping_Method' ) ) { class TutsPlus_Shipping_Method extends WC_Shipping_Method { /** * Constructor for your shipping class * * @access public * @return void */ public function __construct() { $this->id = 'tutsplus'; $this->method_title = __( 'TutsPlus Shipping', 'tutsplus' ); $this->method_description = __( 'Custom Shipping Method for TutsPlus', 'tutsplus' ); // Availability & Countries $this->availability = 'including'; $this->countries = array( 'US', // Unites States of America 'CA', // Canada 'DE', // Germany 'GB', // United Kingdom 'IT', // Italy 'ES', // Spain 'HR' // Croatia ); $this->init(); $this->enabled = isset( $this->settings['enabled'] ) ? $this->settings['enabled'] : 'yes'; $this->title = isset( $this->settings['title'] ) ? $this->settings['title'] : __( 'TutsPlus Shipping', 'tutsplus' ); } /** * Init your settings * * @access public * @return void */ function init() { // Load the settings API $this->init_form_fields(); $this->init_settings(); // Save settings in admin if you have any defined add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) ); } /** * Define settings field for this shipping * @return void */ function init_form_fields() { $this->form_fields = array( 'enabled' => array( 'title' => __( 'Enable', 'tutsplus' ), 'type' => 'checkbox', 'description' => __( 'Enable this shipping.', 'tutsplus' ), 'default' => 'yes' ), 'title' => array( 'title' => __( 'Title', 'tutsplus' ), 'type' => 'text', 'description' => __( 'Title to be display on site', 'tutsplus' ), 'default' => __( 'TutsPlus Shipping', 'tutsplus' ) ), 'weight' => array( 'title' => __( 'Weight (kg)', 'tutsplus' ), 'type' => 'number', 'description' => __( 'Maximum allowed weight', 'tutsplus' ), 'default' => 100 ), ); } /** * This function is used to calculate the shipping cost. Within this function we can check for weights, dimensions and other parameters. * * @access public * @param mixed $package * @return void */ public function calculate_shipping( $package ) { $weight = 0; $cost = 0; $country = $package["destination"]["country"]; foreach ( $package['contents'] as $item_id => $values ) { $_product = $values['data']; $weight = $weight + $_product->get_weight() * $values['quantity']; } $weight = wc_get_weight( $weight, 'kg' ); if( $weight <= 10 ) { $cost = 0; } elseif( $weight <= 30 ) { $cost = 5; } elseif( $weight <= 50 ) { $cost = 10; } else { $cost = 20; } $countryZones = array( 'HR' => 0, 'US' => 3, 'GB' => 2, 'CA' => 3, 'ES' => 2, 'DE' => 1, 'IT' => 1 ); $zonePrices = array( 0 => 10, 1 => 30, 2 => 50, 3 => 70 ); $zoneFromCountry = $countryZones[ $country ]; $priceFromZone = $zonePrices[ $zoneFromCountry ]; $cost += $priceFromZone; $rate = array( 'id' => $this->id, 'label' => $this->title, 'cost' => $cost ); $this->add_rate( $rate ); } } } } add_action( 'woocommerce_shipping_init', 'tutsplus_shipping_method' ); function add_tutsplus_shipping_method( $methods ) { $methods[] = 'TutsPlus_Shipping_Method'; return $methods; } add_filter( 'woocommerce_shipping_methods', 'add_tutsplus_shipping_method' ); function tutsplus_validate_order( $posted ) { $packages = WC()->shipping->get_packages(); $chosen_methods = WC()->session->get( 'chosen_shipping_methods' ); if( is_array( $chosen_methods ) && in_array( 'tutsplus', $chosen_methods ) ) { foreach ( $packages as $i => $package ) { if ( $chosen_methods[ $i ] != "tutsplus" ) { continue; } $TutsPlus_Shipping_Method = new TutsPlus_Shipping_Method(); $weightLimit = (int) $TutsPlus_Shipping_Method->settings['weight']; $weight = 0; foreach ( $package['contents'] as $item_id => $values ) { $_product = $values['data']; $weight = $weight + $_product->get_weight() * $values['quantity']; } $weight = wc_get_weight( $weight, 'kg' ); if( $weight > $weightLimit ) { $message = sprintf( __( 'Sorry, %d kg exceeds the maximum weight of %d kg for %s', 'tutsplus' ), $weight, $weightLimit, $TutsPlus_Shipping_Method->title ); $messageType = "error"; if( ! wc_has_notice( $message, $messageType ) ) { wc_add_notice( $message, $messageType ); } } } } } add_action( 'woocommerce_review_order_before_cart_contents', 'tutsplus_validate_order' , 10 ); add_action( 'woocommerce_after_checkout_validation', 'tutsplus_validate_order' , 10 ); }
Conclusion
The WooCommerce Shipping API enables you to create your own shipping method with simple steps. Limitations and availability can be set inside the shipping method when calculating the availability or the cost for the shipping method, but they can also be set outside the shipping method by using WooCommerce actions.
If you're looking for other utilities to help you build out your growing set of tools for WordPress or for code to study and become more well-versed in WordPress, don't forget to see what we have available in Envato Market.
If you have any questions about this or any other shipping method, you can post them in the comments below. You can also follow me on my blog or on twitter @igorbenic.
Comments