1

I want to display Current product shipping rate on the product detail page.

I have tried below code,

$qty = 1;
        $countryId='IN';
        $storeId = 1;
        $zipcode = 360002;
        $product = $this->_catalogProduct->load($id);
        $item = $objectManager->create('Magento\Quote\Model\Quote\Item')->setProduct($product)->setQty(1);
        $store = $objectManager->create('Magento\Store\Model\Store')->load($storeId);
        $request = $objectManager->create('Magento\Quote\Model\Quote\Address\RateRequest')->setAllItems(array($item))
            ->setDestCountryId($country)
            ->setPackageValue($product->getFinalPrice())
            ->setOrigPostcode($zipcode)  
            ->setPackageValueWithDiscount($product->getFinalPrice())
            ->setPackageWeight($product->getWeight())
            ->setPackageQty(1)
            ->setPackagePhysicalValue($product->getFinalPrice())
            ->setFreeMethodWeight(0)
            ->setStoreId($store->getId())
            ->setWebsiteId($store->getWebsiteId())
            ->setFreeShipping(0)
            ->setBaseCurrency($store->getBaseCurrency())
            ->setBaseSubtotalInclTax($product->getFinalPrice());
         $result = $this->_rateCollector->create()->collectRates($request)->getResult();

I want to display more than one enabled shipping methods rates in product display page. I have tried this code but i am getting this kind of error.

Uncaught Error: Call to a member function getItemsQty() on null in/vendor/magento/module-quote/Model/Quote/Item.php:275

Please help me on this.

3 Answers 3

4

Although Manish's answer does show how to create a block on the product page to obtain the available shipping rates, the REST API used in the tutorial (rest/default/V1/carts/mine/estimate-shipping-methods) only fetches this for the items that are already in the cart, and not just for the currently viewed product. This is because Magento relies on items that are added to the active quote to obtain the available methods and costs, of which was fetched from the database and utilised. Despite this, we can create a blank quote in memory without saving it into the database, fill in the required data, and then apply this to the shipping calculator.

The best approach to this is to create a new API endpoint, which you can then swap out the endpoint defined in the tutorial with yours.

Interface

<?php

namespace Your\Module\Api;

use Magento\Quote\Api\Data\AddressInterface;

/**
 * Interface ShipmentForProductEstimationInterface
 * @api
 * @since 100.0.7
 */
interface ShipmentForProductEstimationInterface
{
    /**
     * Estimate shipping by address and product, and return list of available shipping methods
     * @param string $sku
     * @param AddressInterface $address
     * @return \Magento\Quote\Api\Data\ShippingMethodInterface[] An array of shipping methods
     */
    public function estimateByProductAndAddress($sku, AddressInterface $address);
}

Model

<?php

namespace Your\Module\Model\Api;

use Magento\Framework\App\ObjectManager;

class ShipmentForProductEstimation implements \Your\Module\Api\ShipmentForProductEstimationInterface {

    /**
     * Shipping method converter
     *
     * @var \Magento\Quote\Model\Cart\ShippingMethodConverter
     */
    protected $converter;

    /**
     * @var \Magento\Quote\Model\Quote\TotalsCollector
     */
    protected $totalsCollector;

    /**
     * @var \Magento\Framework\Reflection\DataObjectProcessor
     */
    protected $dataProcessor;

    /**
     * @var \Magento\Quote\Model\Quote
     */
    protected $quote;

    /**
     * @var \Magento\Catalog\Api\ProductRepositoryInterface
     */
    protected $product;

    /**
     * @var \Magento\Quote\Model\Quote\Item
     */
    protected $item;

    public function __construct(
        \Magento\Quote\Model\Cart\ShippingMethodConverter $converter,
        \Magento\Quote\Model\Quote\TotalsCollector $totalsCollector,
        \Magento\Quote\Model\Quote $quote,
        \Magento\Catalog\Api\ProductRepositoryInterface $product,
        \Magento\Quote\Model\Quote\Item $item
    ) {
        $this->converter = $converter;
        $this->totalsCollector = $totalsCollector;
        $this->quote = $quote;
        $this->product = $product;
        $this->item = $item;
    }

    /**
     * @inheritdoc
     */
    public function estimateByProductAndAddress($sku, \Magento\Quote\Api\Data\AddressInterface $address)
    {
        $output = [];

        $address->setCountryId('IN');

        $p = $this->product->get($sku);

        $this->item->setProduct($p);
        $this->item->setQty(1);

        $this->quote->addItem( $this->item );

        $shippingAddress = $this->quote->getShippingAddress();
        $shippingAddress->addData($this->extractAddressData($address));
        $shippingAddress->setCollectShippingRates(true);

        $this->totalsCollector->collectAddressTotals($this->quote, $shippingAddress);
        $shippingRates = $shippingAddress->getGroupedAllShippingRates();
        foreach ($shippingRates as $carrierRates) {
            foreach ($carrierRates as $rate) {
                $output[] = $this->converter->modelToDataObject($rate, $this->quote->getQuoteCurrencyCode());
            }
        }
        return $output;

    }

    /**
     * Get transform address interface into Array
     *
     * @param \Magento\Framework\Api\ExtensibleDataInterface  $address
     * @return array
     */
    protected function extractAddressData($address)
    {
        $className = \Magento\Customer\Api\Data\AddressInterface::class;
        if ($address instanceof \Magento\Quote\Api\Data\AddressInterface) {
            $className = \Magento\Quote\Api\Data\AddressInterface::class;
        } elseif ($address instanceof \Magento\Quote\Api\Data\EstimateAddressInterface) {
            $className = \Magento\Quote\Api\Data\EstimateAddressInterface::class;
        }
        return $this->getDataObjectProcessor()->buildOutputDataArray(
            $address,
            $className
        );
    }

    /**
     * Gets the data object processor
     *
     * @return \Magento\Framework\Reflection\DataObjectProcessor
     */
    protected function getDataObjectProcessor()
    {
        if ($this->dataProcessor === null) {
            $this->dataProcessor = ObjectManager::getInstance()->get(\Magento\Framework\Reflection\DataObjectProcessor::class);
        }
        return $this->dataProcessor;
    }

}

di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Your\Module\Api\ShipmentForProductEstimationInterface" type="Your\Module\Model\Api\ShipmentForProductEstimation" />
</config>

webapi.xml

<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
    <route url="/V1/shipping/estimate/:sku" method="POST">
        <service class="Your\Module\Api\ShipmentForProductEstimationInterface" method="estimateByProductAndAddress"/>
        <resources>
            <resource ref="anonymous" />
        </resources>
    </route>
</routes>

If you are familiar with API endpoints, you will recognise that this can be called via /rest/default/V1/shipping/estimate/:sku, where :sku is the product SKU you would like to check the shipping methods for. The POST object and return JSON will be exactly the same as that of carts/mine/estimate-shipping-methods, so they are interchangeable.

If the rates displayed are different from when you add the item into the active cart, then that suggests the shipping rate fetches the item and/or address data from the session rather than the Magento\Quote\Model\Quote\Address\RateRequest object - if this is the case, then you may need to alter those shipping classes, or contact the developer to have this looked into.

3
  • This helped me a lot. to debug and i understand. Jul 1, 2020 at 9:18
  • if i call /rest/default/V1/shipping/estimate/:sku i got this <response> <message>Specified request cannot be processed.</message> <trace/> </response>
    – krybbio
    Jul 8, 2020 at 14:50
  • removing default from url works /rest//V1/shipping/estimate/:sku. Magento 2.3.5
    – krybbio
    Jul 8, 2020 at 16:11
0

The Smithee aproach works, but what about configurable products if one of them has differents size? In frontend doesnt show SKU, how to get SKU from a simpleproduct linked with a configurableproduct?

3
  • So your question is: How do I get the SKU of a child product by knowing the SKU of the configurable parent product? Sep 28, 2020 at 11:13
  • 2
    Yes I forgot I'd asked it but I was able to have it working... I did it a JavaScript got a JSON from $('#product_addtocart_form') and get super_attribute value, so send it to my PHP class, whose I verify the product type and if its a configurable, I use the method getProductByAttributes from \Magento\ConfigurableProduct\Model\Product\Type\Configurable, once it returns a product I just add it to quote. If someone need I can explain it better or post the code, but its easy to understand I guess...
    – jefferson
    Oct 5, 2020 at 18:17
  • @jefferson yes can you please provide some details about your implementation for configurable products.
    – Haris
    Dec 2, 2021 at 15:37
0

Refer below link for displaying Shipping Rate on Product Details Page

https://webkul.com/blog/how-to-get-estimate-shipping-in-magento2-using-js/

1- Create catalog_product_view.xml file at app/code/Test/Module/view/frontend/layout path.Using this file call block at product view page.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="checkout" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="content">
             <block class="\Magento\Framework\View\Element\Template" name="estimaterate.js" template="Test_Module::estimaterate.phtml" cacheable="false" />
        </referenceBlock>
    </body>
</page>

2- Create template file which is define in layout file.app/code/Test/Module/view/frontend/templates/estimaterate.phtml

In this file add only a single button. With on click of this button send to request for estimate shipping calculation. And also call the js file.

<button id="getrate">Click to get Shipping rate</button>
<script type="text/x-magento-init">
    {
        "*": {
            "estimateRate":  {}
        }
    }
</script>

3 – Create app/code/Test/Module/view/frontend/requirejs-config.js

var config = {
    map: {
        '*': {
            estimateRate : 'Test_Module/js/estimaterate',
        }
    }
};

4 – Create app/code/Test/Module/view/frontend/web/js/estimaterate.js. In this file call rest/default/V1/carts/mine/estimate-shipping-methods this web API is defined as Magento default. Using this we can get an estimated rate for only login customers. If you want to calculate estimate shipping for guests then you have to call rest/default/V1/guest-carts/:quoteId/estimate-shipping-methods and provide cart id in parameters. You can check this file for reference vendor/magento/module-checkout/view/frontend/web/js/model/resource-url-manager.js

define([
    "jquery",
    'mage/storage',
    'Magento_Checkout/js/model/error-processor'
], function ($,storage, errorProcessor) {
    'use strict';
    $.widget('mage.estimateRate', {
        options: {
        },
        _create: function () {
            var self = this;
            $(document).ready(function () {
                $('#product-addtocart-button').after($('#getrate'));
            });
            $('#getrate').on('click', function (e) {
                var address;
                var serviceUrl = 'rest/default/V1/carts/mine/estimate-shipping-methods',
                payload = JSON.stringify({
                        address: {
                            'city': 'New York',
                            'country_id': 'US',
                            'postcode': '98001',
                        }
                    }
                );
                self.getRate(payload,serviceUrl);
            });
        },
        getRate: function (payload,serviceUrl) {
            storage.post(
                serviceUrl, payload, false
                ).done(
                    function (result) {
                        console.log(result);
                    }
                ).fail(
                    function (response) {
                        errorProcessor.process(response);
                    }
                );
        },
    });
    return $.mage.estimateRate;
});
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.