Skip to content
This repository was archived by the owner on Dec 19, 2019. It is now read-only.

Commit 2fe3c1d

Browse files
committed
Add Merge Cart Mutation
* Merge two guest carts * Merge customer cart with guest cart * Merge customer cart with inactive customer cart Fixes #905
1 parent 50a085d commit 2fe3c1d

File tree

7 files changed

+672
-40
lines changed

7 files changed

+672
-40
lines changed
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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\QuoteGraphQl\Model\Cart;
9+
10+
use Magento\Framework\Exception\NoSuchEntityException;
11+
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
12+
use Magento\Quote\Api\CartRepositoryInterface;
13+
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
14+
use Magento\Quote\Model\Quote;
15+
16+
/**
17+
* Get cart merge
18+
*/
19+
class GetCart
20+
{
21+
/**
22+
* @var MaskedQuoteIdToQuoteIdInterface
23+
*/
24+
private $maskedQuoteIdToQuoteId;
25+
26+
/**
27+
* @var CartRepositoryInterface
28+
*/
29+
private $cartRepository;
30+
31+
/**
32+
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
33+
* @param CartRepositoryInterface $cartRepository
34+
*/
35+
public function __construct(
36+
MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
37+
CartRepositoryInterface $cartRepository
38+
) {
39+
$this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId;
40+
$this->cartRepository = $cartRepository;
41+
}
42+
43+
/**
44+
* Get cart for merge
45+
*
46+
* @param string $cartHash
47+
* @param int|null $customerId
48+
* @param int $storeId
49+
* @return Quote
50+
* @throws GraphQlNoSuchEntityException
51+
* @throws NoSuchEntityException
52+
*/
53+
public function execute(string $cartHash, ?int $customerId, int $storeId): Quote
54+
{
55+
try {
56+
$cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash);
57+
} catch (NoSuchEntityException $exception) {
58+
throw new GraphQlNoSuchEntityException(
59+
__('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash])
60+
);
61+
}
62+
63+
try {
64+
/** @var Quote $cart */
65+
$cart = $this->cartRepository->get($cartId);
66+
} catch (NoSuchEntityException $e) {
67+
throw new GraphQlNoSuchEntityException(
68+
__('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash])
69+
);
70+
}
71+
72+
if ((int)$cart->getStoreId() !== $storeId) {
73+
throw new GraphQlNoSuchEntityException(
74+
__(
75+
'Wrong store code specified for cart "%masked_cart_id"',
76+
['masked_cart_id' => $cartHash]
77+
)
78+
);
79+
}
80+
81+
$cartCustomerId = (int)$cart->getCustomerId();
82+
83+
/* Guest cart, allow operations */
84+
if (0 === $cartCustomerId) {
85+
return $cart;
86+
}
87+
88+
if ($cartCustomerId !== $customerId) {
89+
throw new GraphQlNoSuchEntityException(
90+
__('The current user cannot perform operations on cart "%masked_cart_id"', ['masked_cart_id' => $cartHash])
91+
);
92+
}
93+
return $cart;
94+
}
95+
}

app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
use Magento\Framework\Exception\NoSuchEntityException;
1111
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
1212
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
13-
use Magento\Quote\Api\CartRepositoryInterface;
14-
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
1513
use Magento\Quote\Model\Quote;
1614

1715
/**
@@ -20,25 +18,17 @@
2018
class GetCartForUser
2119
{
2220
/**
23-
* @var MaskedQuoteIdToQuoteIdInterface
21+
* @var GetCart
2422
*/
25-
private $maskedQuoteIdToQuoteId;
23+
private $getCart;
2624

2725
/**
28-
* @var CartRepositoryInterface
29-
*/
30-
private $cartRepository;
31-
32-
/**
33-
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
34-
* @param CartRepositoryInterface $cartRepository
26+
* @param GetCart $getCart
3527
*/
3628
public function __construct(
37-
MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
38-
CartRepositoryInterface $cartRepository
29+
GetCart $getCart
3930
) {
40-
$this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId;
41-
$this->cartRepository = $cartRepository;
31+
$this->getCart = $getCart;
4232
}
4333

4434
/**
@@ -54,38 +44,14 @@ public function __construct(
5444
*/
5545
public function execute(string $cartHash, ?int $customerId, int $storeId): Quote
5646
{
57-
try {
58-
$cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash);
59-
} catch (NoSuchEntityException $exception) {
60-
throw new GraphQlNoSuchEntityException(
61-
__('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash])
62-
);
63-
}
64-
65-
try {
66-
/** @var Quote $cart */
67-
$cart = $this->cartRepository->get($cartId);
68-
} catch (NoSuchEntityException $e) {
69-
throw new GraphQlNoSuchEntityException(
70-
__('Could not find a cart with ID "%masked_cart_id"', ['masked_cart_id' => $cartHash])
71-
);
72-
}
47+
$cart = $this->getCart->execute($cartHash, $customerId, $storeId);
7348

7449
if (false === (bool)$cart->getIsActive()) {
7550
throw new GraphQlNoSuchEntityException(
7651
__('Current user does not have an active cart.')
7752
);
7853
}
7954

80-
if ((int)$cart->getStoreId() !== $storeId) {
81-
throw new GraphQlNoSuchEntityException(
82-
__(
83-
'Wrong store code specified for cart "%masked_cart_id"',
84-
['masked_cart_id' => $cartHash]
85-
)
86-
);
87-
}
88-
8955
$cartCustomerId = (int)$cart->getCustomerId();
9056

9157
/* Guest cart, allow operations */
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
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\QuoteGraphQl\Model\Cart;
9+
10+
use Magento\Quote\Model\Quote;
11+
use Magento\Quote\Model\QuoteIdMask;
12+
use Magento\Quote\Model\QuoteIdMaskFactory;
13+
use Magento\Quote\Model\ResourceModel\Quote\QuoteIdMask as QuoteIdMaskResourceModel;
14+
use Magento\Quote\Api\CartRepositoryInterface;
15+
16+
/**
17+
* Merge two carts
18+
*/
19+
class MergeCarts
20+
{
21+
/**
22+
* @var QuoteIdMaskFactory
23+
*/
24+
private $quoteMaskFactory;
25+
26+
/**
27+
* @var QuoteIdMaskResourceModel
28+
*/
29+
private $quoteMaskResource;
30+
31+
/**
32+
* @var CartRepositoryInterface
33+
*/
34+
private $cartRepository;
35+
36+
/**
37+
* @param QuoteIdMaskFactory $quoteMaskFactory
38+
* @param QuoteIdMaskResourceModel $quoteMaskResource
39+
* @param CartRepositoryInterface $cartRepository
40+
*/
41+
public function __construct(
42+
QuoteIdMaskFactory $quoteMaskFactory,
43+
QuoteIdMaskResourceModel $quoteMaskResource,
44+
CartRepositoryInterface $cartRepository
45+
) {
46+
$this->quoteMaskFactory = $quoteMaskFactory;
47+
$this->quoteMaskResource = $quoteMaskResource;
48+
$this->cartRepository = $cartRepository;
49+
}
50+
51+
/**
52+
* Merge two quotes
53+
*
54+
* @param Quote $firstCart
55+
* @param Quote $secondQuote
56+
* @return string
57+
*/
58+
public function execute(Quote $firstCart, Quote $secondQuote): string
59+
{
60+
$firstCart->merge($secondQuote);
61+
$firstCart->setIsActive(true);
62+
63+
$this->updateMaskedId($secondQuote);
64+
$maskedQuoteId = $this->updateMaskedId($firstCart);
65+
66+
$this->cartRepository->save($firstCart);
67+
68+
$secondQuote->setIsActive(false);
69+
$this->cartRepository->save($secondQuote);
70+
71+
return $maskedQuoteId;
72+
}
73+
74+
/**
75+
* Update quote masked id
76+
*
77+
* @param Quote $quote
78+
* @return string
79+
*/
80+
private function updateMaskedId(Quote $quote): string
81+
{
82+
/** @var QuoteIdMask $quoteIdMask */
83+
$quoteIdMask = $this->quoteMaskFactory->create();
84+
$this->quoteMaskResource->load($quoteIdMask, $quote->getId(), 'quote_id');
85+
$quoteIdMask->unsetData('masked_id');
86+
$this->quoteMaskResource->save($quoteIdMask);
87+
$maskedId = $quoteIdMask->getMaskedId();
88+
89+
return $maskedId;
90+
}
91+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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\QuoteGraphQl\Model\Resolver;
9+
10+
use Magento\Framework\GraphQl\Config\Element\Field;
11+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
12+
use Magento\Framework\GraphQl\Query\ResolverInterface;
13+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
14+
use Magento\QuoteGraphQl\Model\Cart\GetCart;
15+
use Magento\QuoteGraphQl\Model\Cart\MergeCarts as MergeCartsModel;
16+
17+
/**
18+
* @inheritdoc
19+
*/
20+
class MergeCarts implements ResolverInterface
21+
{
22+
/**
23+
* @var GetCart
24+
*/
25+
private $getCart;
26+
27+
/**
28+
* @var MergeCartsModel
29+
*/
30+
private $mergeCarts;
31+
32+
/**
33+
* @param GetCart $getCart
34+
* @param MergeCartsModel $mergeCarts
35+
*/
36+
public function __construct(
37+
GetCart $getCart,
38+
MergeCartsModel $mergeCarts
39+
) {
40+
$this->getCart = $getCart;
41+
$this->mergeCarts = $mergeCarts;
42+
}
43+
44+
/**
45+
* @inheritdoc
46+
*/
47+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
48+
{
49+
if (empty($args['input']['first_cart_id'])) {
50+
throw new GraphQlInputException(__('Required parameter "first_cart_id" is missing'));
51+
}
52+
if (empty($args['input']['second_cart_id'])) {
53+
throw new GraphQlInputException(__('Required parameter "second_cart_id" is missing'));
54+
}
55+
56+
$currentUserId = $context->getUserId();
57+
$storeId = $storeId = (int) $context->getExtensionAttributes()->getStore()->getId();
58+
$firstCart = $this->getCart->execute($args['input']['first_cart_id'], $currentUserId, $storeId);
59+
$secondCart = $this->getCart->execute($args['input']['second_cart_id'], $currentUserId, $storeId);
60+
61+
$maskedQuoteId = $this->mergeCarts->execute($firstCart, $secondCart);
62+
63+
return $maskedQuoteId;
64+
}
65+
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type Mutation {
2020
setGuestEmailOnCart(input: SetGuestEmailOnCartInput): SetGuestEmailOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetGuestEmailOnCart")
2121
setPaymentMethodAndPlaceOrder(input: SetPaymentMethodAndPlaceOrderInput): PlaceOrderOutput @deprecated(reason: "Should use setPaymentMethodOnCart and placeOrder mutations in single request.") @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentAndPlaceOrder")
2222
placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder")
23+
mergeCarts(input: MergeCartsInput): String @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\MergeCarts")
2324
}
2425

2526
input createEmptyCartInput {
@@ -146,6 +147,11 @@ input SetGuestEmailOnCartInput {
146147
email: String!
147148
}
148149

150+
input MergeCartsInput {
151+
first_cart_id: String!
152+
second_cart_id: String!
153+
}
154+
149155
type CartPrices {
150156
grand_total: Money
151157
subtotal_including_tax: Money

0 commit comments

Comments
 (0)