Skip to content

Commit c8cdc96

Browse files
committed
Merge remote-tracking branch 'origin/2.3-develop' into set-guest-email-mutation
# Conflicts: # app/code/Magento/QuoteGraphQl/etc/schema.graphqls
2 parents 5b85e5d + af6820c commit c8cdc96

File tree

15 files changed

+1092
-41
lines changed

15 files changed

+1092
-41
lines changed
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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\Exception\LocalizedException;
11+
use Magento\Framework\GraphQl\Config\Element\Field;
12+
use Magento\Framework\GraphQl\Query\ResolverInterface;
13+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
14+
use Magento\Quote\Model\Quote;
15+
use Magento\Quote\Model\Quote\Address\Total;
16+
use Magento\Quote\Model\Quote\TotalsCollector;
17+
18+
/**
19+
* @inheritdoc
20+
*/
21+
class CartPrices implements ResolverInterface
22+
{
23+
/**
24+
* @var TotalsCollector
25+
*/
26+
private $totalsCollector;
27+
28+
/**
29+
* @param TotalsCollector $totalsCollector
30+
*/
31+
public function __construct(
32+
TotalsCollector $totalsCollector
33+
) {
34+
$this->totalsCollector = $totalsCollector;
35+
}
36+
37+
/**
38+
* @inheritdoc
39+
*/
40+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
41+
{
42+
if (!isset($value['model'])) {
43+
throw new LocalizedException(__('"model" value should be specified'));
44+
}
45+
46+
/** @var Quote $quote */
47+
$quote = $value['model'];
48+
$cartTotals = $this->totalsCollector->collectQuoteTotals($quote);
49+
$currency = $quote->getQuoteCurrencyCode();
50+
51+
return [
52+
'grand_total' => ['value' => $cartTotals->getGrandTotal(), 'currency' => $currency],
53+
'subtotal_including_tax' => ['value' => $cartTotals->getSubtotalInclTax(), 'currency' => $currency],
54+
'subtotal_excluding_tax' => ['value' => $cartTotals->getSubtotal(), 'currency' => $currency],
55+
'subtotal_with_discount_excluding_tax' => [
56+
'value' => $cartTotals->getSubtotalWithDiscount(), 'currency' => $currency
57+
],
58+
'applied_taxes' => $this->getAppliedTaxes($cartTotals, $currency),
59+
'model' => $quote
60+
];
61+
}
62+
63+
/**
64+
* Returns taxes applied to the current quote
65+
*
66+
* @param Total $total
67+
* @param string $currency
68+
* @return array
69+
*/
70+
private function getAppliedTaxes(Total $total, string $currency): array
71+
{
72+
$appliedTaxesData = [];
73+
$appliedTaxes = $total->getAppliedTaxes();
74+
75+
if (count($appliedTaxes) === 0) {
76+
return $appliedTaxesData;
77+
}
78+
79+
foreach ($appliedTaxes as $appliedTax) {
80+
$appliedTaxesData[] = [
81+
'label' => $appliedTax['id'],
82+
'amount' => ['value' => $appliedTax['amount'], 'currency' => $currency]
83+
];
84+
}
85+
return $appliedTaxesData;
86+
}
87+
}

app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
6565

6666
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
6767

68+
if ($context->getUserId() === 0) {
69+
if (!$cart->getCustomerEmail()) {
70+
throw new GraphQlInputException(__("Guest email for cart is missing. Please enter"));
71+
}
72+
$cart->setCheckoutMethod(CartManagementInterface::METHOD_GUEST);
73+
}
74+
6875
try {
6976
$orderId = $this->cartManagement->placeOrder($cart->getId());
7077
$order = $this->orderRepository->get($orderId);

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,19 @@ input SetGuestEmailOnCartInput {
135135
email: String!
136136
}
137137

138+
type CartPrices {
139+
grand_total: Money
140+
subtotal_including_tax: Money
141+
subtotal_excluding_tax: Money
142+
subtotal_with_discount_excluding_tax: Money
143+
applied_taxes: [CartTaxItem]
144+
}
145+
146+
type CartTaxItem {
147+
amount: Money!
148+
label: String!
149+
}
150+
138151
type SetPaymentMethodOnCartOutput {
139152
cart: Cart!
140153
}
@@ -167,6 +180,7 @@ type Cart {
167180
billing_address: CartAddress! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\BillingAddress")
168181
available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods")
169182
selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod")
183+
prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices")
170184
}
171185

172186
type CartAddress {
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
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\GraphQl\Quote\Customer;
9+
10+
use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
11+
use Magento\Integration\Api\CustomerTokenServiceInterface;
12+
use Magento\TestFramework\Helper\Bootstrap;
13+
use Magento\TestFramework\TestCase\GraphQlAbstract;
14+
15+
/**
16+
* Test getting cart totals for registered customer
17+
*/
18+
class CartTotalsTest extends GraphQlAbstract
19+
{
20+
/**
21+
* @var CustomerTokenServiceInterface
22+
*/
23+
private $customerTokenService;
24+
25+
/**
26+
* @var GetMaskedQuoteIdByReservedOrderId
27+
*/
28+
private $getMaskedQuoteIdByReservedOrderId;
29+
30+
protected function setUp()
31+
{
32+
$objectManager = Bootstrap::getObjectManager();
33+
$this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
34+
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
35+
}
36+
37+
/**
38+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
39+
* @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php
40+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
41+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php
42+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
43+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
44+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
45+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php
46+
*/
47+
public function testGetCartTotalsWithTaxApplied()
48+
{
49+
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
50+
$query = $this->getQuery($maskedQuoteId);
51+
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
52+
53+
self::assertArrayHasKey('prices', $response['cart']);
54+
$pricesResponse = $response['cart']['prices'];
55+
self::assertEquals(21.5, $pricesResponse['grand_total']['value']);
56+
self::assertEquals(21.5, $pricesResponse['subtotal_including_tax']['value']);
57+
self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']);
58+
self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']);
59+
60+
$appliedTaxesResponse = $pricesResponse['applied_taxes'];
61+
62+
self::assertEquals('US-TEST-*-Rate-1', $appliedTaxesResponse[0]['label']);
63+
self::assertEquals(1.5, $appliedTaxesResponse[0]['amount']['value']);
64+
self::assertEquals('USD', $appliedTaxesResponse[0]['amount']['currency']);
65+
}
66+
67+
/**
68+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
69+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
70+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
71+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
72+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
73+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php
74+
*/
75+
public function testGetTotalsWithNoTaxApplied()
76+
{
77+
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
78+
$query = $this->getQuery($maskedQuoteId);
79+
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
80+
81+
$pricesResponse = $response['cart']['prices'];
82+
self::assertEquals(20, $pricesResponse['grand_total']['value']);
83+
self::assertEquals(20, $pricesResponse['subtotal_including_tax']['value']);
84+
self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']);
85+
self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']);
86+
self::assertEmpty($pricesResponse['applied_taxes']);
87+
}
88+
89+
/**
90+
* The totals calculation is based on quote address.
91+
* But the totals should be calculated even if no address is set
92+
*
93+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
94+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
95+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
96+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
97+
*/
98+
public function testGetCartTotalsWithNoAddressSet()
99+
{
100+
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
101+
$query = $this->getQuery($maskedQuoteId);
102+
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
103+
104+
$pricesResponse = $response['cart']['prices'];
105+
self::assertEquals(20, $pricesResponse['grand_total']['value']);
106+
self::assertEquals(20, $pricesResponse['subtotal_including_tax']['value']);
107+
self::assertEquals(20, $pricesResponse['subtotal_excluding_tax']['value']);
108+
self::assertEquals(20, $pricesResponse['subtotal_with_discount_excluding_tax']['value']);
109+
self::assertEmpty($pricesResponse['applied_taxes']);
110+
}
111+
112+
/**
113+
* _security
114+
* @magentoApiDataFixture Magento/Customer/_files/customer.php
115+
* @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php
116+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
117+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php
118+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
119+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
120+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
121+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php
122+
*/
123+
public function testGetTotalsFromGuestCart()
124+
{
125+
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
126+
$query = $this->getQuery($maskedQuoteId);
127+
128+
$this->expectExceptionMessage(
129+
"The current user cannot perform operations on cart \"$maskedQuoteId\""
130+
);
131+
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
132+
}
133+
134+
/**
135+
* _security
136+
* @magentoApiDataFixture Magento/Customer/_files/three_customers.php
137+
* @magentoApiDataFixture Magento/GraphQl/Tax/_files/tax_rule_for_region_1.php
138+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
139+
* @magentoApiDataFixture Magento/GraphQl/Catalog/_files/apply_tax_for_simple_product.php
140+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
141+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
142+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
143+
* @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_billing_address.php
144+
*/
145+
public function testGetTotalsFromAnotherCustomerCart()
146+
{
147+
$maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
148+
$query = $this->getQuery($maskedQuoteId);
149+
150+
$this->expectExceptionMessage(
151+
"The current user cannot perform operations on cart \"$maskedQuoteId\""
152+
);
153+
$this->graphQlQuery($query, [], '', $this->getHeaderMap('[email protected]'));
154+
}
155+
156+
/**
157+
* Generates GraphQl query for retrieving cart totals
158+
*
159+
* @param string $maskedQuoteId
160+
* @return string
161+
*/
162+
private function getQuery(string $maskedQuoteId): string
163+
{
164+
return <<<QUERY
165+
{
166+
cart(cart_id: "$maskedQuoteId") {
167+
prices {
168+
grand_total {
169+
value,
170+
currency
171+
}
172+
subtotal_including_tax {
173+
value
174+
currency
175+
}
176+
subtotal_excluding_tax {
177+
value
178+
currency
179+
}
180+
subtotal_with_discount_excluding_tax {
181+
value
182+
currency
183+
}
184+
applied_taxes {
185+
label
186+
amount {
187+
value
188+
currency
189+
}
190+
}
191+
}
192+
}
193+
}
194+
QUERY;
195+
}
196+
197+
/**
198+
* @param string $username
199+
* @param string $password
200+
* @return array
201+
*/
202+
private function getHeaderMap(string $username = '[email protected]', string $password = 'password'): array
203+
{
204+
$customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password);
205+
$headerMap = ['Authorization' => 'Bearer ' . $customerToken];
206+
return $headerMap;
207+
}
208+
}

0 commit comments

Comments
 (0)