Skip to content

Commit 4374e7f

Browse files
committed
AC-15054: Product Add to Cart issue in Rest API
1 parent 0d44645 commit 4374e7f

File tree

3 files changed

+326
-17
lines changed

3 files changed

+326
-17
lines changed
Lines changed: 60 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,99 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
55
*/
66

77
declare(strict_types=1);
88

99
namespace Magento\Quote\Plugin;
1010

11+
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Framework\Exception\LocalizedException;
13+
use Magento\Framework\Exception\NoSuchEntityException;
1114
use Magento\Framework\Webapi\Rest\Request as RestRequest;
1215
use Magento\Quote\Api\Data\CartItemInterface;
1316
use Magento\Quote\Api\GuestCartItemRepositoryInterface;
17+
use Magento\Quote\Model\QuoteIdMaskFactory;
18+
use Magento\Store\Model\StoreManagerInterface;
1419

1520
/**
1621
* Update cart id from request param
1722
*/
1823
class UpdateCartId
1924
{
20-
/**
21-
* @var RestRequest $request
22-
*/
23-
private $request;
24-
2525
/**
2626
* @param RestRequest $request
27+
* @param ProductRepositoryInterface $productRepository
28+
* @param StoreManagerInterface $storeManager
29+
* @param QuoteIdMaskFactory $quoteIdMaskFactory
2730
*/
28-
public function __construct(RestRequest $request)
29-
{
30-
$this->request = $request;
31+
public function __construct(
32+
private readonly RestRequest $request,
33+
private readonly ProductRepositoryInterface $productRepository,
34+
private readonly StoreManagerInterface $storeManager,
35+
private readonly QuoteIdMaskFactory $quoteIdMaskFactory
36+
) {
3137
}
3238

3339
/**
3440
* Update id from request if param cartId exist
3541
*
3642
* @param GuestCartItemRepositoryInterface $guestCartItemRepository
3743
* @param CartItemInterface $cartItem
38-
* @return void
44+
* @return array
3945
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
4046
*/
4147
public function beforeSave(
4248
GuestCartItemRepositoryInterface $guestCartItemRepository,
4349
CartItemInterface $cartItem
44-
): void {
50+
): array {
4551
$cartId = $this->request->getParam('cartId');
4652

4753
if ($cartId) {
4854
$cartItem->setQuoteId($cartId);
4955
}
56+
$this->validateProductWebsiteAssignment($cartItem);
57+
return [$cartItem];
58+
}
59+
60+
/**
61+
* Validate that product is assigned to the current website
62+
*
63+
* @param CartItemInterface $cartItem
64+
* @throws LocalizedException
65+
* @throws NoSuchEntityException
66+
*/
67+
private function validateProductWebsiteAssignment(CartItemInterface $cartItem): void
68+
{
69+
$sku = $cartItem->getSku();
70+
if (!$sku) {
71+
return;
72+
}
73+
74+
// Get current website ID from the masked cart ID
75+
$maskedQuoteId = $cartItem->getQuoteId();
76+
$quoteIdMask = $this->quoteIdMaskFactory->create()->load($maskedQuoteId, 'masked_id');
77+
78+
if (!$quoteIdMask->getQuoteId()) {
79+
return;
80+
}
81+
$currentWebsiteId = $this->storeManager->getStore()->getWebsiteId();
82+
try {
83+
$product = $this->productRepository->get($sku, false, null);
84+
85+
$productWebsiteIds = $product->getWebsiteIds();
86+
87+
// Validate website assignment
88+
if (!is_array($productWebsiteIds) || !in_array($currentWebsiteId, $productWebsiteIds)) {
89+
throw new LocalizedException(
90+
__('Product that you are trying to add is not available.')
91+
);
92+
}
93+
} catch (NoSuchEntityException $e) {
94+
throw new LocalizedException(
95+
__('Product that you are trying to add is not available.')
96+
);
97+
}
5098
}
5199
}

app/code/Magento/Quote/etc/webapi_rest/di.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,6 @@
2323
<type name="Magento\Quote\Model\QuoteValidator">
2424
<plugin name="error_redirect_processor" type="Magento\Quote\Plugin\Webapi\Model\ErrorRedirectProcessor" />
2525
</type>
26-
<type name="Magento\Quote\Api\GuestCartItemRepositoryInterface">
27-
<plugin name="validateProductWebsiteAssignmentGuest"
28-
type="Magento\Quote\Plugin\Webapi\ValidateProductWebsiteAssignmentForGuest"
29-
sortOrder="100" />
30-
</type>
3126
<type name="Magento\Quote\Api\CartItemRepositoryInterface">
3227
<plugin name="validateProductWebsiteAssignment"
3328
type="Magento\Quote\Plugin\Webapi\ValidateProductWebsiteAssignment"
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Quote\Test\Api;
9+
10+
use Magento\Catalog\Test\Fixture\Product;
11+
use Magento\Customer\Test\Fixture\Customer;
12+
use Magento\Store\Test\Fixture\Website;
13+
use Magento\Store\Test\Fixture\Store;
14+
use Magento\Store\Test\Fixture\Group;
15+
use Magento\TestFramework\Fixture\DataFixture;
16+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
17+
use Magento\TestFramework\TestCase\WebapiAbstract;
18+
19+
/**
20+
* Web API REST test for ValidateProductWebsiteAssignment plugins using fixtures
21+
*
22+
* @magentoAppArea webapi_rest
23+
*/
24+
class ValidateProductWebsiteAssignmentTest extends WebapiAbstract
25+
{
26+
/**
27+
* @var DataFixtureStorageManager
28+
*/
29+
private $fixtures;
30+
31+
/**
32+
* @var string
33+
*/
34+
private $customerToken;
35+
36+
/**
37+
* @var string
38+
*/
39+
private $guestCartId;
40+
41+
/**
42+
* Setup test environment
43+
*/
44+
protected function setUp(): void
45+
{
46+
$this->_markTestAsRestOnly();
47+
parent::setUp();
48+
$this->fixtures = DataFixtureStorageManager::getStorage();
49+
}
50+
51+
/**
52+
* Test customer cart REST API validates product website assignment successfully
53+
*/
54+
#[
55+
DataFixture(Website::class, ['code' => 'test_website', 'name' => 'Test Website'], 'website2'),
56+
DataFixture(Group::class, ['website_id' => '$website2.id$'], 'store_group2'),
57+
DataFixture(Store::class, ['store_group_id' => '$store_group2.id$'], 'store2'),
58+
DataFixture(
59+
Product::class,
60+
[
61+
'sku' => 'product-base-website',
62+
'name' => 'Product Base Website',
63+
'price' => 10.00,
64+
'website_ids' => [1], // Base website only
65+
'stock_data' => ['use_config_manage_stock' => 1, 'qty' => 100, 'is_in_stock' => 1]
66+
],
67+
'product_base'
68+
),
69+
DataFixture(
70+
Customer::class,
71+
[
72+
'website_id' => 1
73+
],
74+
'customer'
75+
)
76+
]
77+
public function testCustomerCartRestApiValidatesProductWebsiteAssignmentSuccessfully(): void
78+
{
79+
$customer = $this->fixtures->get('customer');
80+
$product = $this->fixtures->get('product_base');
81+
82+
// Get customer token
83+
$this->customerToken = $this->getCustomerToken($customer->getEmail(), 'password');
84+
85+
// Create customer cart
86+
$this->createCustomerCart();
87+
88+
// Add product to cart via REST API
89+
$serviceInfo = [
90+
'rest' => [
91+
'resourcePath' => '/V1/carts/mine/items',
92+
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
93+
'token' => $this->customerToken,
94+
],
95+
];
96+
97+
$cartItem = [
98+
'cartItem' => [
99+
'sku' => $product->getSku(),
100+
'qty' => 1,
101+
],
102+
];
103+
104+
$result = $this->_webApiCall($serviceInfo, $cartItem);
105+
106+
$this->assertNotNull($result);
107+
$this->assertEquals($product->getSku(), $result['sku']);
108+
$this->assertEquals(1, $result['qty']);
109+
}
110+
111+
/**
112+
* Test customer cart REST API throws exception when product not on website
113+
*/
114+
#[
115+
DataFixture(Website::class, ['code' => 'test_website', 'name' => 'Test Website'], 'website2'),
116+
DataFixture(Group::class, ['website_id' => '$website2.id$'], 'store_group2'),
117+
DataFixture(Store::class, ['store_group_id' => '$store_group2.id$'], 'store2'),
118+
DataFixture(
119+
Product::class,
120+
[
121+
'sku' => 'product-second-website',
122+
'name' => 'Product Second Website',
123+
'price' => 15.00,
124+
'website_ids' => ['$website2.id$'], // Second website only
125+
'stock_data' => ['use_config_manage_stock' => 1, 'qty' => 100, 'is_in_stock' => 1]
126+
],
127+
'product_second'
128+
),
129+
DataFixture(
130+
Customer::class,
131+
[
132+
'website_id' => 1 // Base website
133+
],
134+
'customer'
135+
)
136+
]
137+
public function testCustomerCartRestApiThrowsExceptionWhenProductNotOnWebsite(): void
138+
{
139+
$customer = $this->fixtures->get('customer');
140+
$product = $this->fixtures->get('product_second');
141+
142+
$this->customerToken = $this->getCustomerToken($customer->getEmail(), 'password');
143+
144+
$this->createCustomerCart();
145+
146+
// Try to add product from wrong website
147+
$serviceInfo = [
148+
'rest' => [
149+
'resourcePath' => '/V1/carts/mine/items',
150+
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
151+
'token' => $this->customerToken,
152+
],
153+
];
154+
155+
$cartItem = [
156+
'cartItem' => [
157+
'sku' => $product->getSku(),
158+
'qty' => 1,
159+
],
160+
];
161+
162+
// This should throw an exception
163+
$this->expectException(\Exception::class);
164+
$this->expectExceptionMessage('Product that you are trying to add is not available.');
165+
166+
$this->_webApiCall($serviceInfo, $cartItem);
167+
}
168+
169+
/**
170+
* Test guest cart REST API throws exception when product not on website
171+
*/
172+
#[
173+
DataFixture(Website::class, ['code' => 'test_website', 'name' => 'Test Website'], 'website2'),
174+
DataFixture(Group::class, ['website_id' => '$website2.id$'], 'store_group2'),
175+
DataFixture(Store::class, ['store_group_id' => '$store_group2.id$'], 'store2'),
176+
DataFixture(
177+
Product::class,
178+
[
179+
'sku' => 'product-second-guest',
180+
'name' => 'Product Second Guest',
181+
'price' => 18.00,
182+
'website_ids' => ['$website2.id$'], // Second website only
183+
'stock_data' => ['use_config_manage_stock' => 1, 'qty' => 100, 'is_in_stock' => 1]
184+
],
185+
'product_second'
186+
)
187+
]
188+
public function testGuestCartRestApiThrowsExceptionWhenProductNotOnWebsite(): void
189+
{
190+
$product = $this->fixtures->get('product_second');
191+
192+
$this->setupGuestCart();
193+
194+
// Try to add product from wrong website
195+
$serviceInfo = [
196+
'rest' => [
197+
'resourcePath' => '/V1/guest-carts/' . $this->guestCartId . '/items',
198+
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
199+
],
200+
];
201+
202+
$cartItem = [
203+
'cartItem' => [
204+
'sku' => $product->getSku(),
205+
'qty' => 1,
206+
],
207+
];
208+
209+
// This should throw an exception
210+
$this->expectException(\Exception::class);
211+
$this->expectExceptionMessage('Product that you are trying to add is not available.');
212+
213+
$this->_webApiCall($serviceInfo, $cartItem);
214+
}
215+
216+
/**
217+
* Get customer authentication token
218+
*/
219+
private function getCustomerToken(string $email, string $password): string
220+
{
221+
$serviceInfo = [
222+
'rest' => [
223+
'resourcePath' => '/V1/integration/customer/token',
224+
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
225+
],
226+
];
227+
228+
$credentials = [
229+
'username' => $email,
230+
'password' => $password,
231+
];
232+
233+
return $this->_webApiCall($serviceInfo, $credentials);
234+
}
235+
236+
/**
237+
* Create customer cart
238+
*/
239+
private function createCustomerCart(): void
240+
{
241+
$serviceInfo = [
242+
'rest' => [
243+
'resourcePath' => '/V1/carts/mine',
244+
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
245+
'token' => $this->customerToken,
246+
],
247+
];
248+
249+
$this->_webApiCall($serviceInfo);
250+
}
251+
252+
/**
253+
* Setup guest cart
254+
*/
255+
private function setupGuestCart(): void
256+
{
257+
$serviceInfo = [
258+
'rest' => [
259+
'resourcePath' => '/V1/guest-carts',
260+
'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST,
261+
],
262+
];
263+
264+
$this->guestCartId = $this->_webApiCall($serviceInfo);
265+
}
266+
}

0 commit comments

Comments
 (0)