最近发现一个Ecstore大bug关于订单促销这一块的,能直接影响到最后的结算金额,使结算价格远远低于应付金额。在和官方技术人员沟通中,发现最新版的ecstore仍然没有修复此bug,因此有必要将此bug公布一下。
活动名称:第2件商品半价,多款商品可同时享受。
场景重现:
1. 建立1条订单策略,优先权50,不排他。指定包含购买商品A数量x2的时候,订单-100元。
2. 建立另1条订单策略,优先权50,不排他。指定包含购买商品B数量x2的时候,订单-60元。
3. 建立商品A,单价为200元,商品B单价为120,确保商品单价要大于减免价格。
结果发现买商品A两件时,结算价格变成了200×2-100-60了,执行了商品A的100元的优惠基础上同时又执行了商品B的的促销优惠。同一时间上,生效的第2件半价的订单促销策略不止这两条,查看其他产品结算价格都正常。起初怀疑是后台设置问题,但是在仔细比对其他产品的第2件商品半价促销设置后,并没有发现有什么设置上的问题,于是开始怀疑本身的代码有bug。
订单促销走的是/b2c/lib/cart/postfilter/promotion.php到/b2c/lib/sales/,代码封装得很深,最后定位到产生的问题的文件/b2c/lib/sales/basic/operator/contain.php这个文件的validate函数。
原代码为:
public function validate($operator,$value,$validate) { if( !$value || !$validate ) return false; //商品包含某个字 switch($operator) { case '()': if(is_array($value)) return in_array($validate,$value); $flag = strpos($validate,$value); if( $flag===false ) return false; else return true; break; case '!()': if(is_array($value)) return !in_array($validate,$value); $flag = strpos($validate,$value); if( $flag===false ) return false; else return true; break; } return false; }
这里函数参数operator是包含的符号'()’,value是策略里的goods_id(多个的情况下是数组),validate是购物车里的goods_id(只可能是单个)。可以看到如果策略里包含多个商品,则会用in_array判断购物车商品是否在策略生效的商品清单中,这个没问题。问题点在于下面这条strpos,如果购物车内的goods_id是123,策略里生效的goods_id是1234的话,那么strpos(‘123′,’1234’)是成立的。另外查了一下后台所有策略的可选项,发现在设置商品名称匹配的时候也用到了包含,所以,这里针对性的略做修改,如下:
case '()': if(is_array($value)) return in_array($validate,$value); //by tiandi 2016.5.9 尝试修复goods_id=123的商品会载入goods_id=1234订单促销的错误,不清楚这里原先为什么要用strpos,可能并不局限用于促销策略模块, //所以可能会造成其他问题。如果value字符串里都是数字(goods_id),则不使用strpos,而直接判断是否和validate相同 //start if(is_numeric($value)&&($validate != $value)) return false; //end $flag = strpos($validate,$value); if( $flag===false ) return false; else return true; break;
这样在比对goods_id的时候就不会再出现读取错误的情况了。Ecstore的框架写得还真是有够复杂,查明上述问题,几乎涉及到10个左右的文件。
PS:已经将该问题提交给官方技术了。