装饰ProductPriceCalculator后购物车价格重复相乘问题
问题:装饰ProductPriceCalculator后购物车价格多次相乘
按照Shopware官方文档装饰ProductPriceCalculator后,商品详情页的自定义价格计算功能正常,但将商品加入购物车后,价格会被多次乘以自定义系数,导致价格异常。
用户实现的代码如下:
public function calculate(iterable $products, SalesChannelContext $context): void { /** @var SalesChannelProductEntity $product */ foreach ($products as $product) { if(array_key_exists('custom_calculation', $product->getCustomFields()) && $product->getCustomFields()['custom_calculation'] != '') { $factor = (float)$product->getCustomFields()['custom_calculation']; $price = $product->getPrice(); $price->first()->setGross($price->first()->getGross() * $factor); $price->first()->setNet($price->first()->getNet() * $factor); } } $this->getDecorated()->calculate($products, $context); }
问题原因
Shopware在购物车操作(添加商品、刷新购物车、结算等场景)中会多次调用ProductPriceCalculator::calculate方法。当前代码直接修改了产品实体的价格对象,而同一个产品实体实例可能被重复传入该方法,导致每次调用都会基于已经修改过的价格再次乘以系数,最终出现价格多次相乘的问题。
解决方案
需要确保自定义价格计算仅基于产品的原始价格执行一次,避免重复计算。以下提供两种可行方案:
方案1:基于原始价格计算(推荐)
从产品的原始数据库数据中获取未修改的价格,以此为基础计算新价格,避免基于已修改的价格重复相乘:
public function calculate(iterable $products, SalesChannelContext $context): void { /** @var SalesChannelProductEntity $product */ foreach ($products as $product) { if(array_key_exists('custom_calculation', $product->getCustomFields()) && $product->getCustomFields()['custom_calculation'] != '') { $factor = (float)$product->getCustomFields()['custom_calculation']; // 获取产品的原始数据库价格(未经过销售渠道和自定义修改的价格) $originalProduct = $product->getProduct(); if ($originalProduct && $originalProduct->getPrice()) { $originalPrice = $originalProduct->getPrice()->first(); $newGross = $originalPrice->getGross() * $factor; $newNet = $originalPrice->getNet() * $factor; // 更新销售渠道产品的价格 $salesChannelPrice = $product->getPrice()->first(); $salesChannelPrice->setGross($newGross); $salesChannelPrice->setNet($newNet); } } } $this->getDecorated()->calculate($products, $context); }
方案2:添加临时标记避免重复计算
给产品实体添加临时扩展标记,标记自定义价格是否已应用,避免重复执行计算逻辑:
use Shopware\Core\Framework\Struct\ArrayStruct; public function calculate(iterable $products, SalesChannelContext $context): void { /** @var SalesChannelProductEntity $product */ foreach ($products as $product) { // 检查是否已应用过自定义价格计算,跳过重复执行 if ($product->hasExtension('custom_price_applied')) { continue; } if(array_key_exists('custom_calculation', $product->getCustomFields()) && $product->getCustomFields()['custom_calculation'] != '') { $factor = (float)$product->getCustomFields()['custom_calculation']; $price = $product->getPrice()->first(); $price->setGross($price->getGross() * $factor); $price->setNet($price->getNet() * $factor); // 添加临时扩展标记,标记已完成自定义价格计算 $product->addExtension('custom_price_applied', new ArrayStruct(['applied' => true])); } } $this->getDecorated()->calculate($products, $context); }
说明
- 方案1通过关联的原始产品实体获取未修改的价格,计算逻辑更可靠,适合大多数场景。
- 方案2的临时标记仅在当前实体实例生命周期内有效,若产品实体被重新加载(如购物车刷新时重新从数据库获取),标记会丢失,可能需要结合其他逻辑补充。
内容的提问来源于stack exchange,提问作者DannyDan
相关产品推荐
相关产品推荐

