Vue-storefront: Product configurable options - can鈥檛 add the same SKU with different configurations to the cart

Created on 29 May 2019  路  10Comments  路  Source: DivanteLtd/vue-storefront

Current behavior


When adding a product to cart 2 or more times, each time with different custom options which normally have different prices, the calculated total price is wrong.

Expected behavior


孝he total price should be the sum of the prices for each product/options pair added to cart. Instead the price ends up being the quantity multiplied by the price of the last added to cart product (with its options), dismissing the prices of previously added products/options.

Steps to reproduce the issue

  1. Go to: https://demo.vuestorefront.io/gear/gear-3/sprite-yoga-companion-kit-45.html
  2. Add the product to cart with default options - price is $75.03.
  3. Add the product to cart again but choose "Sprite Stasis Ball 65 cm" option - with the custom option price becomes $79.95.
    Check microcart - it shows 2 pieces of the same product with total price $159.90, while expected price is $75.03 + $79.95 = $154.98.

Repository

Can you handle fixing this bug by yourself?

  • [ ] YES
  • [X] NO

Which Release Cycle state this refers to? Info for developer.

Pick one option.

  • [ ] This is a bug report for test version on https://test.storefrontcloud.io - In this case Developer should create branch from develop branch and create Pull Request 2. Feature / Improvement back to develop.
  • [ ] This is a bug report for current Release Candidate version on https://next.storefrontcloud.io - In this case Developer should create branch from release branch and create Pull Request 3. Stabilisation fix back to release.
  • [X] This is a bug report for current Stable version on https://demo.storefrontcloud.io and should be placed in next stable version hotfix - In this case Developer should create branch from hotfix or master branch and create Pull Request 4. Hotfix back to hotfix.

Environment details

  • Browser: Chrome Version 74.0.3729.169 (Official Build) (64-bit)
  • OS: macOS Mojave 10.14.5
  • Node:
  • Code Version:

Additional information

Medium complexity Complex QA approved after merge bug vs-hackathon

Most helpful comment

This is related to #1620 and the way we鈥檙e identifying clients side configurable/bundle products with data we get from the server during the server sync (cart/serverAfterPuled action)

All 10 comments

This is related to #1620 and the way we鈥檙e identifying clients side configurable/bundle products with data we get from the server during the server sync (cart/serverAfterPuled action)

This needs to be handled in multiple places. Most mutations in cart/store/mutations.ts and some methods in cart/store/actions.ts identify products by only verifying its SKU. I suggest product_option.extension_attributes.custom_options be taken in account as well. This should allow for multiple cart items having the same SKU.

@pkarw, what is a proper way to identify product taking in to account the options? I'm using this for time being, it works, but I'm not confident its the best approach:

state.cartItems.find(p => p.sku === product.sku && JSON.stringify(p.product_option) === JSON.stringify(product.product_option))

Hi! Thanks for Your input @bobcho. It's probably one of the ways to solve this issue. I like it because it's pretty general and will also work for the situation of different custom_options with the same SKU in cart. However, going this way the final implementation probably should use the kind of specific product.chcksm property which contains a hash of the product_options - to avoid JSON.stringify on compare.

Let's consider which other ways we have to solve this issue?

  1. Go on with the checksums. We need to update the places where we do just update the items in the cart as well (probably the only place is updateQuantity)
  1. We can try to use product.server_item_id to identify items by the merge. In this case, we must assign server_item_id immediately after the product is added to the cart for the first time - in here:
    https://github.com/DivanteLtd/vue-storefront/blob/7fa05a2833e06d52c88165083c1e3e8923e81249/core/modules/cart/store/actions.ts#L572

It shouldn't be hard as the current server API responds with the proper information: https://www.dropbox.com/s/5hxkx3x9fiof3kv/Screenshot%202019-06-01%2006.58.01.png?dl=0

Probably we should update it immediately anyway as it's not related solely to this issue.

Then, we can use this product.server_item_id to identify the products during sync. If the product has not yet set server_item_id it means it's a new one - not yet added to the server cart.

While adding to the cart we could keep the per sku identification - however, I'm not sure about the custom_options case - probably the sku is the same so kind of checksum on product_option will be required anyway just for this check.

  1. Alter the client's side SKU - magento builds kind of parentSku-childSku1-childSku2 virtual identifiers so we can do the same on the client's side. Still - it doesn't solve the product custom options issue - and it's vulnerable to desync in case smbd. change the virtual sku naming algorithm server side.

Thanks for the feedback!

Good point about the checksum - do you think it should represent only product_option or better to have a more "global" checksum representing the product itself (also taking SKU in account)? Something like:

product.chcksm = sha3_224(JSON.stringify({sku: product.sku, product_option: product.product_option ? product.product_option : null}));

I may be wrong as I've not checked the Magento API code, but judging by the demo API responses, I think 1 and 2 would require server API mod, because currently it seems the server only checks the SKU when adding / modifying items in the cart (api/cart/update). The API call that vue-storefront makes includes the options, but it seems they are not taken in account by the server. This results in the server and client having misaligned carts.

And then for 3 I agree with you - it doesn't solve the custom options issue and is vulnerable to desync. But it may work for bundles without any server mods.

Hey, do you know when this can be fixed? I try to find a solution by myself but at the moment I'm not that into the system to solve it in a good way. We want to launch our new shop but aren't not able to do without this functionality.
By the way, great System, I will continue learning vue storefront :)

@PatrickKluwig could you give me your email or write to me at [email protected]? We can speed up development of this functionality.

@patrickfriday Hey, thank you for your answere, the email address [email protected] is not available.

@PatrickKluwig that cannot be as I just got notification to that email :( What is yours, then I will write you an email :-)

I have a solution for personalized products. Overite the core module productsEquals.ts. After this you will be able to add same SKU multiple time to basket and sync with m2.
if(String(product1.sku ) === String(product2.sku)){
if(product1.product_option.extension_attributes.custom_options && product2.product_option){
let p1 = product1.product_option.extension_attributes.custom_options
let p2 = product2.product_option.extension_attributes.custom_options
let p1Array = []
let p2Array = []
for( let key in p1){
typeof p1[key].option_value === "undefined"? p1[key].option_value = null :p1[key].option_value=p1[key].option_value
p1Array.push({
"option_id": p1[key].option_id,
"option_label": p1[key].option_value
})
}
for( let key in p2){
p2Array.push({
"option_id": p2[key].option_id,
"option_label": p2[key].option_value
})
}
let values = (o) => Object.keys(o).sort().map(k => o[k]).join('|')
let mapped1 = p1Array.map(o => values(o))
let mapped2 = p2Array.map(o => values(o))
var res = mapped1.every(v => mapped2.includes(v))
return res
}
}
return String(product1.sku ) === String(product2.sku)

Was this page helpful?
0 / 5 - 0 ratings