Skip to content

Commit 18c77e3

Browse files
author
Prabhu Ram
committed
Merge remote-tracking branch 'rogyar/257-single-mutation-add-to-cart' into 241-customer-orders
2 parents 51d71d9 + 4391235 commit 18c77e3

File tree

55 files changed

+3116
-33
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+3116
-33
lines changed

app/code/Magento/GraphQl/etc/schema.graphqls

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,8 @@ enum CurrencyEnum @doc(description: "The list of available currency codes") {
277277
TRL
278278
XPF
279279
}
280+
281+
input EnteredOptionInput @doc(description: "Defines a customer-entered option") {
282+
uid: ID! @doc(description: "An encoded ID")
283+
value: String! @doc(description: "Text the customer entered")
284+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Quote\Model\Cart;
9+
10+
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\Framework\Exception\NoSuchEntityException;
12+
use Magento\Quote\Api\CartRepositoryInterface;
13+
use Magento\Quote\Api\Data\CartInterface;
14+
use Magento\Quote\Model\Cart\BuyRequest\BuyRequestBuilder;
15+
use Magento\Quote\Model\Cart\Data\AddProductsToCartOutput;
16+
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
17+
use Magento\Quote\Model\Quote;
18+
use Magento\Framework\Message\MessageInterface;
19+
20+
/**
21+
* Unified approach to add products to the Shopping Cart.
22+
* Client code must validate, that customer is eligible to call service with provided {cartId} and {cartItems}
23+
*/
24+
class AddProductsToCart
25+
{
26+
/**#@+
27+
* Error message codes
28+
*/
29+
private const ERROR_PRODUCT_NOT_FOUND = 'PRODUCT_NOT_FOUND';
30+
private const ERROR_INSUFFICIENT_STOCK = 'INSUFFICIENT_STOCK';
31+
private const ERROR_NOT_SALABLE = 'NOT_SALABLE';
32+
private const ERROR_UNDEFINED = 'UNDEFINED';
33+
/**#@-*/
34+
35+
/**
36+
* List of error messages and codes.
37+
*/
38+
private const MESSAGE_CODES = [
39+
'Could not find a product with SKU' => self::ERROR_PRODUCT_NOT_FOUND,
40+
'The required options you selected are not available' => self::ERROR_NOT_SALABLE,
41+
'Product that you are trying to add is not available.' => self::ERROR_NOT_SALABLE,
42+
'This product is out of stock' => self::ERROR_INSUFFICIENT_STOCK,
43+
'There are no source items' => self::ERROR_NOT_SALABLE,
44+
'The fewest you may purchase is' => self::ERROR_INSUFFICIENT_STOCK,
45+
'The most you may purchase is' => self::ERROR_INSUFFICIENT_STOCK,
46+
'The requested qty is not available' => self::ERROR_INSUFFICIENT_STOCK,
47+
];
48+
49+
/**
50+
* @var ProductRepositoryInterface
51+
*/
52+
private $productRepository;
53+
54+
/**
55+
* @var array
56+
*/
57+
private $errors = [];
58+
59+
/**
60+
* @var CartRepositoryInterface
61+
*/
62+
private $cartRepository;
63+
64+
/**
65+
* @var MaskedQuoteIdToQuoteIdInterface
66+
*/
67+
private $maskedQuoteIdToQuoteId;
68+
69+
/**
70+
* @var BuyRequestBuilder
71+
*/
72+
private $requestBuilder;
73+
74+
/**
75+
* @param ProductRepositoryInterface $productRepository
76+
* @param CartRepositoryInterface $cartRepository
77+
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
78+
* @param BuyRequestBuilder $requestBuilder
79+
*/
80+
public function __construct(
81+
ProductRepositoryInterface $productRepository,
82+
CartRepositoryInterface $cartRepository,
83+
MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
84+
BuyRequestBuilder $requestBuilder
85+
) {
86+
$this->productRepository = $productRepository;
87+
$this->cartRepository = $cartRepository;
88+
$this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId;
89+
$this->requestBuilder = $requestBuilder;
90+
}
91+
92+
/**
93+
* Add cart items to the cart
94+
*
95+
* @param string $maskedCartId
96+
* @param Data\CartItem[] $cartItems
97+
* @return AddProductsToCartOutput
98+
* @throws NoSuchEntityException Could not find a Cart with provided $maskedCartId
99+
*/
100+
public function execute(string $maskedCartId, array $cartItems): AddProductsToCartOutput
101+
{
102+
$cartId = $this->maskedQuoteIdToQuoteId->execute($maskedCartId);
103+
$cart = $this->cartRepository->get($cartId);
104+
105+
foreach ($cartItems as $cartItemPosition => $cartItem) {
106+
$this->addItemToCart($cart, $cartItem, $cartItemPosition);
107+
}
108+
109+
if ($cart->getData('has_error')) {
110+
$errors = $cart->getErrors();
111+
112+
/** @var MessageInterface $error */
113+
foreach ($errors as $error) {
114+
$this->addError($error->getText());
115+
}
116+
}
117+
118+
if (count($this->errors) !== 0) {
119+
/* Revert changes introduced by add to cart processes in case of an error */
120+
$cart->getItemsCollection()->clear();
121+
}
122+
123+
return $this->prepareErrorOutput($cart);
124+
}
125+
126+
/**
127+
* Adds a particular item to the shopping cart
128+
*
129+
* @param CartInterface|Quote $cart
130+
* @param Data\CartItem $cartItem
131+
* @param int $cartItemPosition
132+
*/
133+
private function addItemToCart(CartInterface $cart, Data\CartItem $cartItem, int $cartItemPosition): void
134+
{
135+
$sku = $cartItem->getSku();
136+
137+
if ($cartItem->getQuantity() <= 0) {
138+
$this->addError(__('The product quantity should be greater than 0')->render());
139+
140+
return;
141+
}
142+
143+
try {
144+
$product = $this->productRepository->get($sku, false, null, true);
145+
} catch (NoSuchEntityException $e) {
146+
$this->addError(
147+
__('Could not find a product with SKU "%sku"', ['sku' => $sku])->render(),
148+
$cartItemPosition
149+
);
150+
151+
return;
152+
}
153+
154+
try {
155+
$result = $cart->addProduct($product, $this->requestBuilder->build($cartItem));
156+
$this->cartRepository->save($cart);
157+
} catch (\Throwable $e) {
158+
$this->addError(
159+
__($e->getMessage())->render(),
160+
$cartItemPosition
161+
);
162+
$cart->setHasError(false);
163+
164+
return;
165+
}
166+
167+
if (is_string($result)) {
168+
$errors = array_unique(explode("\n", $result));
169+
foreach ($errors as $error) {
170+
$this->addError(__($error)->render(), $cartItemPosition);
171+
}
172+
}
173+
}
174+
175+
/**
176+
* Add order line item error
177+
*
178+
* @param string $message
179+
* @param int $cartItemPosition
180+
* @return void
181+
*/
182+
private function addError(string $message, int $cartItemPosition = 0): void
183+
{
184+
$this->errors[] = new Data\Error(
185+
$message,
186+
$this->getErrorCode($message),
187+
$cartItemPosition
188+
);
189+
}
190+
191+
/**
192+
* Get message error code.
193+
*
194+
* TODO: introduce a separate class for getting error code from a message
195+
*
196+
* @param string $message
197+
* @return string
198+
*/
199+
private function getErrorCode(string $message): string
200+
{
201+
foreach (self::MESSAGE_CODES as $codeMessage => $code) {
202+
if (false !== stripos($message, $codeMessage)) {
203+
return $code;
204+
}
205+
}
206+
207+
/* If no code was matched, return the default one */
208+
return self::ERROR_UNDEFINED;
209+
}
210+
211+
/**
212+
* Creates a new output from existing errors
213+
*
214+
* @param CartInterface $cart
215+
* @return AddProductsToCartOutput
216+
*/
217+
private function prepareErrorOutput(CartInterface $cart): AddProductsToCartOutput
218+
{
219+
$output = new AddProductsToCartOutput($cart, $this->errors);
220+
$this->errors = [];
221+
$cart->setHasError(false);
222+
223+
return $output;
224+
}
225+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Quote\Model\Cart\BuyRequest;
9+
10+
use Magento\Framework\DataObject;
11+
use Magento\Framework\DataObjectFactory;
12+
use Magento\Quote\Model\Cart\Data\CartItem;
13+
14+
/**
15+
* Build buy request for adding products to cart
16+
*/
17+
class BuyRequestBuilder
18+
{
19+
/**
20+
* @var BuyRequestDataProviderInterface[]
21+
*/
22+
private $providers;
23+
24+
/**
25+
* @var DataObjectFactory
26+
*/
27+
private $dataObjectFactory;
28+
29+
/**
30+
* @param DataObjectFactory $dataObjectFactory
31+
* @param array $providers
32+
*/
33+
public function __construct(
34+
DataObjectFactory $dataObjectFactory,
35+
array $providers = []
36+
) {
37+
$this->dataObjectFactory = $dataObjectFactory;
38+
$this->providers = $providers;
39+
}
40+
41+
/**
42+
* Build buy request for adding product to cart
43+
*
44+
* @see \Magento\Quote\Model\Quote::addProduct
45+
* @param CartItem $cartItem
46+
* @return DataObject
47+
*/
48+
public function build(CartItem $cartItem): DataObject
49+
{
50+
$requestData = [
51+
['qty' => $cartItem->getQuantity()]
52+
];
53+
54+
/** @var BuyRequestDataProviderInterface $provider */
55+
foreach ($this->providers as $provider) {
56+
$requestData[] = $provider->execute($cartItem);
57+
}
58+
59+
return $this->dataObjectFactory->create(['data' => array_merge(...$requestData)]);
60+
}
61+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Quote\Model\Cart\BuyRequest;
9+
10+
use Magento\Quote\Model\Cart\Data\CartItem;
11+
12+
/**
13+
* Provides data for buy request for different types of products
14+
*/
15+
interface BuyRequestDataProviderInterface
16+
{
17+
/**
18+
* Provide buy request data from add to cart item request
19+
*
20+
* @param CartItem $cartItem
21+
* @return array
22+
*/
23+
public function execute(CartItem $cartItem): array;
24+
}

0 commit comments

Comments
 (0)