Fixing “hasMerchantReturnPolicy” and “shippingDetails” for WooCommerce
Having installed WooCommerce 9.1.2 (at the time of writting this post) Google Search Console reports two warnings:
- Missing field “hasMerchantReturnPolicy” (in “offers”)
- Missing field “shippingDetails” (in “offers”)
It won’t hurt your free Google Merchant listings cause you can set there your Return Policy and your shipping details. If you want to get rid of this annoying message in Google Search Console you can make update to your Schema Markup in a few easy steps. No WordPress plugins required.
It is always a good idea to have a child theme installed to your current one, because changes you will make there won’t be overwritten by the main theme update.
In your child theme folder, locate (or create) a file named functions.php. This file will allow us to make an update to the current schema markup made by WooCommerce team.
My web store has a free shipping method related to the basket value. The value is 100 (or more). User gets free shipping after exceeding this order value. The goal is to show the shipping fee if the basket value is lower than 100 and show 0 otherwise.
I have simply added these two fields: hasMerchantReturnPolicy and shippingDetails editting functions.php file this way:
// ------------------------ extends product schema markup for missing shippingDetails & hasMerchantReturnPolicy --------------------------
add_filter('woocommerce_structured_data_product_offer', 'add_custom_schema_to_woocommerce_product_offer', 10, 2);
function add_custom_schema_to_woocommerce_product_offer($markup, $product) {
// Get your minimum shipping cost and free shipping threshold
$minimum_shipping_cost = get_minimum_shipping_cost();
$free_shipping_threshold = get_free_shipping_threshold();
// Check if your product qualifies for free shipping
$shipping_rate_value = $product->get_price() >= $free_shipping_threshold ? 0.00 : $minimum_shipping_cost;
$return_policy = [
"@type" => "MerchantReturnPolicy",
"name" => "14-Day Return Policy",
"returnPolicyCategory" => "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays" => "14",
"returnMethod" => "https://schema.org/ReturnByMail",
"returnFees" => "https://schema.org/ReturnFeesCustomerResponsibility",
"applicableCountry" => "PL"
];
$shipping_details = [
"@type" => "OfferShippingDetails",
"shippingRate" => [
"@type" => "MonetaryAmount",
"value" => number_format($shipping_rate_value, 2),
"currency" => get_woocommerce_currency()
],
"deliveryTime" => [
"@type" => "ShippingDeliveryTime",
"businessDays" => "1-7",
"cutoffTime" => "23:59:59",
"handlingTime" => [
"@type" => "QuantitativeValue",
"minValue" => "0",
"maxValue" => "1",
"unitCode" => "DAY"
],
"transitTime" => [
"@type" => "QuantitativeValue",
"minValue" => "1",
"maxValue" => "2",
"unitCode" => "DAY"
]
],
"shippingDestination" => [
"@type" => "DefinedRegion",
"addressCountry" => "PL"
]
];
// Add the return policy and shipping details to the existing product offer markup
$markup['hasMerchantReturnPolicy'] = $return_policy;
$markup['shippingDetails'] = $shipping_details;
return $markup;
}
function get_minimum_shipping_cost() {
$lowest_cost = null;
// Download shipping zones
$shipping_zones = WC_Shipping_Zones::get_zones();
foreach ($shipping_zones as $zone) {
$shipping_methods = $zone['shipping_methods'];
foreach ($shipping_methods as $method) {
if ($method->id !== 'free_shipping' && isset($method->cost)) {
$cost = floatval(preg_replace('/,/','.',$method->cost));
if (is_null($lowest_cost) || $cost < $lowest_cost) { $lowest_cost = $cost; } } } } // Get shipping methods for your default zone $default_zone = WC_Shipping_Zones::get_zone(0); $shipping_methods = $default_zone->get_shipping_methods(true);
foreach ($shipping_methods as $method) {
if ($method->id !== 'free_shipping' && isset($method->cost)) {
$cost = floatval(preg_replace('/,/','.',$method->cost));
if (is_null($lowest_cost) || $cost < $lowest_cost) { $lowest_cost = $cost; } } } return is_null($lowest_cost) ? 0 : $lowest_cost; } function get_free_shipping_threshold() { // Get shipping settings for shipping zones $shipping_zones = WC_Shipping_Zones::get_zones(); foreach ($shipping_zones as $zone) { $shipping_methods = $zone['shipping_methods']; foreach ($shipping_methods as $method) { if ($method->id === 'free_shipping' && isset($method->min_amount)) {
return floatval(preg_replace('/,/','.',$method->min_amount));
}
}
}
// Also check the default shipping zone
$default_zone = WC_Shipping_Zones::get_zone(0);
$shipping_methods = $default_zone->get_shipping_methods(true);
foreach ($shipping_methods as $method) {
if ($method->id === 'free_shipping' && isset($method->min_amount)) {
return floatval(preg_replace('/,/','.',$method->min_amount));
}
}
// Default free shipping threshold if not set
return 100; // or any default value
}
// ------------------------------------------------------------------------------------------------------------------------------------------
Now Google Search Console need a click to check the fix and you’re done. Please be sure to update the schema to fit your needs under shipping policy and shiping details.