diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/MergeCarts.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/MergeCarts.php index d77d19df55603..297fd25be1fae 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/MergeCarts.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/MergeCarts.php @@ -7,17 +7,24 @@ namespace Magento\QuoteGraphQl\Model\Resolver; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; -use Magento\Quote\Api\CartRepositoryInterface; use Magento\GraphQl\Model\Query\ContextInterface; -use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Model\Cart\CustomerCartResolver; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\QuoteGraphQl\Model\Cart\GetCartForUser; /** * Merge Carts Resolver + * + * @SuppressWarnings(PHPMD.LongVariable) */ class MergeCarts implements ResolverInterface { @@ -31,44 +38,95 @@ class MergeCarts implements ResolverInterface */ private $cartRepository; + /** + * @var CustomerCartResolver + */ + private $customerCartResolver; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedQuoteId; + /** * @param GetCartForUser $getCartForUser * @param CartRepositoryInterface $cartRepository + * @param CustomerCartResolver|null $customerCartResolver + * @param QuoteIdToMaskedQuoteIdInterface|null $quoteIdToMaskedQuoteId */ public function __construct( GetCartForUser $getCartForUser, - CartRepositoryInterface $cartRepository + CartRepositoryInterface $cartRepository, + CustomerCartResolver $customerCartResolver = null, + QuoteIdToMaskedQuoteIdInterface $quoteIdToMaskedQuoteId = null ) { $this->getCartForUser = $getCartForUser; $this->cartRepository = $cartRepository; + $this->customerCartResolver = $customerCartResolver + ?: ObjectManager::getInstance()->get(CustomerCartResolver::class); + $this->quoteIdToMaskedQuoteId = $quoteIdToMaskedQuoteId + ?: ObjectManager::getInstance()->get(QuoteIdToMaskedQuoteIdInterface::class); } /** * @inheritdoc */ - public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) - { + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { if (empty($args['source_cart_id'])) { - throw new GraphQlInputException(__('Required parameter "source_cart_id" is missing')); - } - - if (empty($args['destination_cart_id'])) { - throw new GraphQlInputException(__('Required parameter "destination_cart_id" is missing')); + throw new GraphQlInputException(__( + 'Required parameter "source_cart_id" is missing' + )); } /** @var ContextInterface $context */ if (false === $context->getExtensionAttributes()->getIsCustomer()) { - throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); + throw new GraphQlAuthorizationException(__( + 'The current customer isn\'t authorized.' + )); + } + $currentUserId = $context->getUserId(); + + if (!isset($args['destination_cart_id'])) { + try { + $cart = $this->customerCartResolver->resolve($currentUserId); + } catch (CouldNotSaveException $exception) { + throw new GraphQlNoSuchEntityException( + __('Could not create empty cart for customer'), + $exception + ); + } + $customerMaskedCartId = $this->quoteIdToMaskedQuoteId->execute( + (int) $cart->getId() + ); + } else { + if (empty($args['destination_cart_id'])) { + throw new GraphQlInputException(__( + 'The parameter "destination_cart_id" cannot be empty' + )); + } } $guestMaskedCartId = $args['source_cart_id']; - $customerMaskedCartId = $args['destination_cart_id']; + $customerMaskedCartId = $customerMaskedCartId ?? $args['destination_cart_id']; - $currentUserId = $context->getUserId(); $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); // passing customerId as null enforces source cart should always be a guestcart - $guestCart = $this->getCartForUser->execute($guestMaskedCartId, null, $storeId); - $customerCart = $this->getCartForUser->execute($customerMaskedCartId, $currentUserId, $storeId); + $guestCart = $this->getCartForUser->execute( + $guestMaskedCartId, + null, + $storeId + ); + $customerCart = $this->getCartForUser->execute( + $customerMaskedCartId, + $currentUserId, + $storeId + ); $customerCart->merge($guestCart); $guestCart->setIsActive(false); $this->cartRepository->save($customerCart); diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 4e0e7ce5732be..cc9d1803b3e31 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -20,7 +20,7 @@ type Mutation { setPaymentMethodOnCart(input: SetPaymentMethodOnCartInput): SetPaymentMethodOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentMethodOnCart") setGuestEmailOnCart(input: SetGuestEmailOnCartInput): SetGuestEmailOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetGuestEmailOnCart") setPaymentMethodAndPlaceOrder(input: SetPaymentMethodAndPlaceOrderInput): PlaceOrderOutput @deprecated(reason: "Should use setPaymentMethodOnCart and placeOrder mutations in single request.") @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentAndPlaceOrder") - mergeCarts(source_cart_id: String!, destination_cart_id: String!): Cart! @doc(description:"Merges the source cart into the destination cart") @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\MergeCarts") + mergeCarts(source_cart_id: String!, destination_cart_id: String): Cart! @doc(description:"Merges the source cart into the destination cart") @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\MergeCarts") placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") addProductsToCart(cartId: String!, cartItems: [CartItemInput!]!): AddProductsToCartOutput @doc(description:"Add any type of product to the cart") @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddProductsToCart") } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php index 65e91bf193020..4f50b9df3098a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/MergeCartsTest.php @@ -193,7 +193,7 @@ public function testMergeCartsWithEmptySourceCartId() public function testMergeCartsWithEmptyDestinationCartId() { $this->expectException(\Exception::class); - $this->expectExceptionMessage('Required parameter "destination_cart_id" is missing'); + $this->expectExceptionMessage('The parameter "destination_cart_id" cannot be empty'); $guestQuote = $this->quoteFactory->create(); $this->quoteResource->load( @@ -209,6 +209,54 @@ public function testMergeCartsWithEmptyDestinationCartId() $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testMergeCartsWithoutDestinationCartId() + { + $guestQuote = $this->quoteFactory->create(); + $this->quoteResource->load( + $guestQuote, + 'test_order_with_virtual_product_without_address', + 'reserved_order_id' + ); + $guestQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$guestQuote->getId()); + $query = $this->getCartMergeMutationWithoutDestinationCartId( + $guestQuoteMaskedId + ); + $mergeResponse = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + + self::assertArrayHasKey('mergeCarts', $mergeResponse); + $cartResponse = $mergeResponse['mergeCarts']; + self::assertArrayHasKey('items', $cartResponse); + self::assertCount(2, $cartResponse['items']); + + $customerQuote = $this->quoteFactory->create(); + $this->quoteResource->load($customerQuote, 'test_quote', 'reserved_order_id'); + $customerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$customerQuote->getId()); + + $cartResponse = $this->graphQlMutation( + $this->getCartQuery($customerQuoteMaskedId), + [], + '', + $this->getHeaderMap() + ); + + self::assertArrayHasKey('cart', $cartResponse); + self::assertArrayHasKey('items', $cartResponse['cart']); + self::assertCount(2, $cartResponse['cart']['items']); + $item1 = $cartResponse['cart']['items'][0]; + self::assertArrayHasKey('quantity', $item1); + self::assertEquals(2, $item1['quantity']); + $item2 = $cartResponse['cart']['items'][1]; + self::assertArrayHasKey('quantity', $item2); + self::assertEquals(1, $item2['quantity']); + } + /** * Add simple product to cart * @@ -256,6 +304,31 @@ private function getCartMergeMutation(string $guestQuoteMaskedId, string $custom QUERY; } + /** + * Create the mergeCart mutation + * + * @param string $guestQuoteMaskedId + * @return string + */ + private function getCartMergeMutationWithoutDestinationCartId( + string $guestQuoteMaskedId + ): string { + return <<