Skip to content

Cancel with "Renew quote-id" crashes: OrderAddressExtension::setDiscounts() on the new quote #244

@marcepartment

Description

@marcepartment

Module version: 4.0.12 (also present on current 4.0.x)
Magento: 2.4.x

Summary

When a payment is cancelled/denied and payment/paynl/cancel_behaviour is set to "Renew quote-id", Finish::initiateNewQuote() copies the cancelled order address onto the quote address with a raw addData($orderAddress->getData()). The order address' data array includes the extension_attributes key, which holds a Magento\Sales\Api\Data\OrderAddressExtension instance. That object is now stored on the quote address.

On the next total collection, Magento\SalesRule\Model\Quote\Discount::collect() calls $address->getExtensionAttributes()->setDiscounts([]). Because the quote address now returns the leaked OrderAddressExtension (which has no discounts extension attribute), this fatals:

Error: Call to undefined method Magento\Sales\Api\Data\OrderAddressExtension::setDiscounts()
in vendor/magento/module-sales-rule/Model/Quote/Discount.php:165
#0 .../module-quote/Model/Quote/TotalsCollector.php(261): ...Discount->collect()
#1 .../module-quote/Model/Quote/TotalsCollector.php(156): ...->collectAddressTotals()
...
#6 .../module-quote/Model/Quote.php(2023): ...TotalsCollector\Interceptor->collect()

The customer is shown the Magento error page after cancelling on the Pay/iDEAL Wero page.

Steps to reproduce

  1. Enable Pay. test mode.
  2. Set the cancel behaviour ("Renew quote-id") so initiateNewQuote() runs (i.e. not "Maintain quote-id").
  3. Start a checkout, go to Pay/iDEAL, and cancel the payment.
  4. Observe the fatal in var/log/exception.log and the Magento error page.

Root cause

Controller/Checkout/Finish.php, initiateNewQuote():

$newBillingAddress->addData($billingAddress->getData());   // copies extension_attributes (OrderAddressExtension)
$newShippingAddress->addData($shippingAddress->getData()); // same

extension_attributes (and order-address identity keys such as entity_id / address_id / address_type) should not be copied verbatim from a sales order address onto a quote address.

Suggested fix

Filter out the unsafe keys before addData():

$unsafeAddressKeys = array_flip(['extension_attributes', 'entity_id', 'address_id', 'address_type']);

$billingAddress = $cancelledOrder->getBillingAddress();
if ($billingAddress) {
    $newQuote->getBillingAddress()
        ->addData(array_diff_key($billingAddress->getData(), $unsafeAddressKeys));
}

$shippingAddress = $cancelledOrder->getShippingAddress();
if ($shippingAddress) {
    $newQuote->getShippingAddress()
        ->addData(array_diff_key($shippingAddress->getData(), $unsafeAddressKeys));
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions