diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php deleted file mode 100644 index fb742477ec99..000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php +++ /dev/null @@ -1,94 +0,0 @@ -dataObjectConverter = $dataObjectConverter; - } - - /** - * Collect and return information about shipping and billing addresses - * - * @param CartInterface $cart - * @return array - */ - public function getCartAddresses(CartInterface $cart): array - { - $addressData = []; - $shippingAddress = $cart->getShippingAddress(); - $billingAddress = $cart->getBillingAddress(); - - if ($shippingAddress) { - $shippingData = $this->dataObjectConverter->toFlatArray($shippingAddress, [], AddressInterface::class); - $shippingData['address_type'] = 'SHIPPING'; - $addressData[] = array_merge($shippingData, $this->extractAddressData($shippingAddress)); - } - - if ($billingAddress) { - $billingData = $this->dataObjectConverter->toFlatArray($billingAddress, [], AddressInterface::class); - $billingData['address_type'] = 'BILLING'; - $addressData[] = array_merge($billingData, $this->extractAddressData($billingAddress)); - } - - return $addressData; - } - - /** - * Extract the necessary address fields from address model - * - * @param QuoteAddress $address - * @return array - */ - private function extractAddressData(QuoteAddress $address): array - { - $addressData = [ - 'country' => [ - 'code' => $address->getCountryId(), - 'label' => $address->getCountry() - ], - 'region' => [ - 'code' => $address->getRegionCode(), - 'label' => $address->getRegion() - ], - 'street' => $address->getStreet(), - 'selected_shipping_method' => [ - 'code' => $address->getShippingMethod(), - 'label' => $address->getShippingDescription(), - 'free_shipping' => $address->getFreeShipping(), - ], - 'items_weight' => $address->getWeight(), - 'customer_notes' => $address->getCustomerNotes() - ]; - - return $addressData; - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/BillingAddressDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/BillingAddressDataProvider.php deleted file mode 100644 index bcd781025a6d..000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/BillingAddressDataProvider.php +++ /dev/null @@ -1,72 +0,0 @@ -cartRepository = $cartRepository; - $this->addressMapper = $addressMapper; - $this->dataObjectConverter = $dataObjectConverter; - } - - /** - * Collect and return information about a billing addresses - * - * @param CartInterface $cart - * @return null|array - */ - public function getCartAddresses(CartInterface $cart): ?array - { - $cart = $this->cartRepository->get($cart->getId()); - $billingAddress = $cart->getBillingAddress(); - - if (!$billingAddress) { - return null; - } - $billingData = $this->dataObjectConverter->toFlatArray($billingAddress, [], AddressInterface::class); - $addressData = array_merge($billingData, $this->addressMapper->toNestedArray($billingAddress)); - - return $addressData; - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/ShippingAddressesDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/ShippingAddressesDataProvider.php deleted file mode 100644 index eb9d76080959..000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/ShippingAddressesDataProvider.php +++ /dev/null @@ -1,76 +0,0 @@ -dataObjectConverter = $dataObjectConverter; - $this->cartRepository = $cartRepository; - $this->addressMapper = $addressMapper; - } - - /** - * Collect and return information about shipping addresses - * - * @param CartInterface $cart - * @return array - */ - public function getCartAddresses(CartInterface $cart): array - { - $cart = $this->cartRepository->get($cart->getId()); - $addressData = []; - $shippingAddresses = $cart->getAllShippingAddresses(); - - if ($shippingAddresses) { - foreach ($shippingAddresses as $shippingAddress) { - $shippingData = $this->dataObjectConverter->toFlatArray($shippingAddress, [], AddressInterface::class); - $addressData[] = array_merge($shippingData, $this->addressMapper->toNestedArray($shippingAddress)); - } - } - - return $addressData; - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/Mapper/Address.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php similarity index 64% rename from app/code/Magento/QuoteGraphQl/Model/Cart/Address/Mapper/Address.php rename to app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php index 64ac407e2692..b0e5070315d8 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/Mapper/Address.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromAddress.php @@ -5,26 +5,42 @@ */ declare(strict_types=1); -namespace Magento\QuoteGraphQl\Model\Cart\Address\Mapper; +namespace Magento\QuoteGraphQl\Model\Cart; +use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Quote\Api\Data\AddressInterface; use Magento\Quote\Model\Quote\Address as QuoteAddress; /** - * Class Address - * * Extract the necessary address fields from an Address model */ -class Address +class ExtractDataFromAddress { /** - * Converts Address model data to nested array + * @var ExtensibleDataObjectConverter + */ + private $dataObjectConverter; + + /** + * @param ExtensibleDataObjectConverter $dataObjectConverter + */ + public function __construct(ExtensibleDataObjectConverter $dataObjectConverter) + { + $this->dataObjectConverter = $dataObjectConverter; + } + + /** + * Converts Address model to flat array * * @param QuoteAddress $address * @return array */ - public function toNestedArray(QuoteAddress $address): array + public function execute(QuoteAddress $address): array { - $addressData = [ + $addressData = $this->dataObjectConverter->toFlatArray($address, [], AddressInterface::class); + $addressData['model'] = $address; + + $addressData = array_merge($addressData, [ 'country' => [ 'code' => $address->getCountryId(), 'label' => $address->getCountry() @@ -41,7 +57,7 @@ public function toNestedArray(QuoteAddress $address): array ], 'items_weight' => $address->getWeight(), 'customer_notes' => $address->getCustomerNotes() - ]; + ]); if (!$address->hasItems()) { return $addressData; diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php index 7de5a4ac05c2..a03533ecefff 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php @@ -11,7 +11,7 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\QuoteGraphQl\Model\Cart\Address\BillingAddressDataProvider; +use Magento\QuoteGraphQl\Model\Cart\ExtractDataFromAddress; /** * @inheritdoc @@ -19,17 +19,16 @@ class BillingAddress implements ResolverInterface { /** - * @var BillingAddressDataProvider + * @var ExtractDataFromAddress */ - private $addressDataProvider; + private $extractDataFromAddress; /** - * @param BillingAddressDataProvider $addressDataProvider + * @param ExtractDataFromAddress $extractDataFromAddress */ - public function __construct( - BillingAddressDataProvider $addressDataProvider - ) { - $this->addressDataProvider = $addressDataProvider; + public function __construct(ExtractDataFromAddress $extractDataFromAddress) + { + $this->extractDataFromAddress = $extractDataFromAddress; } /** @@ -40,9 +39,14 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value if (!isset($value['model'])) { throw new LocalizedException(__('"model" value should be specified')); } - $cart = $value['model']; - return $this->addressDataProvider->getCartAddresses($cart); + $billingAddress = $cart->getBillingAddress(); + if (null === $billingAddress) { + return null; + } + + $addressData = $this->extractDataFromAddress->execute($billingAddress); + return $addressData; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php deleted file mode 100644 index 69544672bf12..000000000000 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php +++ /dev/null @@ -1,48 +0,0 @@ -addressDataProvider = $addressDataProvider; - } - - /** - * @inheritdoc - */ - public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) - { - if (!isset($value['model'])) { - throw new LocalizedException(__('"model" value should be specified')); - } - - $cart = $value['model']; - - return $this->addressDataProvider->getCartAddresses($cart); - } -} diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php index caa0eee22d70..3a55ef9ae25a 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php @@ -11,7 +11,7 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\QuoteGraphQl\Model\Cart\Address\ShippingAddressesDataProvider; +use Magento\QuoteGraphQl\Model\Cart\ExtractDataFromAddress; /** * @inheritdoc @@ -19,17 +19,16 @@ class ShippingAddresses implements ResolverInterface { /** - * @var ShippingAddressesDataProvider + * @var ExtractDataFromAddress */ - private $addressDataProvider; + private $extractDataFromAddress; /** - * @param ShippingAddressesDataProvider $addressDataProvider + * @param ExtractDataFromAddress $extractDataFromAddress */ - public function __construct( - ShippingAddressesDataProvider $addressDataProvider - ) { - $this->addressDataProvider = $addressDataProvider; + public function __construct(ExtractDataFromAddress $extractDataFromAddress) + { + $this->extractDataFromAddress = $extractDataFromAddress; } /** @@ -40,9 +39,16 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value if (!isset($value['model'])) { throw new LocalizedException(__('"model" value should be specified')); } - $cart = $value['model']; - return $this->addressDataProvider->getCartAddresses($cart); + $addressesData = []; + $shippingAddresses = $cart->getAllShippingAddresses(); + + if (count($shippingAddresses)) { + foreach ($shippingAddresses as $shippingAddress) { + $addressesData[] = $this->extractDataFromAddress->execute($shippingAddress); + } + } + return $addressesData; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php new file mode 100644 index 000000000000..7804b8defe37 --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAdress/AvailableShippingMethods.php @@ -0,0 +1,77 @@ +dataObjectConverter = $dataObjectConverter; + $this->shippingMethodConverter = $shippingMethodConverter; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($value['model'])) { + throw new LocalizedException(__('"model" values should be specified')); + } + $address = $value['model']; + + // Allow shipping rates by setting country id for new addresses + if (!$address->getCountryId() && $address->getCountryCode()) { + $address->setCountryId($address->getCountryCode()); + } + + $address->setCollectShippingRates(true); + $address->collectShippingRates(); + $cart = $address->getQuote(); + + $methods = []; + $shippingRates = $address->getGroupedAllShippingRates(); + foreach ($shippingRates as $carrierRates) { + foreach ($carrierRates as $rate) { + $methods[] = $this->dataObjectConverter->toFlatArray( + $this->shippingMethodConverter->modelToDataObject($rate, $cart->getQuoteCurrencyCode()), + [], + ShippingMethodInterface::class + ); + } + } + return $methods; + } +} diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 0a86315382ae..c52bd52fb322 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - Cart(cart_id: String!): Cart @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart") @doc(description:"Returns information about shopping cart") + cart(cart_id: String!): Cart @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart") @doc(description:"Returns information about shopping cart") } type Mutation { @@ -117,8 +117,8 @@ type CartAddress { country: CartAddressCountry telephone: String address_type: AdressTypeEnum - selected_shipping_method: CheckoutShippingMethod - available_shipping_methods: [CheckoutShippingMethod] + selected_shipping_method: ShippingMethod + available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAdress\\AvailableShippingMethods") items_weight: Float customer_notes: String cart_items: [CartItemQuantity] @@ -139,12 +139,27 @@ type CartAddressCountry { label: String } -type CheckoutShippingMethod { +type ShippingMethod { code: String label: String free_shipping: Boolean! error_message: String - # TODO: Add more complex structure for shipping rates + amount: Float! + base_amount: Float! + amount_incl_tax: Float! + base_amount_incl_tax: Float! +} + +type AvailableShippingMethod { + carrier_code: String! + carrier_title: String! + method_code: String! + method_title: String! + error_message: String + amount: Float! + base_amount: Float! + price_excl_tax: Float! + price_incl_tax: Float! } enum AdressTypeEnum { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetAvailableShippingMethodsTest.php new file mode 100644 index 000000000000..7b72afa15701 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetAvailableShippingMethodsTest.php @@ -0,0 +1,123 @@ +customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + $this->quoteResource = $objectManager->create(QuoteResource::class); + $this->quote = $objectManager->create(Quote::class); + $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php + */ + public function testGetAvailableShippingMethods() + { + $this->quoteResource->load( + $this->quote, + 'test_order_1', + 'reserved_order_id' + ); + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + + $query = <<graphQlQuery( + $query, + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', 'password') + ); + self::assertArrayHasKey('cart', $response); + self::assertEquals($maskedQuoteId, $response['cart']['cart_id']); + self::assertArrayHasKey('shipping_addresses', $response['cart']); + self::assertCount(1, $response['cart']['shipping_addresses']); + self::assertArrayHasKey('available_shipping_methods', $response['cart']['shipping_addresses'][0]); + self::assertCount(1, $response['cart']['shipping_addresses'][0]['available_shipping_methods']); + + $expectedAddressData = [ + 'amount' => 10, + 'base_amount' => 10, + 'carrier_code' => 'flatrate', + 'carrier_title' => 'Flat Rate', + 'error_message' => '', + 'method_code' => 'flatrate', + 'method_title' => 'Fixed', + 'price_incl_tax' => 10, + 'price_excl_tax' => 10, + ]; + self::assertEquals( + $expectedAddressData, + $response['cart']['shipping_addresses'][0]['available_shipping_methods'][0] + ); + } + + /** + * @param string $email + * @param string $password + * @return array + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php index f0038351bcdc..44cd2ef526bd 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php @@ -68,9 +68,9 @@ public function testGetOwnCartForRegisteredCustomer() $response = $this->sendRequestWithToken($query); - self::assertArrayHasKey('Cart', $response); - self::assertNotEmpty($response['Cart']['items']); - self::assertNotEmpty($response['Cart']['shipping_addresses']); + self::assertArrayHasKey('cart', $response); + self::assertNotEmpty($response['cart']['items']); + self::assertNotEmpty($response['cart']['shipping_addresses']); } /** @@ -110,7 +110,7 @@ public function testGetCartForGuest() $response = $this->graphQlQuery($query); - self::assertArrayHasKey('Cart', $response); + self::assertArrayHasKey('cart', $response); } public function testGetNonExistentCart() @@ -134,7 +134,7 @@ private function prepareGetCartQuery( ) : string { return <<assertNewShippingAddressFields($shippingAddressResponse); + $this->assertAvailableShippingRates($shippingAddressResponse); } /** @@ -354,6 +366,17 @@ public function testSetNewRegisteredCustomerShippingAddressOnCart() city postcode telephone + available_shipping_methods { + amount + base_amount + carrier_code + carrier_title + error_message + method_code + method_title + price_excl_tax + price_incl_tax + } } } } @@ -366,6 +389,7 @@ public function testSetNewRegisteredCustomerShippingAddressOnCart() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewShippingAddressFields($shippingAddressResponse); + $this->assertAvailableShippingRates($shippingAddressResponse); } /** @@ -412,6 +436,17 @@ public function testSetSavedRegisteredCustomerShippingAddressOnCart() city postcode telephone + available_shipping_methods { + amount + base_amount + carrier_code + carrier_title + error_message + method_code + method_title + price_excl_tax + price_incl_tax + } } } } @@ -424,6 +459,7 @@ public function testSetSavedRegisteredCustomerShippingAddressOnCart() self::assertArrayHasKey('shipping_addresses', $cartResponse); $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertSavedShippingAddressFields($shippingAddressResponse); + $this->assertAvailableShippingRates($shippingAddressResponse); } /** @@ -466,6 +502,31 @@ private function assertSavedShippingAddressFields(array $shippingAddressResponse $this->assertResponseFields($shippingAddressResponse, $assertionMap); } + /** + * Verify the expected shipping method is available + * + * @param array $shippingAddressResponse + */ + private function assertAvailableShippingRates(array $shippingAddressResponse): void + { + $this->assertArrayHasKey('available_shipping_methods', $shippingAddressResponse); + $rate = current($shippingAddressResponse['available_shipping_methods']); + + $assertionMap = [ + ['response_field' => 'amount', 'expected_value' => 5], + ['response_field' => 'base_amount', 'expected_value' => 5], + ['response_field' => 'carrier_code', 'expected_value' => 'flatrate'], + ['response_field' => 'carrier_title', 'expected_value' => 'Flat Rate'], + ['response_field' => 'error_message', 'expected_value' => ''], + ['response_field' => 'method_code', 'expected_value' => 'flatrate'], + ['response_field' => 'method_title', 'expected_value' => 'Fixed'], + ['response_field' => 'price_incl_tax', 'expected_value' => 5], + ['response_field' => 'price_excl_tax', 'expected_value' => 5], + ]; + + $this->assertResponseFields($rate, $assertionMap); + } + /** * @param string $username * @return array