Skip to content

Billing address needs to come first #26209

Open
@kassner

Description

@kassner

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:

  1. Make sure the shipping address is always the last one to be calculated; or
  2. Make sure theTotalsCollector adds instead of overwriting the amount;

Is there another suggestion? Which one would be the preferred way?

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    Component: Framework/CodeComponent: QuoteIssue: Clear DescriptionGate 2 Passed. Manual verification of the issue description passedIssue: ConfirmedGate 3 Passed. Manual verification of the issue completed. Issue is confirmedIssue: Format is validGate 1 Passed. Automatic verification of issue format passedIssue: Ready for WorkGate 4. Acknowledged. Issue is added to backlog and ready for developmentPriority: P3May be fixed according to the position in the backlog.Progress: ready for devReproduced on 2.4.xThe issue has been reproduced on latest 2.4-develop branchRisk: highSeverity: S3Affects non-critical data or functionality and does not force users to employ a workaround.Triage: Dev.ExperienceIssue related to Developer Experience and needs help with Triage to Confirm or Reject it

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions