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
- Enable Pay. test mode.
- Set the cancel behaviour ("Renew quote-id") so
initiateNewQuote() runs (i.e. not "Maintain quote-id").
- Start a checkout, go to Pay/iDEAL, and cancel the payment.
- 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));
}
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_behaviouris set to "Renew quote-id",Finish::initiateNewQuote()copies the cancelled order address onto the quote address with a rawaddData($orderAddress->getData()). The order address' data array includes theextension_attributeskey, which holds aMagento\Sales\Api\Data\OrderAddressExtensioninstance. 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 leakedOrderAddressExtension(which has nodiscountsextension attribute), this fatals:The customer is shown the Magento error page after cancelling on the Pay/iDEAL Wero page.
Steps to reproduce
initiateNewQuote()runs (i.e. not "Maintain quote-id").var/log/exception.logand the Magento error page.Root cause
Controller/Checkout/Finish.php,initiateNewQuote():extension_attributes(and order-address identity keys such asentity_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():