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

Commit 4b89ee0

Browse files
committed
Cart totals implementation
1 parent 3a4ba66 commit 4b89ee0

File tree

10 files changed

+444
-0
lines changed

10 files changed

+444
-0
lines changed
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\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\TotalsCollector;
16+
17+
/**
18+
* @inheritdoc
19+
*/
20+
class CartTaxes implements ResolverInterface
21+
{
22+
/**
23+
* @var TotalsCollector
24+
*/
25+
private $totalsCollector;
26+
27+
/**
28+
* @param TotalsCollector $totalsCollector
29+
*/
30+
public function __construct(
31+
TotalsCollector $totalsCollector
32+
) {
33+
$this->totalsCollector = $totalsCollector;
34+
}
35+
36+
/**
37+
* @inheritdoc
38+
*/
39+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
40+
{
41+
if (!isset($value['model'])) {
42+
throw new LocalizedException(__('"model" value should be specified'));
43+
}
44+
45+
$data = [];
46+
47+
/** @var Quote $quote */
48+
$quote = $value['model'];
49+
$appliedTaxes = $this->totalsCollector->collectQuoteTotals($value['model'])->getAppliedTaxes();
50+
51+
if (count($appliedTaxes) == 0) {
52+
return [];
53+
}
54+
55+
$currency = $quote->getQuoteCurrencyCode();
56+
foreach ($appliedTaxes as $appliedTax) {
57+
$data[] = [
58+
'label' => $appliedTax['id'],
59+
'amount' => ['value' => $appliedTax['amount'], 'currency' => $currency]
60+
];
61+
}
62+
63+
return $data;
64+
}
65+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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\Api\CartTotalRepositoryInterface;
15+
16+
/**
17+
* @inheritdoc
18+
*/
19+
class Totals implements ResolverInterface
20+
{
21+
/**
22+
* @var CartTotalRepositoryInterface
23+
*/
24+
private $cartTotalRepository;
25+
26+
/**
27+
* @param CartTotalRepositoryInterface $cartTotalRepository
28+
*/
29+
public function __construct(
30+
CartTotalRepositoryInterface $cartTotalRepository
31+
) {
32+
$this->cartTotalRepository = $cartTotalRepository;
33+
}
34+
35+
/**
36+
* @inheritdoc
37+
*/
38+
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
39+
{
40+
if (!isset($value['model'])) {
41+
throw new LocalizedException(__('"model" value should be specified'));
42+
}
43+
44+
$cartTotals = $this->cartTotalRepository->get($value['model']->getId());
45+
46+
$currency = $cartTotals->getQuoteCurrencyCode();
47+
$data = $this->addCurrencyCode([
48+
'grand_total' => ['value' => $cartTotals->getGrandTotal(), ],
49+
'subtotal_including_tax' => ['value' => $cartTotals->getSubtotalInclTax()],
50+
'subtotal_excluding_tax' => ['value' => $cartTotals->getSubtotal()],
51+
'subtotal_with_discount_excluding_tax' => ['value' => $cartTotals->getSubtotalWithDiscount()]
52+
], $currency);
53+
54+
$data['model'] = $value['model'];
55+
56+
return $data;
57+
}
58+
59+
/**
60+
* Adds currency code to the totals
61+
*
62+
* @param array $totals
63+
* @param string|null $currencyCode
64+
* @return array
65+
*/
66+
private function addCurrencyCode(array $totals, $currencyCode): array
67+
{
68+
foreach ($totals as &$total) {
69+
$total['currency'] = $currencyCode;
70+
}
71+
72+
return $totals;
73+
}
74+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,19 @@ input PaymentMethodInput {
120120
additional_data: PaymentMethodAdditionalDataInput
121121
}
122122

123+
type CartPrices {
124+
grand_total: Money
125+
subtotal_including_tax: Money
126+
subtotal_excluding_tax: Money
127+
subtotal_with_discount_excluding_tax: Money
128+
applied_taxes: [CartTaxItem] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTaxes")
129+
}
130+
131+
type CartTaxItem {
132+
amount: Money!
133+
label: String!
134+
}
135+
123136
input PaymentMethodAdditionalDataInput {
124137
}
125138

@@ -150,6 +163,7 @@ type Cart {
150163
billing_address: CartAddress! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\BillingAddress")
151164
available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods")
152165
selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod")
166+
prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Totals")
153167
}
154168

155169
type CartAddress {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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\Integration\Api\CustomerTokenServiceInterface;
11+
use Magento\Quote\Model\QuoteFactory;
12+
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
13+
use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
14+
use Magento\TestFramework\Helper\Bootstrap;
15+
use Magento\TestFramework\TestCase\GraphQlAbstract;
16+
17+
/**
18+
* Test getting cart totals for registered customer
19+
*/
20+
class CartTotalsTest extends GraphQlAbstract
21+
{
22+
/**
23+
* @var CustomerTokenServiceInterface
24+
*/
25+
private $customerTokenService;
26+
27+
/**
28+
* @var QuoteResource
29+
*/
30+
private $quoteResource;
31+
32+
/**
33+
* @var QuoteFactory
34+
*/
35+
private $quoteFactory;
36+
37+
/**
38+
* @var QuoteIdToMaskedQuoteIdInterface
39+
*/
40+
private $quoteIdToMaskedId;
41+
42+
protected function setUp()
43+
{
44+
$objectManager = Bootstrap::getObjectManager();
45+
$this->quoteResource = $objectManager->create(QuoteResource::class);
46+
$this->quoteFactory = $objectManager->create(QuoteFactory::class);
47+
$this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class);
48+
$this->customerTokenService = $objectManager->create(CustomerTokenServiceInterface::class);
49+
}
50+
51+
/**
52+
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_tax.php
53+
*/
54+
public function testGetCartTotalsForCustomerWithTaxApplied()
55+
{
56+
$maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_tax');
57+
58+
$query = <<<QUERY
59+
{
60+
cart(cart_id: "$maskedQuoteId") {
61+
prices {
62+
grand_total {
63+
value,
64+
currency
65+
}
66+
subtotal_including_tax {
67+
value
68+
currency
69+
}
70+
subtotal_excluding_tax {
71+
value
72+
currency
73+
}
74+
subtotal_with_discount_excluding_tax {
75+
value
76+
currency
77+
}
78+
applied_taxes {
79+
label
80+
amount {
81+
value
82+
currency
83+
}
84+
}
85+
}
86+
}
87+
}
88+
QUERY;
89+
$response = $this->sendRequestWithToken($query);
90+
91+
self::assertArrayHasKey('prices', $response['cart']);
92+
$pricesResponse = $response['cart']['prices'];
93+
self::assertEquals(10, $pricesResponse['grand_total']['value']);
94+
self::assertEquals(10.83, $pricesResponse['subtotal_including_tax']['value']);
95+
self::assertEquals(10, $pricesResponse['subtotal_excluding_tax']['value']);
96+
self::assertEquals(10, $pricesResponse['subtotal_with_discount_excluding_tax']['value']);
97+
98+
$appliedTaxesResponse = $pricesResponse['applied_taxes'];
99+
100+
self::assertEquals('US-CA-*-Rate 1', $appliedTaxesResponse[0]['label']);
101+
self::assertEquals(0.83, $appliedTaxesResponse[0]['amount']['value']);
102+
self::assertEquals('USD', $appliedTaxesResponse[0]['amount']['currency']);
103+
}
104+
105+
/**
106+
* @param string $reversedQuoteId
107+
* @return string
108+
*/
109+
private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
110+
{
111+
$quote = $this->quoteFactory->create();
112+
$this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
113+
114+
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
115+
}
116+
117+
/**
118+
* Sends a GraphQL request with using a bearer token
119+
*
120+
* @param string $query
121+
* @return array
122+
* @throws \Magento\Framework\Exception\AuthenticationException
123+
*/
124+
private function sendRequestWithToken(string $query): array
125+
{
126+
127+
$customerToken = $this->customerTokenService->createCustomerAccessToken('[email protected]', 'password');
128+
$headerMap = ['Authorization' => 'Bearer ' . $customerToken];
129+
130+
return $this->graphQlQuery($query, [], '', $headerMap);
131+
}
132+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
use Magento\Catalog\Api\ProductRepositoryInterface;
8+
use Magento\Catalog\Model\ProductFactory;
9+
use Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory as TaxClassCollectionFactory;
10+
use Magento\Tax\Model\ClassModel as TaxClassModel;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
13+
$objectManager = Bootstrap::getObjectManager();
14+
/** @var ProductRepositoryInterface $productRepository */
15+
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
16+
/** @var ProductFactory $productFactory */
17+
$productFactory = $objectManager->create(ProductFactory::class);
18+
$product = $productFactory->create();
19+
$product
20+
->setTypeId('simple')
21+
->setId(1)
22+
->setAttributeSetId(4)
23+
->setWebsiteIds([1])
24+
->setName('Simple Product')
25+
->setSku('simple')
26+
->setPrice(10)
27+
->setMetaTitle('meta title')
28+
->setMetaKeyword('meta keyword')
29+
->setMetaDescription('meta description')
30+
->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
31+
->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
32+
->setStockData(['use_config_manage_stock' => 1, 'qty' => 22, 'is_in_stock' => 1])
33+
->setQty(22);
34+
35+
36+
/** @var TaxClassCollectionFactory $taxClassCollectionFactory */
37+
$taxClassCollectionFactory = $objectManager->create(TaxClassCollectionFactory::class);
38+
$taxClassCollection = $taxClassCollectionFactory->create();
39+
40+
/** @var TaxClassModel $taxClass */
41+
$taxClassCollection->addFieldToFilter('class_type', TaxClassModel::TAX_CLASS_TYPE_PRODUCT);
42+
$taxClass = $taxClassCollection->getFirstItem();
43+
44+
$product->setCustomAttribute('tax_class_id', $taxClass->getClassId());
45+
$productRepository->save($product);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
require __DIR__ . '/product_simple_rollback.php';
8+
require __DIR__ . '/../../Tax/_files/tax_classes_rollback.php';

0 commit comments

Comments
 (0)