diff --git a/src/Model/Resolver/ApplyCoupon.php b/src/Model/Resolver/ApplyCoupon.php deleted file mode 100644 index 3ad086e..0000000 --- a/src/Model/Resolver/ApplyCoupon.php +++ /dev/null @@ -1,113 +0,0 @@ -couponManagement = $couponManagement; - } - - /** - * Fetches the data from persistence models and format it according to the GraphQL schema. - * - * @param \Magento\Framework\GraphQl\Config\Element\Field $field - * @param ContextInterface $context - * @param ResolveInfo $info - * @param array|null $value - * @param array|null $args - * @return mixed|Value - * @throws \Exception - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) - { - $couponCode = $args['coupon_code']; - - if (empty($couponCode)) { - throw new GraphQlInputException(__('Coupon Code can not be empty')); - } - - $cart = $this->getCart($args); - - if ($cart->getItemsCount() < 1) { - throw new CartCouponException(__("Cart does not contain products")); - } - - $cartId = $cart->getId(); - $appliedCouponCode = $this->couponManagement->get($cartId); - - if ($appliedCouponCode !== null) { - throw new CartCouponException( - __('A coupon is already applied to the cart. Please remove it to apply another.') - ); - } - - try { - $this->couponManagement->set($cartId, $couponCode); - } catch (NoSuchEntityException | CouldNotSaveException $e) { - throw new CartCouponException(__('Coupon Code is invalid'), $e); - } - - return []; - } -} diff --git a/src/Model/Resolver/CartIsInStorePickupAvailable.php b/src/Model/Resolver/CartIsInStorePickupAvailable.php new file mode 100644 index 0000000..adf8e35 --- /dev/null +++ b/src/Model/Resolver/CartIsInStorePickupAvailable.php @@ -0,0 +1,62 @@ +inStorePickupDeliveryAvailableForCart = $inStorePickupDeliveryAvailableForCart; + } + + /** + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @return array + */ + 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->inStorePickupDeliveryAvailableForCart->execute((int) $cart->getId()); + } +} diff --git a/src/Model/Resolver/CartItems.php b/src/Model/Resolver/CartItems.php new file mode 100644 index 0000000..d5c77d4 --- /dev/null +++ b/src/Model/Resolver/CartItems.php @@ -0,0 +1,156 @@ +emulation = $emulation; + $this->helperImage = $helperImage; + $this->storeManager = $storeManager; + } + + /** + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @return array + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $result = parent::resolve($field, $context, $info, $value, $args); + + $cartItems = []; + + $storeId = $this->storeManager->getStore()->getId(); + $this->emulation->startEnvironmentEmulation($storeId, Area::AREA_FRONTEND, true); + + foreach ($result as $itemData) { + $cartItem = $itemData['model']; + $product = $cartItem->getProduct(); + + $cartItems[] = array_merge($itemData, [ + 'sku' => $cartItem->getSku(), + 'product' => array_merge($itemData['product'], + [ + 'thumbnail' => + [ + 'path' => $product->getThumbnail(), + 'url' => $this->getImageUrl('thumbnail', $product->getThumbnail(), $product) + ] + ]) + ]); + } + + $this->emulation->stopEnvironmentEmulation(); + + return $cartItems; + } + + /** + * @param string $imageType + * @param string|null $imagePath + * @param Product $product + * @return string + */ + protected function getImageUrl( + string $imageType, + ?string $imagePath, + $product + ): string { + if (!isset($imagePath)) { + return $this->helperImage->getDefaultPlaceholderUrl($imageType); + } + + $imageId = sprintf('scandipwa_%s', $imageType); + + $image = $this->helperImage + ->init( + $product, + $imageId, + ['type' => $imageType] + ) + ->constrainOnly(true) + ->keepAspectRatio(true) + ->keepTransparency(true) + ->keepFrame(false); + + return $image->getUrl(); + } +} diff --git a/src/Model/Resolver/CartMinimumOrderAmount.php b/src/Model/Resolver/CartMinimumOrderAmount.php new file mode 100644 index 0000000..36476f2 --- /dev/null +++ b/src/Model/Resolver/CartMinimumOrderAmount.php @@ -0,0 +1,73 @@ +amountValidationMessage = $amountValidationMessage; + } + + /** + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @return array + * @throws NotFoundException + */ + 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')); + } + + $quote = $value['model']; + + $minimumOrderAmountReached = $quote->validateMinimumAmount(); + $minimumOrderDescription = $this->amountValidationMessage->getMessage(); + + return [ + 'minimum_order_amount_reached' => $minimumOrderAmountReached, + 'minimum_order_description' => $minimumOrderDescription + ]; + } +} diff --git a/src/Model/Resolver/CartPrices.php b/src/Model/Resolver/CartPrices.php new file mode 100644 index 0000000..41c0bdc --- /dev/null +++ b/src/Model/Resolver/CartPrices.php @@ -0,0 +1,107 @@ +totalsCollector = $totalsCollector; + } + + /** + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @return array + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $result = parent::resolve($field, $context, $info, $value, $args); + + $quote = $result['model']; + + $applied_rule_ids = $quote->getAppliedRuleIds(); + $coupon_code = $quote->getCouponCode(); + $quote_currency_code = $quote->getQuoteCurrencyCode(); + + $cartTotals = $this->totalsCollector->collectQuoteTotals($quote); + + return array_merge($result, [ + 'applied_rule_ids' => $applied_rule_ids, + 'applied_taxes' => $this->getAppliedTaxes($cartTotals, $quote_currency_code), + 'coupon_code' => $coupon_code, + 'quote_currency_code' => $quote_currency_code + ]); + } + + /** + * Returns taxes applied to the current quote + * + * @param Total $total + * @param string $currency + * @return array + */ + protected function getAppliedTaxes(Total $total, string $currency): array + { + $appliedTaxesData = []; + $appliedTaxes = $total->getAppliedTaxes(); + + if (empty($appliedTaxes)) { + return $appliedTaxesData; + } + + foreach ($appliedTaxes as $appliedTax) { + $appliedTaxesData[] = [ + 'label' => $appliedTax['id'], + 'amount' => ['value' => $appliedTax['amount'], 'currency' => $currency], + 'percent' => $appliedTax['percent'] + ]; + } + + return $appliedTaxesData; + } +} diff --git a/src/Model/Resolver/GetCartForCustomer.php b/src/Model/Resolver/GetCartForCustomer.php deleted file mode 100644 index 6e7b63d..0000000 --- a/src/Model/Resolver/GetCartForCustomer.php +++ /dev/null @@ -1,304 +0,0 @@ -configurable = $configurable; - $this->productFactory = $productFactory; - $this->productPostProcessor = $productPostProcessor; - $this->customizableOption = $customizableOption; - $this->bundleOptions = $bundleOptions; - $this->serializer = $serializer; - $this->linkRepository = $linkRepository; - $this->quoteIdToMaskedQuoteId = $quoteIdToMaskedQuoteId; - $this->inStorePickupDeliveryAvailableForCart = $inStorePickupDeliveryAvailableForCart; - $this->amountValidationMessage = $amountValidationMessage; - } - - /** - * @param QuoteItem $item - * @param Product $product - * @return array - * @throws LocalizedException - */ - protected function mergeQuoteItemData( - QuoteItem $item, - Product $product - ) { - return [ - 'product' => $this->productsData[$product->getId()], - 'customizable_options' => $this->getCustomizableOptions($item), - 'bundle_options' => $this->bundleOptions->getData($item), - 'downloadable_links' => $this->getDownloadableLinks($item, $product) - ] + $item->getData(); - } - - /** - * @param $item - * @param $product - * @return array - */ - private function getDownloadableLinks($item, $product): array - { - $quoteItemLinks= $item->getOptionByCode('downloadable_link_ids'); - - if (null === $quoteItemLinks) { - return []; - } - - $downloadableLinks = []; - $downloadableLinkIds = explode(',', $quoteItemLinks->getValue()); - $productLinks = $this->linkRepository->getLinksByProduct($product); - - /** @var Link $productLink */ - foreach ($productLinks as $productLink) { - if (in_array($productLink->getId(), $downloadableLinkIds)){ - $downloadableLinks[] = [ - 'label' => $productLink->getTitle(), - 'id' => $productLink->getId() - ]; - } - } - - return $downloadableLinks; - } - - - /** - * @param $item - * @return array - * @throws LocalizedException - */ - private function getCustomizableOptions($item): array - { - $quoteItemOption = $item->getOptionByCode('option_ids'); - - if (null === $quoteItemOption) { - return []; - } - - $customizableOptionsData = []; - $customizableOptionIds = explode(',', $quoteItemOption->getValue()); - - foreach ($customizableOptionIds as $customizableOptionId) { - $customizableOption = $this->customizableOption->getData( - $item, - (int)$customizableOptionId - ); - $customizableOptionsData[] = $customizableOption; - } - - return $customizableOptionsData; - } - - /** - * @param AddressInterface $address - * @return array - */ - private function getAppliedTaxes(AddressInterface $address): array - { - $taxes = $address->getData('applied_taxes'); - - if (is_string($taxes)) { - $taxes = $this->serializer->unserialize($taxes); - } - - return is_array($taxes) ? array_values($taxes) : []; - } - - /** - * Fetches the data from persistence models and format it according to the GraphQL schema. - * - * @param Field $field - * @param ContextInterface $context - * @param ResolveInfo $info - * @param array|null $value - * @param array|null $args - * @return Value|CartInterface|mixed - * @throws NotFoundException - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) { - $cart = $this->getCart($args); - $items = $cart->getItems(); - $itemsData = []; - - if ($items) { - // Prepare product data in advance - $products = array_map(function ($item) { - return $item->getProduct(); - }, $items); - - $adjustedInfo = $info->fieldNodes[0]; - $this->productsData = $this->productPostProcessor->process( - $products, - 'items/product', - $adjustedInfo, - ['isCartProduct'=> true] - ); - - foreach ($items as $item) { - /** @var QuoteItem $item */ - $product = $item->getProduct(); - $itemsData[] = $this->mergeQuoteItemData($item, $product); - } - } - - $cartData = $cart->getData(); - // In interface it is PHPDocumented that it returns bool, - // while in implementation it returns int. - $is_virtual = (bool)$cart->isVirtual(); - $address = $is_virtual ? $cart->getBillingAddress() : $cart->getShippingAddress(); - $tax_amount = $address->getTaxAmount(); - $discount_amount = $address->getDiscountAmount(); - $applied_taxes = $this->getAppliedTaxes($address); - $grand_total = $address->getGrandTotal(); - $subtotal_incl_tax = $address->getSubtotalInclTax(); - $shipping_tax_amount = $address->getShippingTaxAmount(); - $shipping_amount = $address->getShippingAmount(); - $shipping_incl_tax = $address->getShippingInclTax(); - $shipping_method = $address->getShippingMethod(); - $masked_id = $this->quoteIdToMaskedQuoteId->execute(intval($cart->getId())); - $isInStorePickupAvailable = $this->inStorePickupDeliveryAvailableForCart->execute((int) $cart->getId()); - $minimumOrderAmountReached = $cart->validateMinimumAmount(); - $minimumOrderDescription = $this->amountValidationMessage->getMessage(); - - return array_merge( - $cartData, - [ - 'id' => $masked_id, - 'items' => $itemsData, - 'tax_amount' => $tax_amount, - 'subtotal_incl_tax' => $subtotal_incl_tax, - 'discount_amount' => $discount_amount, - 'is_virtual' => $is_virtual, - 'applied_taxes' => $applied_taxes, - 'grand_total' => $grand_total, - 'shipping_tax_amount' => $shipping_tax_amount, - 'shipping_amount' => $shipping_amount, - 'shipping_incl_tax' => $shipping_incl_tax, - 'shipping_method' => $shipping_method, - 'shipping_address' => $cart->getShippingAddress(), - 'is_in_store_pickup_available' => $isInStorePickupAvailable, - 'minimum_order_amount' => [ - 'minimum_order_amount_reached' => $minimumOrderAmountReached, - 'minimum_order_description' => $minimumOrderDescription - ] - ] - ); - } -} diff --git a/src/Model/Resolver/PlaceOrder.php b/src/Model/Resolver/PlaceOrder.php deleted file mode 100644 index ee99b71..0000000 --- a/src/Model/Resolver/PlaceOrder.php +++ /dev/null @@ -1,134 +0,0 @@ -getCartForUser = $getCartForUser; - $this->cartManagement = $cartManagement; - $this->orderRepository = $orderRepository; - $this->checkCartCheckoutAllowance = $checkCartCheckoutAllowance; - $this->storeManager = $storeManager; - $this->cartRepository = $cartRepository; - } - - /** - * @inheritdoc - */ - public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) - { - $guestCartId = $args['guestCartId'] ?? ''; - - $customerId = $context->getUserId(); - $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); - - if ($guestCartId !== '') { - $cart = $this->getCartForUser->execute($guestCartId, $customerId, $storeId); - } else { - $cart = $this->cartManagement->getCartForCustomer($customerId); - } - - $this->checkCartCheckoutAllowance->execute($cart); - - if ((int)$context->getUserId() === 0) { - if (!$cart->getCustomerEmail()) { - throw new GraphQlInputException(__("Guest email for cart is missing.")); - } - $cart->setCheckoutMethod(CartManagementInterface::METHOD_GUEST); - } - - try { - $currStoreId = $this->storeManager->getStore()->getId(); - $cart->setStoreId($currStoreId); - $this->cartRepository->save($cart); - - $orderId = $this->cartManagement->placeOrder($cart->getId()); - $order = $this->orderRepository->get($orderId); - - return [ - 'order' => [ - 'order_id' => $order->getIncrementId(), - ], - ]; - } catch (NoSuchEntityException $e) { - throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e); - } catch (LocalizedException $e) { - throw new GraphQlInputException(__('Unable to place order: %message', ['message' => $e->getMessage()]), $e); - } - } -} diff --git a/src/Model/Resolver/RemoveCartItem.php b/src/Model/Resolver/RemoveCartItem.php deleted file mode 100644 index d27320a..0000000 --- a/src/Model/Resolver/RemoveCartItem.php +++ /dev/null @@ -1,94 +0,0 @@ -overriderCartId = $overriderCartId; - $this->cartItemRepository = $cartItemRepository; - $this->guestCartItemRepository = $guestCartItemRepository; - } - - /** - * Fetches the data from persistence models and format it according to the GraphQL schema. - * - * @param \Magento\Framework\GraphQl\Config\Element\Field $field - * @param ContextInterface $context - * @param ResolveInfo $info - * @param array|null $value - * @param array|null $args - * @return mixed|Value - * @throws \Exception - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) - { - ['item_id' => $itemId] = $args; - - if (isset($args['guestCartId'])) { - $this->guestCartItemRepository->deleteById($args['guestCartId'], $itemId); - } else { - $this->cartItemRepository->deleteById($this->overriderCartId->getOverriddenValue(), $itemId); - } - - return []; - } -} \ No newline at end of file diff --git a/src/Model/Resolver/RemoveCoupon.php b/src/Model/Resolver/RemoveCoupon.php deleted file mode 100644 index e5fbba6..0000000 --- a/src/Model/Resolver/RemoveCoupon.php +++ /dev/null @@ -1,93 +0,0 @@ -couponManagement = $couponManagement; - } - - /** - * Fetches the data from persistence models and format it according to the GraphQL schema. - * - * @param \Magento\Framework\GraphQl\Config\Element\Field $field - * @param ContextInterface $context - * @param ResolveInfo $info - * @param array|null $value - * @param array|null $args - * @return mixed|Value - * @throws \Exception - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) - { - $cart = $this->getCart($args); - - if ($cart->getItemsCount() < 1) { - throw new CartCouponException(__("Cart does not contain products")); - } - - $this->couponManagement->remove($cart->getId()); - - return []; - } -} diff --git a/src/Model/Resolver/SaveCartItem.php b/src/Model/Resolver/SaveCartItem.php deleted file mode 100755 index eefc90f..0000000 --- a/src/Model/Resolver/SaveCartItem.php +++ /dev/null @@ -1,586 +0,0 @@ -quoteIdMaskFactory = $quoteIdMaskFactory; - $this->quoteRepository = $quoteRepository; - $this->overriderCartId = $overriderCartId; - $this->productRepository = $productRepository; - $this->attributeRepository = $attributeRepository; - $this->quoteIdMaskResource = $quoteIdMaskResource; - $this->configurableType = $configurableType; - $this->stockStatusRepository = $stockStatusRepository; - $this->getStockItemData = $getStockItemData; - $this->getReservationsQuantity = $getReservationsQuantity; - $this->getStockItemConfiguration = $getStockItemConfiguration; - $this->imageUpload = $imageUpload; - $this->updateCartItem = $updateCartItem; - } - - /** - * @param array $options - * @return array - */ - private function prepareOptions(array $options): array - { - if (isset ($options['product_option']['extension_attributes']['configurable_item_options'])) { - $configurableOptions = &$options['product_option']['extension_attributes']['configurable_item_options']; - $stringifiedOptionValues = array_map(function ($item) { - $item['option_value'] = (string)$item['option_value']; - return $item; - }, $configurableOptions); - $configurableOptions = $stringifiedOptionValues; - } - - return $options; - } - - /** - * @param string $request - * @param Product $product - * @param array $options - * @return array - */ - private function getOptionsFromBuyRequest(Product $product, array $options) : array { - $request = $options['product_option']['buy_request']; - $data = json_decode($request, true); - $data['product'] = $product->getEntityId(); - $data['qty'] = $options['quantity']; - - return $data; - } - - /** - * @param Product $product - * @param array $options - * @return array - */ - private function getOptionsFromExtensions(Product $product, array $options) : array { - $options = $this->prepareOptions($options); - $data = [ - 'product' => $product->getEntityId(), - 'qty' => $options['quantity'] - ]; - - switch ($product->getTypeId()) { - case ProductType::TYPE_SIMPLE: - case ProductType::TYPE_VIRTUAL: - case Configurable::TYPE_CODE: - $this->setCustomizableOptions($options, $data); - $data = $this->setConfigurableRequestOptions($options, $data); - break; - case Type::TYPE_CODE: - $data = $this->setBundleRequestOptions($options, $data); - break; - case DownloadableType::TYPE_DOWNLOADABLE: - $this->setCustomizableOptions($options, $data); - $data = $this->setDownloadableRequestLinks($options, $data); - break; - } - - return $data; - } - - /** - * @param Product $product - * @param array $options - * @return DataObject - */ - private function prepareAddItem(Product $product, array $options): DataObject - { - if (isset($options['product_option']['buy_request'])) { - $data = $this->getOptionsFromBuyRequest($product, $options); - } else { - $data = $this->getOptionsFromExtensions($product, $options); - } - - $request = new DataObject(); - $request->setData($data); - - return $request; - } - - /** - * @param array $options - * @param array $data - * @return array - */ - private function setConfigurableRequestOptions(array $options, array $data): array - { - $configurableOptions = $options['product_option']['extension_attributes']['configurable_item_options'] ?? []; - $superAttributes = []; - - foreach ($configurableOptions as $option) { - $superAttributes[$option['option_id']] = $option['option_value']; - } - - $data['super_attribute'] = $superAttributes; - return $data; - } - - /** - * @param array $options - * @param array $data - */ - private function setCustomizableOptions(array $options, array &$data): void - { - $customizableOptionsData = $options['product_option']['extension_attributes']['customizable_options'] ?? []; - $customizableOptions = $this->getCustomizableOptions($customizableOptionsData); - // Necessary for multi selections, i.e., checkboxes which have same parent option_id - $customizableOptionsArrayData = $options['product_option']['extension_attributes']['customizable_options_multi'] ?? []; - $customizableOptionsMulti = $this->getCustomizableOptions($customizableOptionsArrayData, true); - - if (count($customizableOptions)) { - foreach ($customizableOptions as $key => $value) { - $data['options'][$key] = $value; - } - } - - if (count($customizableOptionsMulti)) { - foreach ($customizableOptionsMulti as $key => $value) { - $data['options'][$key] = $value; - } - } - } - - /** - * @param $customizableOptions - * @param bool $isMulti - * @return array - */ - private function getCustomizableOptions($customizableOptions, $isMulti = false): array - { - $data = []; - - if (count($customizableOptions)) { - foreach ($customizableOptions as $customizableOption) { - if ($isMulti) { - $data[$customizableOption['option_id']][] = $customizableOption['option_value']; - } else { - $data[$customizableOption['option_id']] = $customizableOption['option_value']; - } - } - } - - return $data; - } - - /** - * @param array $options - * @param array $data - * @return array - */ - private function setBundleRequestOptions(array $options, array $data): array - { - $data['bundle_option'] = []; - $data['bundle_option_qty'] = []; - $bundleOptions = $options['product_option']['extension_attributes']['bundle_options'] ?? []; - - foreach ($bundleOptions as $bundleOption) { - $optionId = $bundleOption['id']; - $data['bundle_option'][$optionId][] = $bundleOption['value']; - $data['bundle_option_qty'][$optionId] = $bundleOption['quantity']; - } - - return $data; - } - - /** - * @param array $options - * @param array $data - * @return array - */ - private function setDownloadableRequestLinks(array $options, array $data): array - { - $data['links'] = []; - $linkOptions = $options['product_option']['extension_attributes']['downloadable_product_links'] ?? []; - foreach ($linkOptions as $link) { - $data['links'][] = $link['link_id']; - } - return $data; - } - - /** - * @param string $guestCardId - * @return string - */ - protected function getGuestQuoteId(string $guestCardId): string - { - $quoteIdMask = $this->quoteIdMaskFactory->create(); - $this->quoteIdMaskResource->load($quoteIdMask, $guestCardId, 'masked_id'); - - return $quoteIdMask->getQuoteId() ?? ''; - } - - /** - * Fetches the data from persistence models and format it according to the GraphQL schema. - * - * @param Field $field - * @param ContextInterface $context - * @param ResolveInfo $info - * @param array|null $value - * @param array|null $args - * @return mixed|Value - * @throws Exception - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) - { - $requestCartItem = $args['cartItem']; - if (!$this->validateCartItem($requestCartItem)) { - throw new GraphQlInputException(new Phrase('Cart item ID or product SKU must be passed')); - } - $quoteId = isset($args['guestCartId']) - ? $this->getGuestQuoteId($args['guestCartId']) - : $this->overriderCartId->getOverriddenValue(); - $quote = $this->quoteRepository->getActive($quoteId); - ['quantity' => $qty] = $requestCartItem; - - $itemId = $this->getItemId($requestCartItem); - - if ($itemId) { - $cartItem = $quote->getItemById($itemId); - $product = $cartItem->getProduct(); - $options = $product->getTypeInstance(true)->getOrderOptions($product) ?? []; - - if ($product->getData('has_options') && isset($options['options'])) { - $this->updateCartItem->execute($quote, $itemId, $qty, []); - } else if ($product->getTypeId() === Bundle::TYPE_CODE) { - $this->updateCartItem->execute($quote, $itemId, $qty, []); - } else { - $this->checkItemQty($cartItem, $qty); - $cartItem->setQty($qty); - } - - $this->quoteRepository->save($quote); - } else { - $sku = $this->getSku($requestCartItem); - $product = $this->productRepository->get($sku); - - if (!$product) { - throw new GraphQlNoSuchEntityException(new Phrase('Product could not be loaded')); - } - - $newQuoteItem = $this->buildQuoteItem( - $sku, - $qty, - (int) $quoteId, - $requestCartItem['product_option'] ?? [] - ); - - try { - $result = $quote->addProduct($product, $this->prepareAddItem( - $product, - $newQuoteItem - )); - if (is_string($result)){ - throw new GraphQlInputException(new Phrase($result)); - } - - $this->quoteRepository->save($quote); - } catch (\Exception $e) { - throw new GraphQlInputException(new Phrase($e->getMessage())); - } - - // Related to bug: https://github.com/magento/magento2/issues/2991 - $quote = $this->quoteRepository->getActive($quoteId); - $quote->setTotalsCollectedFlag(false)->collectTotals(); - $this->quoteRepository->save($quote); - - // We need file upload logic exactly after new quote has arrived - // Otherwise magento gives us quote with empty prices - $this->imageUpload->processFileUpload($quote, $requestCartItem); - } - - return []; - } - - /** - * @param CartItemInterface $cartItem - * @param $qty - * @throws GraphQlInputException - * @throws LocalizedException - * @throws SkuIsNotAssignedToStockException - */ - protected function checkItemQty(CartItemInterface $cartItem, $qty): void - { - $product = $cartItem->getProduct(); - - if ($cartItem->getProductType() === Configurable::TYPE_CODE) { - $attributesInfo = $cartItem->getBuyRequest()->getDataByKey('super_attribute'); - $product = $this->configurableType->getProductByAttributes($attributesInfo, $product); - } - - $stockStatus = $this->stockStatusRepository->get($product->getId()); - $stockItem = $stockStatus->getStockItem(); - - if (!$stockItem->getManageStock()) { // just skip all checks, if stock is not managed - return; - } - - $allowedBackorder = $stockItem->getBackorders(); - $fitsInStock = $qty <= $stockItem->getQty(); - - if (!$fitsInStock && !$allowedBackorder) { - throw new GraphQlInputException(new Phrase('Provided quantity exceeds stock limits')); - } - - $isMinSaleQuantityCheckFailed = $qty < $stockItem->getMinSaleQty(); - - if ($isMinSaleQuantityCheckFailed) { - throw new GraphQlInputException( - new Phrase('The fewest you may purchase is %1', [$stockItem->getMinSaleQty()]) - ); - } - - $isMaxSaleQuantityCheckFailed = $qty > $stockItem->getMaxSaleQty(); - - if ($isMaxSaleQuantityCheckFailed) { - throw new GraphQlInputException( - new Phrase('The requested qty exceeds the maximum qty allowed in shopping cart') - ); - } - - $stockId = $stockItem->getStockId(); - $sku = $product->getSku(); - - $stockItemData = $this->getStockItemData->execute($sku, $stockId); - - /** @var StockItemConfigurationInterface $stockItemConfiguration */ - $stockItemConfiguration = $this->getStockItemConfiguration->execute($sku, $stockId); - - $qtyWithReservation = $stockItemData[GetStockItemDataInterface::QUANTITY] + - $this->getReservationsQuantity->execute($sku, $stockId); - - $qtyLeftInStock = $qtyWithReservation - $stockItemConfiguration->getMinQty(); - - $isInStock = bccomp((string) $qtyLeftInStock, (string) $qty, 4) >= 0 || $allowedBackorder; - $isEnoughQty = (bool)$stockItemData[GetStockItemDataInterface::IS_SALABLE] && $isInStock; - - if (!$isEnoughQty) { - throw new GraphQlInputException(new Phrase('The requested quantity is not available')); - } - } - - /** - * @param string $sku - * @param float $qty - * @param int $quoteId - * @param array $options - * @return array - */ - protected function buildQuoteItem(string $sku, float $qty, int $quoteId, array $options = []): array - { - return [ - 'quantity' => $qty, - 'sku' => $sku, - 'quote_id' => $quoteId, - 'product_option' => $options - ]; - } - - /** - * @param array $cartItem - * @return bool - */ - private function isIdStructUsed(array $cartItem): bool - { - return array_key_exists('id', $cartItem) && is_array($cartItem['id']); - } - - /** - * @param array $cartItem - * @return int|null - */ - protected function getItemId(array $cartItem): ?int - { - if (isset($cartItem['item_id'])) { - return $cartItem['item_id']; - } - - if ($this->isIdStructUsed($cartItem)) { - return $this->getItemId($cartItem['id']); - } - - return null; - } - - /** - * @param array $cartItem - * @return string|null - */ - protected function getSku(array $cartItem): ?string - { - if (isset($cartItem['sku'])) { - return $cartItem['sku']; - } - - if ($this->isIdStructUsed($cartItem)) { - return $this->getSku($cartItem['id']); - } - - return null; - } - - /** - * @param array $cartItem - * @return bool - */ - protected function validateCartItem(array $cartItem): bool - { - return isset($cartItem['item_id']) || isset($cartItem['sku']) || isset($cartItem['id']); - } -} diff --git a/src/Model/Resolver/SetBillingAddressOnCart.php b/src/Model/Resolver/SetBillingAddressOnCart.php deleted file mode 100644 index 80e4f9b..0000000 --- a/src/Model/Resolver/SetBillingAddressOnCart.php +++ /dev/null @@ -1,123 +0,0 @@ -getCartForUser = $getCartForUser; - $this->cartManagement = $cartManagement; - $this->setBillingAddressOnCart = $setBillingAddressOnCart; - $this->checkCartCheckoutAllowance = $checkCartCheckoutAllowance; - $this->customerRepository = $customerRepository; - } - - /** - * @inheritdoc - */ - public function resolve( - Field $field, - $context, - ResolveInfo $info, - array $value = null, - array $args = null - ) { - $guestCartId = $args['input']['guest_cart_id'] ?? ''; - - if (empty($args['input']['billing_address'])) { - throw new GraphQlInputException(__('Required parameter "billing_address" is missing')); - } - $billingAddress = $args['input']['billing_address']; - $customerAddressId = isset($billingAddress['customer_address_id']) - ? $billingAddress['customer_address_id'] - : null; - $sameAsShipping = !!(isset($args['input']['same_as_shipping']) && $args['input']['same_as_shipping']); - - $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); - - $customerId = $context->getUserId(); - if ($guestCartId !== '') { - $cart = $this->getCartForUser->execute($guestCartId, $customerId, $storeId); - } else { - $cart = $this->cartManagement->getCartForCustomer($customerId); - } - - $this->checkCartCheckoutAllowance->execute($cart); - $billingAddressIsSaved = false; - - if ($sameAsShipping && $customerAddressId) { - $customer = $cart->getCustomer(); - $billingAddressId = $customer->getDefaultBilling(); - - if (!$billingAddressId) { - $customer->setDefaultBilling((string)$customerAddressId); - $this->customerRepository->save($customer); - $billingAddressIsSaved = true; - } else { - unset($billingAddress['customer_address_id']); - } - } - - if (!$billingAddressIsSaved) { - $this->setBillingAddressOnCart->execute($context, $cart, $billingAddress); - } - - return [ - 'cart' => [ - 'model' => $cart, - ], - ]; - } -} diff --git a/src/Model/Resolver/SetPaymentMethodOnCart.php b/src/Model/Resolver/SetPaymentMethodOnCart.php deleted file mode 100644 index bb6639b..0000000 --- a/src/Model/Resolver/SetPaymentMethodOnCart.php +++ /dev/null @@ -1,96 +0,0 @@ -getCartForUser = $getCartForUser; - $this->cartManagement = $cartManagement; - $this->setPaymentMethodOnCart = $setPaymentMethodOnCart; - $this->checkCartCheckoutAllowance = $checkCartCheckoutAllowance; - } - - /** - * @inheritdoc - */ - public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) - { - $guestCartId = $args['input']['guest_cart_id'] ?? ''; - - if (empty($args['input']['payment_method']['code'])) { - throw new GraphQlInputException(__('Required parameter "code" for "payment_method" is missing.')); - } - $paymentData = $args['input']['payment_method']; - - $storeId = (int)$context->getExtensionAttributes()->getStore()->getId(); - - $customerId = $context->getUserId(); - if ($guestCartId !== '') { - $cart = $this->getCartForUser->execute($guestCartId, $customerId, $storeId); - } else { - $cart = $this->cartManagement->getCartForCustomer($customerId); - } - - $this->checkCartCheckoutAllowance->execute($cart); - $this->setPaymentMethodOnCart->execute($cart, $paymentData); - - return [ - 'cart' => [ - 'model' => $cart, - ], - ]; - } -} diff --git a/src/Model/Resolver/ShippingAddress/SelectedShippingMethod.php b/src/Model/Resolver/ShippingAddress/SelectedShippingMethod.php new file mode 100644 index 0000000..c695ec5 --- /dev/null +++ b/src/Model/Resolver/ShippingAddress/SelectedShippingMethod.php @@ -0,0 +1,71 @@ + [ + 'city' => $address->getCity(), + 'country' => [ + 'code' => $address->getCountryId() + ], + 'email' => $address->getEmail(), + 'firstname' => $address->getFirstname(), + 'lastname' => $address->getLastname(), + 'postcode' => $address->getPostcode(), + 'region' => [ + 'label' => $address->getRegion() + ], + 'street' => $address->getStreet(), + 'telephone' => $address->getTelephone(), + 'vat_id' => $address->getVatId() + ], + 'amount_incl_tax' => $address->getShippingInclTax(), + 'tax_amount' => $address->getShippingTaxAmount() + ]); + } +} diff --git a/src/etc/di.xml b/src/etc/di.xml index 5dce1dc..d0ca514 100644 --- a/src/etc/di.xml +++ b/src/etc/di.xml @@ -34,10 +34,21 @@ ScandiPWA\QuoteGraphQl\Model\Product\Option\Type\File\ValidatorFile\Proxy - - - - - + + + + + + + + diff --git a/src/etc/schema.graphqls b/src/etc/schema.graphqls index f0c1767..8dc13a4 100755 --- a/src/etc/schema.graphqls +++ b/src/etc/schema.graphqls @@ -13,7 +13,6 @@ type Query { getPaymentMethods(guestCartId: String): [PaymentMethod] @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\GetPaymentMethods") - getCartForCustomer(guestCartId: String): QuoteData @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\GetCartForCustomer") getOrderList: OrderList @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\OrderListResolver") @doc(description: "The Sales Order query returns information about a Sales order") getOrderById(id: Int!): Order @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\ExpandedOrderResolver") @doc(description: "The Sales Order query returns information about a Sales order") getBraintreeConfig: Braintree @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\BraintreeResolver") @@ -24,42 +23,7 @@ type Query { type Mutation { estimateShippingCosts(address: EstimateShippingCostsAddress!, guestCartId: String): [ShippingMethod] @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\EstimateShippingCosts") saveAddressInformation(addressInformation: SaveAddressInformation!, guestCartId: String): PaymentDetails @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\SaveAddressInformation") - saveCartItem(cartItem: CartItemInput!, guestCartId: String): Query @resolver(class:"\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\SaveCartItem") - removeCartItem(guestCartId: String, item_id: Int!): Query @resolver(class:"\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\RemoveCartItem") - applyCoupon(guestCartId: String, coupon_code: String!): Query @resolver(class:"\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\ApplyCoupon") - removeCoupon(guestCartId: String): Query @resolver(class:"\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\RemoveCoupon") linkOrder(customer_email: String!): Boolean @resolver(class:"\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\LinkOrder") - - # Magento 2 overrides to stop sending logged in user cart IDs - s_setPaymentMethodOnCart(input: S_SetPaymentMethodOnCartInput!): SetPaymentMethodOnCartOutput @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\SetPaymentMethodOnCart") - s_placeOrder(guestCartId: String): PlaceOrderOutput @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder") - s_setBillingAddressOnCart(input: S_SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\SetBillingAddressOnCart") -} - -input S_SetBillingAddressOnCartInput { - guest_cart_id: String - billing_address: BillingAddressInput! - same_as_shipping: Boolean -} - -input S_SetPaymentMethodOnCartInput { - guest_cart_id: String - payment_method: PaymentMethodInput! -} - -input CartItemInput { - id: CartItemId - sku: String - quantity: Float! - quote_id: String - item_id: Int - product_type: String - product_option: ProductOptionInput -} - -input CartItemId { - sku: String, - item_id: Int } input ProductOptionInput { @@ -91,22 +55,6 @@ input DownloadableProductLinksInput { link_id: Int } -input PaymentInformation { - billing_address: AddressInput! - paymentMethod: PaymentMethodInput! -} - -input PaymentMethodInput { - method: String - additional_data: PaymentMethodAdditionalData -} - -input PaymentMethodAdditionalData { - payment_method_nonce: String - cc_stripejs_token: String - cc_save: Boolean -} - input AddressInput { method: String region: String @@ -152,12 +100,6 @@ type Braintree { is_three_d_secure: Boolean } -type QuoteData implements TotalsObject { - id: String - is_virtual: Boolean - applied_taxes: [AppliedTaxItem] -} - type PaymentTotals implements TotalsObject { } @@ -247,23 +189,6 @@ type MinimumOrderAmount { minimum_order_description: String } -type TotalsSegment { - code: String - title: String - value: Float - extension_attributes: ExtensionAttributes -} - -type ExtensionAttributes { - tax_grandtotal_details: [TaxGrandTotalDetails] -} - -type TaxGrandTotalDetails { - # rates: - amount: Float - group_id: Int -} - type ShippingMethod { carrier_code: String method_code: String @@ -379,17 +304,6 @@ type CartDisplayConfig { display_zero_tax_subtotal: Boolean } -type AppliedTaxItem { - amount: Float - percent: Float - rates: [AppliedTaxItemRate] -} - -type AppliedTaxItemRate { - percent: Float - title: String -} - extend input CartAddressInput { vat_id: String } @@ -425,3 +339,35 @@ input AddressExtensionAttributes { attribute_code: String value: String } + +#Retrieving a shopping cart via Magento's standard functions extension + +type Cart @doc(description: "Contains the contents and other details about a guest or customer cart.") { + minimum_order_amount: MinimumOrderAmount @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\CartMinimumOrderAmount") @doc(description: "Indicates whether the cart contains only virtual products.") + is_in_store_pickup_available: Boolean @resolver(class: "\\ScandiPWA\\QuoteGraphQl\\Model\\Resolver\\CartIsInStorePickupAvailable") @doc(description: "Indicates whether the pickup in store is available.") +} + +type CartPrices @doc(description: "Contains details about the final price of items in the cart, including discount and tax information.") { + applied_rule_ids: String @doc(description: "Ids of the rules applied to the cart.") + coupon_code: String @doc(description: "Coupon code applied to cart.") + quote_currency_code: String @doc(description: "Base quote currency code.") +} + +type CartTaxItem @doc(description: "Contains tax information about an item in the cart.") { + percent: Float @doc(description: "The tax percentage applied to the item.") +} + +interface CartItemInterface @typeResolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemTypeResolver") @doc(description: "An interface for products in a cart.") { + sku: String +} + +type SelectedShippingMethod @doc(description: "Contains details about the selected shipping method and carrier.") { + amount_incl_tax: Float @doc(description: "The cost of shipping including tax using this shipping method.") + tax_amount: Float @doc(description: "A shipping tax amount.") + address: CartAddress @doc(description: "An array of shipping addresses.") +} + +type CartAddress implements CartAddressInterface { + email: String + vat_id: String +}