收藏夹是“保存购买意图”,不是把商品完整复制一份。用户几个月前收藏的商品,后来可能换了可配置选项、删除了自定义选项、移动到别的网站,或者简单商品已经下架。于是商品详情页还能打开,点击“加入购物车”却只得到一句笼统的错误。

先看失败的是一件,还是整个收藏夹

单个商品失败,优先比较这条 wishlist item 保存的购买参数;所有商品都失败,才去查 Quote、Session、表单校验或自定义插件。可以从后台复现同一客户,也可以在脱敏副本中读取 wishlist item 的 buy_request,但不要随意修改生产数据。

旧购买参数与现在的商品定义对不上

可配置商品依赖 super_attribute;Bundle、Grouped 和带必选 Custom Option 的商品还有各自参数。最常见的真实变化是:

  • 管理员删除了原来的颜色或尺寸选项;
  • 子商品仍存在,但已禁用、不可见或不属于当前 Website;
  • 某个原本非必填的 Custom Option 被改成必填;
  • 商品类型或选项 ID 在导入后重建,旧 ID 失去意义。

这时不应该偷偷替客户选择另一个尺寸。更合理的体验是把用户带回商品页,保留仍有效的选择,并明确提示需要重新选择已经失效的选项。

商品“有货”不等于当前请求可加入购物车

在 MSI 环境中,要以当前 Website 对应 Stock 的 salable quantity 为准。检查父商品状态没有意义,还要确认具体子 SKU 在目标 Stock 是否可售。与此同时,最小购买数量、增量数量、客户组限制和缺货预售配置也会影响 Quote 校验。

php bin/magento inventory:reservation:list-inconsistencies
php bin/magento indexer:status inventory

SELECT product_id, website_id
FROM catalog_product_website
WHERE product_id IN (:parent_id, :child_id);

上面的 SQL 只用于确认网站归属。库存问题应通过 Inventory API 或 CLI 判断,不要自己把 source_item 的 quantity 当成可售数量。

一个实用的判断顺序

  1. 用目标 Store View 打开商品页,看它是否真的允许购买;
  2. 确认失败的是父商品选择还是具体子商品;
  3. 对比 wishlist 的 buy request 与商品当前必选参数;
  4. 再检查 Website、客户组、最小数量和 MSI 可售性;
  5. 最后才审查 addProduct 插件和 Quote 校验扩展。

如果异常信息被前端统一改成“无法加入购物车”,临时记录异常类和商品 ID,不要只记录翻译后的消息。LocalizedException、无效选项和库存异常的处理方向完全不同。

修复后的体验比“按钮不报错”更重要

测试一条当天新收藏的商品、一条选项已删除的旧收藏、一条子商品缺货的收藏,以及跨 Website 登录的收藏。有效商品应直接进入购物车;失效选项应要求重选;不可售商品要给出准确原因。不要为了让批量“全部加入购物车”返回成功而吞掉失败项,否则客户会以为所有商品都已加入,结账时才发现数量不对。