2

i want to add product to cart with additional option using GraphQL, i am using below mutation to add product to cart

mutation {
  addSimpleProductsToCart(
    input: {
      cart_id: "{{cart_id}}"
      cart_items: [
        {
          data: {
            quantity: 1
            sku: "tshirt"
          }
          customizable_options:[
          {
            id:1
            value_string:"helsslo"
          }
        ]
        }
      ]
    }
  ) {
    cart {
      items {
        id
        product {
          sku
          stock_status
        }
        quantity
      }
    }
  }
}

this is the responce i get

{
    "data": {
        "addSimpleProductsToCart": {
            "cart": {
                "items": [
                    {
                        "id": "112",
                        "product": {
                            "sku": "Tshirt",
                            "stock_status": "IN_STOCK"
                        },
                        "quantity": 2
                    }
                ]
            }
        }
    }
}

1 Answer 1

1

If you know that option id is 1 so you doing all right. But if your question is "why is SKU does not contains the suffix from selected custom option" the answer is: SKU from response in GraphQL just a product SKU (not a quote-item-sku), so it is ok. When you place your order the order item would has correct SKU with all suffixes from the selected custom options (in case they exists).

Here is my example with product configuration (custom options), request, response and order item (after placing order through GraphQL):

Product configuration in admin (custom option with id 5 has SKU "text" so result SKU would be "G-text" for order item):

product custom options configuration in admin section

Request:

mutation addSimpleProductsToCart($magentoCartId: String!) {
    addSimpleProductsToCart(
        input: {
            cart_id: $magentoCartId
            cart_items: [
                {
                    data: {
                        quantity: 1
                        sku: "G"
                    }
                    customizable_options: [
                        {
                            id: 5
                            value_string: "Hello GraphQL! :)"
                        }
                    ]
                }
            ]
        }
    ) {
        cart {
            items {
                ... on SimpleCartItem {
                    product {
                        name
                        sku
                    }
                    quantity
                    customizable_options {
                        label
                        type
                        values {
                            label
                            value
                            price {
                                value
                            }
                        }
                    }
                }
            }
        }
    }
}

Response (this cart already has two products without custom options):

{
    "data": {
        "addSimpleProductsToCart": {
            "cart": {
                "items": [
                    {
                        "product": {
                            "name": "A",
                            "sku": "A"
                        },
                        "quantity": 1,
                        "customizable_options": []
                    },
                    {
                        "product": {
                            "name": "B",
                            "sku": "B"
                        },
                        "quantity": 1,
                        "customizable_options": []
                    },
                    {
                        "product": {
                            "name": "G",
                            "sku": "G"
                        },
                        "quantity": 3,
                        "customizable_options": [
                            {
                                "label": "Text",
                                "type": "field",
                                "values": [
                                    {
                                        "label": "",
                                        "value": "Hello GraphQL! :)",
                                        "price": {
                                            "value": 33
                                        }
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        }
    }
}

And finally order items, where the las item has correct SKU - "G-text":

order items with correct sku

More detailed investigation

Let's go deeper and check how it is going in the resolver class. First point is vendor/magento/module-quote-graph-ql/etc/schema.graphqls where the mutation was defined:

addSimpleProductsToCart(input: AddSimpleProductsToCartInput @doc(description: "An input object that defines which simple products to add to the cart.")): AddSimpleProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart") @doc(description:"Add one or more simple products to the specified cart. We recommend using `addProductsToCart` instead.")

From there we get the AddSimpleProductsToCartOutput output which is returning our cart data. Let's check it's definition (in same graphql schema):

type AddSimpleProductsToCartOutput @doc(description: "Contains details about the cart after adding simple or group products.") {
    cart: Cart! @doc(description: "The cart after adding products.")
}

It is returning the Cart type which containing cart items:

type Cart @doc(description: "Contains the contents and other details about a guest or customer cart.") {
    id: ID! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\MaskedCartId") @doc(description: "The unique ID for a `Cart` object.")
    items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems") @doc(description: "An array of products that have been added to the cart.")
    ...
}

and finally each cart item contains a ProductInterface (it is a product and totally not a quote item):

interface CartItemInterface @typeResolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemTypeResolver") @doc(description: "An interface for products in a cart.") {
    id: String! @deprecated(reason: "Use `uid` instead.")
    ...
    product: ProductInterface! @doc(description: "Details about an item in the cart.")
}

which was defined in the catalog graphql module (vendor/magento/module-catalog-graph-ql/etc/schema.graphqls) and has target field sku:

interface ProductInterface @typeResolver(class: "Magento\\CatalogGraphQl\\Model\\ProductInterfaceTypeResolverComposite") @doc(description: "Contains fields that are common to all types of products.") {
    id: Int @deprecated(reason: "Use the `uid` field instead.") @doc(description: "The ID number assigned to the product.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\EntityIdToId")
    ...
    sku: String @doc(description: "A number or code assigned to a product to identify the product, options, price, and manufacturer.")
    ...
}

Theoretically this is the answer - SKU from response is the pure product entity SKU, which has no modifications from selected custom options or from any additional modifications. To confirm that we must go deeper and inspect how the resolver class works.

Problem view from `PHP` side:

We must check from where the product data comes to the response. As per definition of the Cart type (which is a main part of our output model) we get the CartItems array for which the vendor/magento/module-quote-graph-ql/Model/Resolver/CartItems.php resolver was used. What inside? The resolve method unpack all quote items from the quote and one by one get corresponding product and unpack it's data to the result object ($itemsData):

$cartProductsData = $this->getCartProductsData($cart);
$cartItems = $cart->getAllVisibleItems();
/** @var QuoteItem $cartItem */
foreach ($cartItems as $cartItem) {
    $productId = $cartItem->getProduct()->getId();
    if (!isset($cartProductsData[$productId])) {
        $itemsData[] = new GraphQlNoSuchEntityException(
            __("The product that was requested doesn't exist. Verify the product and try again.")
        );
        continue;
    }
    $productData = $cartProductsData[$productId];

    $itemsData[] = [
        'id' => $cartItem->getItemId(),
        'uid' => $this->uidEncoder->encode((string) $cartItem->getItemId()),
        'quantity' => $cartItem->getQty(),
        'product' => $productData,
        'model' => $cartItem,
    ];
}

The SKU field was inside the $productData array which comes from the getCartProductsData method:

/**
 * Get product data for cart items
 *
 * @param Quote $cart
 * @return array
 */
private function getCartProductsData(Quote $cart): array
{
    $products = $this->getCartProducts->execute($cart);
    $productsData = [];
    foreach ($products as $product) {
        $productsData[$product->getId()] = $product->getData();
        $productsData[$product->getId()]['model'] = $product;
        $productsData[$product->getId()]['uid'] = $this->uidEncoder->encode((string) $product->getId());
    }

    return $productsData;
}

where we obtain product for the cart from the Magento\QuoteGraphQl\Model\Cart\GetCartProducts model. All this class is doing is getting all product ids from the $cart (argument) and use it as a filter for the product repository:

/**
 * Get product models based on items in cart
 *
 * @param Quote $cart
 * @return ProductInterface[]
 */
public function execute(Quote $cart): array
{
    $cartItems = $cart->getAllVisibleItems();
    if (empty($cartItems)) {
        return [];
    }
    $cartItemIds = \array_map(
        function ($item) {
            return $item->getProduct()->getId();
        },
        $cartItems
    );

    $searchCriteria = $this->searchCriteriaBuilder->addFilter('entity_id', $cartItemIds, 'in')->create();
    $products = $this->productRepository->getList($searchCriteria)->getItems();

    return $products;
}

As a result we see that a product data which contains a SKU just a raw product data from the catalog product repository. It cant know the actual information about the selected custom options and quote item SKU.

To resolve such a problem I'll recommend you to extend the CartItemInterface and add a custom field cart_item_sku with own resolver class which should simply take the cart item (quote item) and return it's SKU as a string.

How to resolve?

You could create a custom module for extending the regular CartItemInterface purpose and add the cart_item_sku to it.

Example module on GitHub

Create new module with general files like registration.php, etc/module.xml, composer.json. In my example on GitHub module has the name MageWorx_ExtendedCartGraphQl.

Create schema where we can extend CartItemInterface adding new field with custom resolver:

etc/schema.graphqls

# Add cart item sku with suffixes to the regular cat item.
interface CartItemInterface {
    cart_item_sku: String! @resolver(class: "MageWorx\\ExtendedCartGraphQl\\Model\\Resolver\\CartItemSku") @doc(description: "SKU of cart item with suffixes from selected custom options or other modifications")
}

Create the resolver class for our new field as defined in the schema:

Model/Resolver/CartItemSku.php

<?php
/**
 * Copyright © MageWorx. All rights reserved.
 * See LICENSE.txt for license details.
 */
declare(strict_types=1);

namespace MageWorx\ExtendedCartGraphQl\Model\Resolver;

use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Quote\Model\Quote\Item as QuoteItem;

class CartItemSku implements \Magento\Framework\GraphQl\Query\ResolverInterface
{
    /**
     * 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)
    {
        if (!isset($value['model'])) {
            throw new LocalizedException(__('"model" value should be specified'));
        }
        /** @var QuoteItem $cartItem */
        $cartItem = $value['model'];

        // Just take sku from quote item :)
        return $cartItem->getSku();
    }
}

Deploy to your server, run setup:upgrade and setup:di:compile. Thats all. Now we can test our new field using GraphQL:

Request:

{
  customerCart {
    id
    items {
        id
        cart_item_sku
        product {
            name
            sku
        }
        quantity
    }
  }
}

Response:

{
    "data": {
        "customerCart": {
            "id": "LI0zUyH5YVKT25fbyl5wRB3UEqRz9JnZ",
            "items": [
                {
                    "id": "46",
                    "cart_item_sku": "G-text",
                    "product": {
                        "name": "G",
                        "sku": "G"
                    },
                    "quantity": 1
                },
                {
                    "id": "47",
                    "cart_item_sku": "A",
                    "product": {
                        "name": "A",
                        "sku": "A"
                    },
                    "quantity": 1
                }
            ]
        }
    }
}

Screenshot from postman:

Screenshot from postman

As you can see the product with selected custom option now has the correct cart_item_sku : "G-text"

Happy coding :)

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.