2

I want to charge custom shipping charge for few product available in store is it possible? i.e currently i am charging flat shipping chargefor all product how to charge custom/different price few product.

example : charging $5 flat charge to all product. But i want to charge $45 for product1, $99 for product2, $114 for product3 etc.

  • You want to set product wise rates right – Ankit Shah Dec 23 '16 at 1:09
  • check out this question it will help you magento.stackexchange.com/questions/136464/… – chirag Dec 23 '16 at 5:56
  • @AnkitShah you are right.so if there is multiple product in cart and all have different rate. Want to add all of them and show total in shipping method – user3443146 Dec 24 '16 at 4:56
2

Flatrate shipping is include in the core module Magento_OfflineShipping. I was able to accomplish what you were looking for by adding a custom flatrate product attribute and extending the Flaterate shipping method.

The code below has only been loosely tested against simple products and will only work if the flatrate shipping calculation is figured per item. You could do something similar when using the per order setting. The custom attribute is only added to the Default product attribute set during installation.

Vendor/Module/etc/module.xml

<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
  <module name="Vendor_Module" setup_version="0.0.1">
    <sequence>
      <module name="Magento_OfflineShipping" />
    </sequence>
  </module>
</config>

Vendor/Module/etc/di.xml

<?xml version="1.0"?>
<config>
  <type name="Magento\OfflineShipping\Model\Carrier\Flatrate\ItemPriceCalculator">
    <plugin name="vendor_module_offlineshipping_carrier_flatrate_itempricecalculator" type="Vendor\Module\Plugin\Flatrate\ItemPriceCalculator" />
  </type>
</config>

Vendor/Module/etc/catalog_attributes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/catalog_attributes.xsd">
  <group name="quote_item">
    <attribute name="custom_flat_rate"/>
  </group>
</config>

Vendor/Module/Plugin/Flatrate/ItemPriceCalculator.php

<?php
namespace Vendor\Module\Plugin\Flatrate;

use Magento\Checkout\Model\Session;

class ItemPriceCalculator
{
  protected $session;
  protected $quote;
  protected $customShippingPrices = [];
  protected $shippingPrices = [];
  protected $basePrice;
  protected $freeBoxes;
  protected $packageCount;
  protected $basePricePackageCount = 0;

  public function __construct(
    Session $session
  ) {
    $this->session = $session;
    $this->quote = $this->session->getQuote();
    $this->getCustomShippingPrices();
  }

  public function aroundGetShippingPricePerItem(
    $subject,
    $proceed,
    $request,
    $basePrice,
    $freeBoxes
  ) {
    $result = $proceed($request, $basePrice, $freeBoxes);
    if (!empty($this->customShippingPrices)) {
      $this->basePrice = $basePrice;
      $this->freeBoxes = $freeBoxes;
      $this->packageCount = $request->getPackageQty();

      if ($this->packageCount == $this->freeBoxes) {
        return 0;
      }

      $this->calculateBasePricePackageCount();
      $this->createShippingPricesArray();
      $this->applyFreeBoxes();

      return array_sum($this->shippingPrices);
    }
    return $result;
  }

  private function getCustomShippingPrices() {
    $items = $this->quote->getAllItems();
    foreach ($items as $item) {
      if ($customRate = $item->getProduct()->getCustomFlatRate()) {
        $this->customShippingPrices[] = $customRate;
      }
    }
  }

  private function calculateBasePricePackageCount() {
    if ($this->packageCount > $this->freeBoxes) {
      $adjustedCount = $this->packageCount - count($this->customShippingPrices);
    }
    $this->basePricePackageCount = ($adjustedCount > 0 ? $adjustedCount : 0);
  }

  private function createShippingPricesArray() {
    if ($this->basePricePackageCount) {
      $this->shippingPrices = array_fill(
        0, $this->basePricePackageCount, $this->basePrice
      );
    }

    foreach ($this->customShippingPrices as $key => $value) {
      $this->shippingPrices[] = $value;
    }
  }

  private function applyFreeBoxes() {
    arsort($this->shippingPrices);

    if ($this->freeBoxes) {
      $this->shippingPrices = array_slice(
        $this->shippingPrices, $this->freeBoxes
      );
    }
  }
}

Vendor/Module/Setup/InstallData.php

<?php
namespace Vendor\Module\Setup;

use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Attribute\Backend\Price;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;

class InstallData implements InstallDataInterface
{
  private $eavSetupFactory;

  public function __construct(
    EavSetupFactory $eavSetupFactory
  ) {
    $this->eavSetupFactory = $eavSetupFactory;
  }

  public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context) {
    $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

    $eavSetup->addAttribute(
      Product::ENTITY,
      'custom_flat_rate',
      [
        'backend' => Price::class,
        'frontend' => '',
        'label' => 'Flatrate Shipping Price',
        'type' => 'decimal',
        'input' => 'price',
        'global' => ScopedAttributeInterface::SCOPE_GLOBAL,
        'visible' => true,
        'required' => false,
        'user_defined' => false,
        'default' => null,
        'searchable' => false,
        'filterable' => false,
        'comparable' => false,
        'visible_on_front' => true,
        'used_in_product_listing' => false,
        'unique' => false,
        'apply_to' => Product\Type::TYPE_SIMPLE
      ]
    );

    $entityTypeId = $eavSetup->getEntityTypeId(Product::ENTITY);
    $attributeSetId = $eavSetup->getAttributeSetId($entityTypeId, 'Default');

    $attribute = $eavSetup->getAttribute($entityTypeId, 'custom_flat_rate');
    if ($attribute) {
      $eavSetup->addAttributeToGroup(
        $entityTypeId,
        $attributeSetId,
        'Product Details',
        $attribute['attribute_id'],
        35
      );
    }
  }
}

Vendor/Module/Test/Unit/Plugin/Flatrate/ItemPriceCalculatorTest.php

<?php
namespace Vendor\Module\Test\Unit\Plugin\Flatrate;

use Vendor\Module\Plugin\Flatrate\ItemPriceCalculator;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Checkout\Model\Session;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Item;
use Magento\Quote\Model\ResourceModel\Quote\Item\Collection as QuoteItemCollection;
use Magento\Catalog\Model\Product;

class ItemPriceCalculatorTest extends \PHPUnit_Framework_TestCase
{
  /** @var ObjectManager */
  protected $objectManager;

  /** @var ItemPriceCalculator */
  protected $itemPriceCalculatorPlugin;

  /** @var Session|MockObject */
  protected $sessionMock;

  /** @var \Magento\OfflineShipping\Model\Carrier\Flatrate\ItemPriceCalculator|MockObject */
  protected $itemPriceCalculatorMock;

  /** @var MockObject */
  protected $proceedMock;

  /** @var RateRequest|MockObject */
  protected $rateRequestMock;

  /** @var Quote|MockObject */
  protected $quoteMock;

  /** @var Item|MockObject */
  protected $quoteItemMock;

  /** @var Product|MockObject */
  protected $productMock;

  /** @var QuoteItemCollection|MockObject */
  protected $quoteItemCollectionMock;

  protected function setUp() {
    $this->objectManager = new ObjectManager($this);

    $this->sessionMock = $this->getMockBuilder(Session::class)
      ->disableOriginalConstructor()
      ->setMethods(['getQuote'])
      ->getMock();

    $this->quoteMock = $this->getMockBuilder(Quote::class)
      ->disableOriginalConstructor()
      ->setMethods(['getAllItems'])
      ->getMock();

    $this->quoteItemMock = $this->getMockBuilder(Item::class)
      ->disableOriginalConstructor()
      ->setMethods(['getProduct'])
      ->getMock();

    $this->quoteItemCollectionMock = $this->objectManager->getCollectionMock(
      QuoteItemCollection::class, [$this->quoteItemMock]
    );

    $this->productMock = $this->getMockBuilder(Product::class)
      ->disableOriginalConstructor()
      ->setMethods(['getCustomFlatRate'])
      ->getMock();

    $this->sessionMock->expects($this->once())
      ->method('getQuote')
      ->willReturn($this->quoteMock);

    $this->quoteMock->expects($this->once())
      ->method('getAllItems')
      ->willReturn($this->quoteItemCollectionMock);

    $this->quoteItemMock->expects($this->any())
      ->method('getProduct')
      ->willReturn($this->productMock);

    $this->itemPriceCalculatorMock = $this->getMockBuilder(
      \Magento\OfflineShipping\Model\Carrier\Flatrate\ItemPriceCalculator::class)
      ->disableOriginalConstructor()
      ->getMock();

    $this->rateRequestMock = $this->getMockBuilder(RateRequest::class)
      ->setMethods(['getPackageQty'])
      ->getMock();
  }

  /**
   * @param string $basePrice
   * @param int $freeBoxes
   * @param int $packageCount
   * @param array $customPrices
   * @param float $expectedResult
   * @dataProvider aroundGetShippingPricePerItemDataProvider
   */
  public function testAroundGetShippingPricePerItem(
    $basePrice,
    $freeBoxes,
    $packageCount,
    $customPrices,
    $expectedResult
  ) {
    $this->itemPriceCalculatorPlugin = $this->objectManager->getObject(
      ItemPriceCalculator::class,
      [
        'session' => $this->sessionMock,
        'customShippingPrices' => $customPrices
      ]
    );

    $this->proceedMock = $this->getMock(\stdClass::class, ['__invoke']);
    $this->proceedMock->expects($this->any())
      ->method('__invoke')
      ->with($this->rateRequestMock, $basePrice, $freeBoxes)
      ->willReturn($packageCount * $basePrice - $freeBoxes * $basePrice);

    $this->rateRequestMock->expects($this->any())
      ->method('getPackageQty')
      ->willReturn($packageCount);

    $result = $this->itemPriceCalculatorPlugin->aroundGetShippingPricePerItem(
      $this->itemPriceCalculatorMock,
      $this->proceedMock,
      $this->rateRequestMock,
      $basePrice,
      $freeBoxes
    );

    $this->assertEquals($expectedResult, $result);
  }

  public function aroundGetShippingPricePerItemDataProvider() {
    return [
    // $basePrice, $freeBoxes, $packageCount, $customPrices, $expected
      ['5.00', 0, 1, [3.99], 3.99],
      ['5.00', 0, 2, [3.99], 8.99],
      ['5.00', 1, 2, [3.99], 3.99],
      ['5.00', 2, 2, [3.99], 0],
      ['5.00', 2, 3, [8.99], 5],
      ['5.00', 0, 3, [2,2], 9],
      ['5.00', 1, 3, [2,2], 4]
    ];
  }
}

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.