In Q4 2023, Google rolled out an update requiring e-commerce websites to include shipping and return information in search results. Specifically, the attributes hasMerchantReturnPolicy and shippingDetails in the product’s schema offer. Although this is not mandatory, it triggers quite an annoying warning in Search Console.
You can find more information at: Shipping and Returns Information – Google Search Central Blog
The code below supports:
- Default WooCommerce schema
- Yoast SEO
- Rank Math
- Schema Pro (Remember to update the settings to apply the new schema).
How to fix the hasMerchantReturnPolicy and shippingDetails error for WooCommerce
Insert the code below into wp-content/themes/{your-theme}/functions.php
.
/*
* hasMerchantReturnPolicy và shippingDetails
*
* */
add_filter( 'woocommerce_structured_data_product_offer', 'devvn_woocommerce_structured_data_product_offer' );
add_filter( 'wpseo_schema_product', 'devvn_wpseo_schema_product' );
add_filter( 'rank_math/snippet/rich_snippet_product_entity', 'devvn_rich_snippet_product_entity', 99 );
add_filter( 'wp_schema_pro_schema_product', 'devvn_wp_schema_pro_schema_product' );
function get_hasMerchantReturnPolicy(){
return '{
"@type": "MerchantReturnPolicy",
"applicableCountry": "vi",
"returnPolicyCategory": "https://schema.org/MerchantReturnFiniteReturnWindow",
"merchantReturnDays": "7",
"returnMethod": "https://schema.org/ReturnByMail",
"returnFees": "https://schema.org/FreeReturn"
}';
}
function get_shippingDetails(){
return '{
"@type": "OfferShippingDetails",
"shippingRate": {
"@type": "MonetaryAmount",
"value": "0",
"currency": "VND"
},
"deliveryTime": {
"@type": "ShippingDeliveryTime",
"businessDays": {
"@type": "OpeningHoursSpecification",
"dayOfWeek": [
"https://schema.org/Monday",
"https://schema.org/Tuesday",
"https://schema.org/Wednesday",
"https://schema.org/Thursday",
"https://schema.org/Friday"
]
},
"handlingTime": {
"@type": "QuantitativeValue",
"minValue": "0",
"maxValue": "3",
"samedaydelivery" : "Yes",
"unitCode": "DAY"
},
"transitTime": {
"@type": "QuantitativeValue",
"minValue": "0",
"maxValue": "3",
"samedaydelivery" : "Yes",
"unitCode": "DAY"
}
},
"shippingDestination": [
{
"@type": "DefinedRegion",
"addressCountry": "VN",
"addressRegion": ["VN"]
}
]
}';
}
function devvn_wpseo_schema_product($data){
if(isset($data['offers'])){
$hasMerchantReturnPolicy = get_hasMerchantReturnPolicy();
$shippingDetails = get_shippingDetails();
foreach ($data['offers'] as $key => $offer){
if(!isset($offers['hasMerchantReturnPolicy']) && $hasMerchantReturnPolicy){
$data['offers'][$key]['hasMerchantReturnPolicy'] = json_decode($hasMerchantReturnPolicy, true);
}
if(!isset($offers['shippingDetails']) && $shippingDetails){
$data['offers'][$key]['shippingDetails'] = json_decode($shippingDetails, true);
}
}
}
return $data;
}
function devvn_rich_snippet_product_entity($entity){
global $product;
if(!is_singular('product') || !$product || is_wp_error($product)) return $entity;
$hasMerchantReturnPolicy = get_hasMerchantReturnPolicy();
$shippingDetails = get_shippingDetails();
if(!isset($entity['offers']['hasMerchantReturnPolicy']) && $hasMerchantReturnPolicy){
$entity['offers']['hasMerchantReturnPolicy'] = json_decode($hasMerchantReturnPolicy, true);
}
if(!isset($entity['offers']['shippingDetails']) && $shippingDetails){
$entity['offers']['shippingDetails'] = json_decode($shippingDetails, true);
}
//Fix Rankmath pro
if(isset($entity['hasVariant']) && $entity['hasVariant']){
foreach ($entity['hasVariant'] as $k=>$productList){
if(!isset($productList['offers']['hasMerchantReturnPolicy']) && $hasMerchantReturnPolicy){
$entity['hasVariant'][$k]['offers']['hasMerchantReturnPolicy'] = json_decode($hasMerchantReturnPolicy, true);
}
if(!isset($productList['offers']['shippingDetails']) && $shippingDetails){
$entity['hasVariant'][$k]['offers']['shippingDetails'] = json_decode($shippingDetails, true);
}
}
}
return $entity;
}
function devvn_wp_schema_pro_schema_product($schema){
if(isset($schema['offers']) && apply_filters( 'wp_schema_pro_remove_product_offers', true )) {
$hasMerchantReturnPolicy = get_hasMerchantReturnPolicy();
$shippingDetails = get_shippingDetails();
if (!isset($schema['offers']['hasMerchantReturnPolicy']) && $hasMerchantReturnPolicy) {
$schema['offers']['hasMerchantReturnPolicy'] = json_decode($hasMerchantReturnPolicy, true);
}
if (!isset($schema['offers']['shippingDetails']) && $shippingDetails) {
$schema['offers']['shippingDetails'] = json_decode($shippingDetails, true);
}
}
return $schema;
}
function devvn_woocommerce_structured_data_product_offer($offers){
$hasMerchantReturnPolicy = get_hasMerchantReturnPolicy();
$shippingDetails = get_shippingDetails();
if(!isset($offers['hasMerchantReturnPolicy']) && $hasMerchantReturnPolicy){
$offers['hasMerchantReturnPolicy'] = json_decode($hasMerchantReturnPolicy, true);
}
if(!isset($offers['shippingDetails']) && $shippingDetails){
$offers['shippingDetails'] = json_decode($shippingDetails, true);
}
return $offers;
}
In the code above, pay attention to the following parameters. Make sure to change them as needed, or you can use the default values provided:
In the get_hasMerchantReturnPolicy
function, there are:
- merchantReturnDays: The number of days for returns
- returnFees: Return fee
- returnMethod: Method of return => By default, the code uses return via email, within 7 days, and with no return fees.
In the get_shippingDetails
function, there are:
- shippingRate > value: The shipping cost
- shippingRate > currency: The currency of the shipping cost
- deliveryTime: Operating days for delivery
- shippingDestination: Shipping country
Make sure to review and replace the values as needed. Or, you can use the default values provided.
Good luck!”