Skip to content

Commit 3d6bdb9

Browse files
authored
LYNX-312: ExtimateTotals GraphQL mutation
1 parent 86b187f commit 3d6bdb9

File tree

3 files changed

+308
-0
lines changed

3 files changed

+308
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?php
2+
/**
3+
* Copyright 2023 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\QuoteGraphQl\Model\Resolver;
18+
19+
use Magento\Checkout\Api\Data\TotalsInformationInterface;
20+
use Magento\Checkout\Api\Data\TotalsInformationInterfaceFactory;
21+
use Magento\Checkout\Api\TotalsInformationManagementInterface;
22+
use Magento\Framework\Exception\NoSuchEntityException;
23+
use Magento\Framework\GraphQl\Config\Element\Field;
24+
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
25+
use Magento\Framework\GraphQl\Query\ResolverInterface;
26+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
27+
use Magento\Quote\Api\CartRepositoryInterface;
28+
use Magento\Quote\Api\Data\AddressInterface;
29+
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
30+
use Magento\Quote\Model\Quote\AddressFactory;
31+
32+
/**
33+
* Apply address and shipping method to totals estimate and return the quote
34+
*/
35+
class EstimateTotals implements ResolverInterface
36+
{
37+
/**
38+
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
39+
* @param CartRepositoryInterface $cartRepository
40+
* @param AddressFactory $addressFactory
41+
* @param TotalsInformationManagementInterface $totalsInformationManagement
42+
* @param TotalsInformationInterfaceFactory $totalsInformationFactory
43+
*/
44+
public function __construct(
45+
private readonly MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
46+
private readonly CartRepositoryInterface $cartRepository,
47+
private readonly AddressFactory $addressFactory,
48+
private readonly TotalsInformationManagementInterface $totalsInformationManagement,
49+
private readonly TotalsInformationInterfaceFactory $totalsInformationFactory
50+
) {
51+
}
52+
53+
/**
54+
* @inheritdoc
55+
*
56+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
57+
*/
58+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
59+
{
60+
if (empty($args['input']['cart_id'])) {
61+
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
62+
}
63+
64+
try {
65+
$cartId = $this->maskedQuoteIdToQuoteId->execute($args['input']['cart_id']);
66+
} catch (NoSuchEntityException $exception) {
67+
throw new GraphQlInputException(
68+
__(
69+
'Could not find a cart with ID "%masked_id"',
70+
[
71+
'masked_id' => $args['input']['cart_id']
72+
]
73+
)
74+
);
75+
}
76+
77+
if (empty($args['input']['address']['country_code'])) {
78+
throw new GraphQlInputException(__('Required parameter "country_code" is missing'));
79+
}
80+
81+
$this->totalsInformationManagement->calculate($cartId, $this->getTotalsInformation($args['input']));
82+
83+
return [
84+
'cart' => [
85+
'model' => $this->cartRepository->get($cartId)
86+
]
87+
];
88+
}
89+
90+
/**
91+
* Retrieve an instance of totals information based on input data
92+
*
93+
* @param array $input
94+
* @return TotalsInformationInterface
95+
*/
96+
private function getTotalsInformation(array $input): TotalsInformationInterface
97+
{
98+
$data = [TotalsInformationInterface::ADDRESS => $this->getAddress($input['address'])];
99+
100+
$shippingMethod = $input['shipping_method'] ?? [];
101+
102+
if (isset($shippingMethod['carrier_code']) && isset($shippingMethod['method_code'])) {
103+
$data[TotalsInformationInterface::SHIPPING_CARRIER_CODE] = $shippingMethod['carrier_code'];
104+
$data[TotalsInformationInterface::SHIPPING_METHOD_CODE] = $shippingMethod['method_code'];
105+
}
106+
107+
return $this->totalsInformationFactory->create(['data' => $data]);
108+
}
109+
110+
/**
111+
* Retrieve an instance of address based on address data
112+
*
113+
* @param array $data
114+
* @return AddressInterface
115+
*/
116+
private function getAddress(array $data): AddressInterface
117+
{
118+
$data = [
119+
AddressInterface::KEY_COUNTRY_ID => $data['country_code'],
120+
AddressInterface::KEY_REGION => $data['region'][AddressInterface::KEY_REGION] ?? null,
121+
AddressInterface::KEY_REGION_ID => $data['region'][AddressInterface::KEY_REGION_ID] ?? null,
122+
AddressInterface::KEY_REGION_CODE => $data['region'][AddressInterface::KEY_REGION_CODE] ?? null,
123+
AddressInterface::KEY_POSTCODE => $data[AddressInterface::KEY_POSTCODE] ?? null,
124+
];
125+
126+
return $this->addressFactory->create(['data' => array_filter($data)]);
127+
}
128+
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type Mutation {
2929
mergeCarts(source_cart_id: String! @doc(description: "The guest's cart ID before they login."), destination_cart_id: String @doc(description: "The cart ID after the guest logs in.")): Cart! @doc(description:"Transfer the contents of a guest cart into the cart of a logged-in customer.") @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\MergeCarts")
3030
placeOrder(input: PlaceOrderInput @doc(description: "An input object that defines the shopper's cart ID.")): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") @doc(description:"Convert the quote into an order.")
3131
addProductsToCart(cartId: String! @doc(description: "The cart ID of the shopper."), cartItems: [CartItemInput!]! @doc(description: "An array that defines the products to add to the cart.")): AddProductsToCartOutput @doc(description:"Add any type of product to the cart.") @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddProductsToCart")
32+
estimateTotals(input: EstimateTotalsInput! @doc(description: "An input object that specifies details for cart totals estimation")): EstimateTotalsOutput! @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\EstimateTotals") @doc(description: "Estimate totals for cart based on the address")
3233
}
3334

3435
input CreateGuestCartInput {
@@ -475,3 +476,19 @@ type StoreConfig {
475476
is_one_page_checkout_enabled: Boolean @doc(description: "Extended Config Data - checkout/options/onepage_checkout_enabled")
476477
max_items_in_order_summary: Int @doc(description: "Extended Config Data - checkout/options/max_items_display_count")
477478
}
479+
480+
input EstimateTotalsInput {
481+
cart_id: String! @doc(description: "The unique ID of the cart to query.")
482+
address: EstimateAddressInput! @doc(description: "Customer's address to estimate totals.")
483+
shipping_method: ShippingMethodInput @doc(description: "Selected shipping method to estimate totals.")
484+
}
485+
486+
type EstimateTotalsOutput @doc(description: "Estimate totals output.") {
487+
cart: Cart @doc(description: "Cart after totals estimation")
488+
}
489+
490+
input EstimateAddressInput @doc(description: "Contains details about an address.") {
491+
country_code: CountryCodeEnum! @doc(description: "The two-letter code representing the customer's country.")
492+
region: CustomerAddressRegionInput @doc(description: "An object containing the region name, region code, and region ID.")
493+
postcode: String @doc(description: "The customer's ZIP or postal code.")
494+
}
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<?php
2+
/**
3+
* Copyright 2023 Adobe
4+
* All Rights Reserved.
5+
*
6+
* NOTICE: All information contained herein is, and remains
7+
* the property of Adobe and its suppliers, if any. The intellectual
8+
* and technical concepts contained herein are proprietary to Adobe
9+
* and its suppliers and are protected by all applicable intellectual
10+
* property laws, including trade secret and copyright laws.
11+
* Dissemination of this information or reproduction of this material
12+
* is strictly forbidden unless prior written permission is obtained from
13+
* Adobe.
14+
*/
15+
declare(strict_types=1);
16+
17+
namespace Magento\GraphQl\Quote;
18+
19+
use Magento\Framework\Exception\LocalizedException;
20+
use Magento\Tax\Test\Fixture\ProductTaxClass;
21+
use Magento\Tax\Test\Fixture\TaxRate as TaxRateFixture;
22+
use Magento\Tax\Test\Fixture\TaxRule as TaxRuleFixture;
23+
use Magento\TestFramework\Fixture\DataFixture;
24+
use Magento\TestFramework\Fixture\DataFixtureStorageManager;
25+
use Magento\TestFramework\TestCase\GraphQlAbstract;
26+
use Magento\Quote\Test\Fixture\GuestCart;
27+
use Magento\Quote\Test\Fixture\QuoteIdMask;
28+
use Magento\Catalog\Test\Fixture\Product as ProductFixture;
29+
use Magento\Quote\Test\Fixture\AddProductToCart;
30+
31+
/**
32+
* Test for guest shipping methods estimate costs
33+
*/
34+
class EstimateTotalsTest extends GraphQlAbstract
35+
{
36+
/**
37+
* @param string $countryCode
38+
* @param string $shipping
39+
* @param array $prices
40+
* @return void
41+
* @throws LocalizedException
42+
* @dataProvider estimationsProvider
43+
*/
44+
#[
45+
DataFixture(
46+
ProductTaxClass::class,
47+
as: 'product_tax_class'
48+
),
49+
DataFixture(
50+
TaxRateFixture::class,
51+
[
52+
'tax_country_id' => 'ES'
53+
],
54+
'rate'
55+
),
56+
DataFixture(
57+
TaxRuleFixture::class,
58+
[
59+
'customer_tax_class_ids' => [3],
60+
'product_tax_class_ids' => ['$product_tax_class.classId$'],
61+
'tax_rate_ids' => ['$rate.id$']
62+
],
63+
'rule'
64+
),
65+
DataFixture(
66+
ProductFixture::class,
67+
[
68+
'custom_attributes' => [
69+
'tax_class_id' => '$product_tax_class.classId$'
70+
],
71+
],
72+
'product'
73+
),
74+
DataFixture(GuestCart::class, ['currency' => 'USD'], 'cart'),
75+
DataFixture(QuoteIdMask::class, ['cart_id' => '$cart.id$'], 'quoteIdMask'),
76+
DataFixture(AddProductToCart::class, [
77+
'cart_id' => '$cart.id$',
78+
'product_id' => '$product.id$',
79+
'qty' => 1
80+
])
81+
]
82+
public function testEstimateTotals(string $countryCode, string $shipping, array $prices): void
83+
{
84+
$maskedQuoteId = DataFixtureStorageManager::getStorage()->get('quoteIdMask')->getMaskedId();
85+
86+
$query = <<<QUERY
87+
mutation {
88+
estimateTotals(input: {
89+
cart_id: "{$maskedQuoteId}",
90+
address: {
91+
country_code: {$countryCode}
92+
},
93+
shipping_method: {
94+
carrier_code: "{$shipping}",
95+
method_code: "{$shipping}"
96+
}
97+
}) {
98+
cart {
99+
prices {
100+
grand_total {
101+
value
102+
currency
103+
}
104+
applied_taxes {
105+
amount {
106+
value
107+
currency
108+
}
109+
}
110+
}
111+
}
112+
}
113+
}
114+
QUERY;
115+
$response = $this->graphQlMutation($query);
116+
117+
self::assertEquals(
118+
[
119+
'estimateTotals' => [
120+
'cart' => [
121+
'prices' => $prices
122+
]
123+
]
124+
],
125+
$response
126+
);
127+
}
128+
129+
public function estimationsProvider(): array
130+
{
131+
return [
132+
[
133+
'ES',
134+
'freeshipping',
135+
[
136+
'grand_total' => [
137+
'value' => 11,
138+
'currency' => 'USD'
139+
],
140+
'applied_taxes' => [
141+
[
142+
'amount' => [
143+
'value' => 1,
144+
'currency' => 'USD'
145+
]
146+
]
147+
]
148+
]
149+
],
150+
[
151+
'IE',
152+
'flatrate',
153+
[
154+
'grand_total' => [
155+
'value' => 15,
156+
'currency' => 'USD'
157+
],
158+
'applied_taxes' => []
159+
]
160+
]
161+
];
162+
}
163+
}

0 commit comments

Comments
 (0)