Description
Preconditions (*)
When programatically creating a quote, if the $quote->getShippingAddress()
is called BEFORE $quote->getBillingAddress()
, the $quote->getShippingAmount()
is always zero. If the $quote->getBillingAddress()
is omitted, this doesn't happen.
Steps to reproduce (*)
Code e.g.:
Given this snipped of code:
/** @var \Magento\Quote\Model\Quote $quote */
$quote = $this->objectManager->create(\Magento\Quote\Model\QuoteFactory::class)->create();
/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
$productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
$product = $productRepository->get('simple');
$quote->addProduct($product, 1);
// set shipping address
$quote->getShippingAddress()->addData([
'firstname' => 'Test',
'lastname' => 'Test',
'city' => 'Test',
'telephone' => '+1234567890',
'postcode' => '12345',
'street' => ['Test Ave 12'],
'country_id' => 'SE',
]);
$address->setCollectShippingRates(true);
$address->collectShippingRates();
$address->setShippingMethod('flatrate_flatrate');
$address->save();
$quote->getPayment()->setMethod('checkmo');
$quote->collectTotals();
$quote->save();
var_dump($quote->getShippingAmount());
var_dump($quote->getTotals()['shipping']->getValue());
This gives me this output, as I expected:
Expected result (*)
float(5)
float(5)
But if I add a call to $quote->getBillingAddress()
AFTER the $quote->getShippingAddress()
call, it gives me this erroneous output:
Actual result (*)
int(0)
int(0)
Moving the $quote->getBillingAddress()
BEFORE the $quote->getShippingAddress()
gives me the expected result again.
As I dug a little in the code, I found this in the Quote\TotalsCollector:
public function collect(\Magento\Quote\Model\Quote $quote)
{
// ...
/** @var \Magento\Quote\Model\Quote\Address $address */
foreach ($quote->getAllAddresses() as $address) {
$addressTotal = $this->collectAddressTotals($quote, $address);
$total->setShippingAmount($addressTotal->getShippingAmount());
$total->setBaseShippingAmount($addressTotal->getBaseShippingAmount());
$total->setShippingDescription($addressTotal->getShippingDescription());
$total->setSubtotal((float)$total->getSubtotal() + $addressTotal->getSubtotal());
$total->setBaseSubtotal((float)$total->getBaseSubtotal() + $addressTotal->getBaseSubtotal());
$total->setSubtotalWithDiscount(
(float)$total->getSubtotalWithDiscount() + $addressTotal->getSubtotalWithDiscount()
);
$total->setBaseSubtotalWithDiscount(
(float)$total->getBaseSubtotalWithDiscount() + $addressTotal->getBaseSubtotalWithDiscount()
);
$total->setGrandTotal((float)$total->getGrandTotal() + $addressTotal->getGrandTotal());
$total->setBaseGrandTotal((float)$total->getBaseGrandTotal() + $addressTotal->getBaseGrandTotal());
}
// ...
}
As you can see, it iterates over $quote->getAllAddresses()
and then later uses $total->setShippingAmount()
. During debug I could see that in the first iteration the shipping amount was set to the shippingAmount just fine, just to later get it overwritten by the second address. Given the billing address does not have any ShipmentAssignements
, it's shipping amount will always be zero.
Proposed solutions (*)
Either:
- Make sure the shipping address is always the last one to be calculated; or
- Make sure theTotalsCollector adds instead of overwriting the amount;
Is there another suggestion? Which one would be the preferred way?
Thanks!