## 摘要订单二开里最容易让人抓狂的问题不是“价格算不出来”而是“每一页都算出了不同的价格”。商品页看着是会员价购物车里又变了确认订单页再变一次用户最后提交时还可能因为优惠券、积分、运费重算而出现第三个金额。表面上是价格不一致底层其实是结算链路里缺少统一入口。CRMEB Pro 的订单价格不是一个字段拍脑袋算完而是从购物车快照、商品原价、会员价、等级价、活动价、优惠券、积分、运费、首单优惠一路叠出来的。真正能落单的金额必须在确认页和创建订单时再次校验不能直接信前端展示值也不能直接信旧缓存。本文基于 CRMEB Pro 真实实现拆开订单从确认到创建的完整链路重点讲清楚商品页为什么只负责展示确认页为什么要重算创建订单为什么还要再算一次以及哪些字段必须落库做快照。本文涉及的真实目录textapp/controller/api/v1/order/StoreOrder.phpapp/services/order/StoreOrderServices.phpapp/services/order/StoreOrderComputedServices.phpapp/services/order/StoreOrderCreateServices.phpapp/services/order/StoreCartServices.phpapp/services/order/StoreOrderCartInfoServices.phpapp/dao/order/StoreOrderDao.phpapp/model/order/StoreOrder.phpapp/model/order/StoreOrderCartInfo.phpapp/services/user/level/SystemUserLevelServices.phpapp/services/user/member/MemberCardServices.phpapp/services/activity/coupon/StoreCouponUserServices.phpapp/services/user/UserServices.php## 一、先看结论订单价格不是一个数而是一串快照CRMEB Pro 订单价格链路里常见的价格字段至少有这些textsumPrice商品原始总价。totalPrice会员价、等级价等处理后的商品总价。couponPrice优惠券抵扣金额。firstOrderPrice首单优惠金额。payPrice用户最终需要支付的金额。payPostage最终要付的运费。deductionPrice积分抵扣金额。changePrice改价优惠金额。真正入库时订单表会保存这些快照字段texttotal_pricecoupon_pricefirst_order_pricepromotions_pricepay_pricepay_postagededuction_pricechange_priceuse_integralgain_integrallevel_extra_integral_level_idlevel_extra_integral_ratio这意味着订单不是“下单时实时算一个总价”就完事了而是要把当时那次结算结果固化下来。否则后面商品改价、会员变更、活动结束订单金额就会跟着飘。## 二、第一道门商品页只负责看不负责最终结算商品详情页和确认页看到的价格很多时候都来自同一类商品价格计算但它们不等于最终订单金额。项目里商品价格会受这些因素影响text普通售价 price付费会员价 vip_price用户等级折扣 discountSKU 独立等级价 level_price活动商品价渠道价首单优惠这里最关键的不是“谁便宜”而是“谁有资格参与计算”。比如 StoreProductServices 里会根据用户等级、付费会员、商品是否启用会员价决定当前展示哪个价格类型。你在前端看到的可能只是text这个商品是会员价那个商品是等级价这个 SKU 命中活动价但真正下单时CRMEB Pro 还要继续往下看text购物车里的 truePrice 是什么cart_num 乘起来是多少是否能叠加优惠券是否能用积分是否有首单优惠运费怎么重算所以二开时不要把会员价逻辑写到前端组件里就结束了前端只能展示不能替代结算。## 三、确认页为什么要重算旧缓存不可信订单确认入口在phpapp/controller/api/v1/order/StoreOrder.php确认数据方法是phppublic function getOrderConfirmData(array $user, $cartId, bool $new, int $addressId, int $shipping_type 1, int $store_id 0, int $coupon_id 0, int $isSendGift 0)它做的第一件事不是直接返回购物车而是重新拿地址、重新拿购物车、重新算价格组php$cartGroup $cartServices-getUserProductCartListV1($uid, $cartId, $new, $addr, $shipping_type, $coupon_id, false, $isSendGift);$priceGroup $computedServices-getOrderPriceGroup($uid, $validCartInfo, $addr, $storeFreePostage, $isSendGift);这里有个很重要的设计text确认页不是展示缓存而是重新计算订单价格组。因为用户可能在下单前做了这些动作text换了收货地址切换了支付方式改了购物车数量优惠券已过门槛变化会员等级变化运费模板变化如果你只信缓存就会出现商品页和确认页金额不一致甚至提交时再炸一次。## 四、computedOrder创建订单前还要再算一遍订单最终创建时StoreOrderCreateServices::createOrder() 还会再调用一次php$priceData $computedServices-computedOrder($uid, $userInfo, $cartGroup, $addressId, $payType, $useIntegral, $couponId, $shippingType, $isSendGift);这一步的意义很直接text确认页算的是给用户看的创建订单算的是要真正落库的这次计算里会重新处理text优惠券是否满足条件首单优惠是否可用积分是否可抵扣运费是否变化送礼附加费是否要加例如优惠券不是拿了就算validateCoupon() 会按商品分类、品牌、商品维度和活动叠加规则再判断一次积分也不是看到能用就全用而是按系统配置的上限、比例、用户可用积分重新压一遍。这就是为什么订单创建时还要再算一次phpcoupon_price $priceData[coupon_price],pay_price $priceData[pay_price],pay_postage $priceData[pay_postage],deduction_price $priceData[deduction_price],change_price $priceData[change_price] ?? 0.00,## 五、价格快照真正落库的位置订单主表创建时会把整单关键信息一次性写进去php$orderInfo [uid $uid,order_id $this-getNewOrderId(),total_price $priceGroup[sumPrice] ?? $priceGroup[totalPrice],total_postage $priceData[total_postage] ?? $priceGroup[storePostage],coupon_price $priceData[coupon_price],first_order_price $priceData[first_order_price],promotions_price $priceData[promotions_price],pay_price $priceData[pay_price],pay_postage $priceData[pay_postage],deduction_price $priceData[deduction_price],use_integral $priceData[usedIntegral],gain_integral $gainIntegral,level_extra_integral_level_id $levelIntegralMultipleLevelId,level_extra_integral_ratio $levelIntegralMultiple,];随后订单商品快照也会保存php$cartServices-setCartInfo($order[id], $cartInfo, (int)$uid, $promotions_give[promotions] ?? []);商品快照里会记录textcart_numtotal_pricepay_pricepay_postagecoupon_pricepromotions_pricefirst_order_pricesku_uniquepromotions_idis_giftis_support_refund这一步很关键因为后面改商品信息、改活动、改运费订单详情页依然要按“当时那一单”的信息展示。## 六、订单价格为什么会乱通常是少了一个统一入口如果你在二开时碰到订单金额不一致优先排这几个点text1. 商品展示价和实际结算价是不是用了两套逻辑。2. 购物车重算时有没有把地址、运费模板、会员价一起算进去。3. 确认页有没有重新校验优惠券和积分。4. 创建订单时有没有再次调用计算服务。5. 落库的订单明细有没有保存价格快照。最容易踩坑的地方是把“展示价格”“确认价格”“提交价格”拆成三套逻辑写散了。CRMEB Pro 现在的思路其实很朴素text展示可以前置结算必须统一落库必须快照## 七、二开建议如果你要扩展订单价格建议优先加在这些位置textStoreCartServices购物车商品整合和活动上下文补齐。StoreOrderComputedServices统一计算商品总价、运费、优惠券、积分、首单优惠。StoreOrderCreateServices落库订单主表和订单商品快照。StoreOrderCartInfoServices保存和读取订单商品明细。不要把规则散到 Controller、前端组件和临时 Job 里。订单是高风险链路散开之后最先坏的一定是金额一致性。## 注意事项1. 订单、支付、积分、优惠券、运费都属于高风险业务改动前要先确认影响范围。2. computedOrder() 和 getOrderConfirmData() 不是重复代码而是两个不同阶段的重算入口。3. 不要直接依赖前端传来的最终金额后端必须重新校验。4. 订单快照一旦入库后续商品改价不应反向污染历史订单。5. 价格相关字段尽量用项目现有的 bc* 计算方式避免精度问题。## 标签建议#CRMEBPro #订单二开 #源码解析 #价格快照 #购物车 #优惠券 #积分抵扣 #运费计算 #会员价 #二开实战