Skip to content

Commit b776c17

Browse files
committed
Fixed a bug where update cart could return unnecessary validation errors
1 parent b84c885 commit b776c17

4 files changed

Lines changed: 62 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release Notes for Craft Commerce
22

3+
## Unreleased
4+
5+
- Fixed a bug where the `commerce/cart/update-cart` action could return unnecessary validation errors. ([3873](https://github.com/craftcms/commerce/issues/3873))
6+
37
## 4.9.2 - 2025-08-20
48

59
- Fixed a PHP error that could occur when saving an order from an `EVENT_ORDER_STATUS_CHANGE` event handler. ([#4101](https://github.com/craftcms/commerce/issues/4101))

src/elements/Variant.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -882,7 +882,7 @@ public function populateLineItem(LineItem $lineItem): void
882882
// Since we do not have a proper stock reservation system, we need deduct stock if they have more in the cart than is available, and to do this quietly.
883883
// If this occurs in the payment request, the user will be notified the order has changed.
884884
if (($order = $lineItem->getOrder()) && !$order->isCompleted) {
885-
if (($lineItem->qty > $this->stock) && !$this->hasUnlimitedStock) {
885+
if (($lineItem->qty > $this->stock) && !$this->hasUnlimitedStock && $this->stock > 0) {
886886
/** @var Order $order */
887887
$message = Craft::t('commerce', '{description} only has {stock} in stock.', ['description' => $lineItem->getDescription(), 'stock' => $this->stock]);
888888
/** @var OrderNotice $notice */

src/elements/traits/OrderValidatorsTrait.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ public function validateAddressReuse(string $attribute): void
111111
*/
112112
public function validateLineItems(): void
113113
{
114+
OrderHelper::normalizeLineItemPurchasableAvailability($this);
114115
OrderHelper::mergeDuplicateLineItems($this);
115116

116117
foreach ($this->getLineItems() as $key => $lineItem) {

src/helpers/Order.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77

88
namespace craft\commerce\helpers;
99

10+
use Craft;
11+
use craft\commerce\base\Purchasable;
1012
use craft\commerce\elements\Order as OrderElement;
13+
use craft\commerce\elements\Variant;
14+
use craft\commerce\models\OrderNotice;
15+
use craft\commerce\Plugin;
16+
use yii\base\InvalidConfigException;
1117

1218
/**
1319
* Order helper
@@ -44,4 +50,54 @@ public static function mergeDuplicateLineItems(OrderElement $order): bool
4450

4551
return $lineItems > $lineItemsByKey;
4652
}
53+
54+
/**
55+
* Removes any line items from the cart that are no longer available.
56+
* If a line item is available but the quantity is more than the available stock,
57+
* the quantity will be reduced to the available stock.
58+
* A notice will be added to the cart for each change.
59+
*
60+
* @param OrderElement $order
61+
* @return void
62+
* @throws InvalidConfigException
63+
* @since 4.9.3
64+
*/
65+
public static function normalizeLineItemPurchasableAvailability(OrderElement $order): void
66+
{
67+
if ($order->isCompleted) {
68+
return;
69+
}
70+
71+
foreach ($order->getLineItems() as $lineItem) {
72+
/* @var $purchasable Purchasable */
73+
$purchasable = $lineItem->getPurchasable();
74+
if (!$purchasable || !Plugin::getInstance()->getPurchasables()->isPurchasableAvailable($purchasable, $order)) {
75+
$message = Craft::t('commerce', '{description} is no longer available.', ['description' => $lineItem->getDescription()]);
76+
/** @var OrderNotice $notice */
77+
$notice = Craft::createObject([
78+
'class' => OrderNotice::class,
79+
'attributes' => [
80+
'message' => $message,
81+
'type' => 'lineItemRemoved',
82+
'attribute' => 'lineItems',
83+
],
84+
]);
85+
$order->addNotice($notice);
86+
$order->removeLineItem($lineItem);
87+
} elseif ($purchasable instanceof Variant && ($lineItem->qty > $purchasable->stock) && !$purchasable->hasUnlimitedStock && $purchasable->stock > 0) {
88+
$message = Craft::t('commerce', '{description} only has {stock} in stock.', ['description' => $lineItem->getDescription(), 'stock' => $purchasable->stock]);
89+
/** @var OrderNotice $notice */
90+
$notice = Craft::createObject([
91+
'class' => OrderNotice::class,
92+
'attributes' => [
93+
'type' => 'lineItemSalePriceChanged',
94+
'attribute' => "lineItems.$lineItem->id.qty",
95+
'message' => $message,
96+
],
97+
]);
98+
$order->addNotice($notice);
99+
$lineItem->qty = $purchasable->stock;
100+
}
101+
}
102+
}
47103
}

0 commit comments

Comments
 (0)