优惠活动

Odoo 电商优惠码为什么不是“输个 code 就减钱”:pending coupon、自动领券与奖励重算主链路讲透

Odoo 网站优惠活动不是把一个 coupon code 塞进订单那么简单。website_sale_loyalty 还会处理待应用优惠码、自动领取奖励、运费重算、过期校验,以及购物车变化后的整套奖励刷新。

网站
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

先说结论

很多人理解网站优惠活动时,会把它简化成一句话:

  • 用户输入优惠码;
  • 系统打个折;
  • 完成。

但在 Odoo 的 website_sale_loyalty 里,真正的主链路要复杂得多。

它至少同时处理这几件事:

  1. 优惠码是不是当前网站可用;
  2. 现在有没有购物车,没购物车时先把 code 暂存起来;
  3. 这个 code 是直接产生唯一奖励,还是要用户自己选 reward;
  4. 奖励是不是能自动领取;
  5. 奖励应用后,配送费要不要重算;
  6. 用户删行、改数量、换配送方式后,奖励是否仍然成立。

所以 Odoo 电商的优惠逻辑,本质上不是“优惠码输入框”,而是:

围绕购物车生命周期不断重算 programs 与 rewards 的一套引擎。


一、为什么 /coupon/<code> 这种链接很重要

website_sale_loyalty/controllers/main.py 里,Odoo 提供了:

  • /coupon/<string:code>

这类链接不是简单跳转,而是把 code 写进 request.session['pending_coupon_code']

这个设计很聪明。

因为现实里的营销入口,很多时候不是用户自己手输,而是:

  • EDM 邮件里的跳转链接;
  • 活动页面按钮;
  • 社媒海报上的专属 URL;
  • 登录前就能访问的推广页。

此时系统还不一定有购物车。

如果要求“必须先建 cart 才能识别优惠码”,体验就会很糟。

所以 Odoo 的做法是:

  • 先记住这个 code,等 cart 出现再尝试应用。

这就是 pending_coupon_code 的意义。


二、为什么 code 不一定立刻生效

模型里 _try_pending_coupon() 会在后续时机读取 session 里的待应用 code,再调用 _try_apply_code()

这里最关键的不是“能不能输进去”,而是:

  • 当前购物车是否满足活动条件;
  • 当前用户身份是否允许该 program;
  • 当前网站是否匹配该 loyalty program;
  • 奖励是否已过期、是否还可领取。

也就是说,优惠码并不是被“保存进订单”就算成功,而是每次都要过资格判断。

这跟很多轻量电商不同。

Odoo 没把优惠码视为一个静态标签,而是把它放在了动态规则引擎里。


三、为什么有些奖励会自动领,有些必须用户手动选

_auto_apply_rewards() 里能看出 Odoo 非常克制。

它只会在比较明确、安全的场景自动应用奖励,比如:

  • 这个 program 只有一个 reward;
  • 不是 nominative program;
  • 不是多商品 reward;
  • 该 reward 没被用户主动禁用;
  • 当前订单里还没应用过。

换句话说,Odoo 的自动领券策略不是“能自动就自动”,而是:

只有在系统几乎不会误判用户意图时,才自动代劳。

这也是为什么有些活动你会看到自动折扣,有些却需要用户点“领取奖励”。

不是体验不统一,而是不同奖励的歧义程度不一样。


四、为什么 claim reward 不等于简单新增一条折扣行

在 controller 里,claim_reward()_apply_reward() 最终会触发订单侧 _apply_program_reward(),随后再调用 _update_programs_and_rewards()

这说明应用奖励不是一次性落库动作,而是一次重新结算购物车的起点

原因很简单:

  • 一个 reward 进来,可能影响其他 reward 的可用性;
  • 全局折扣可能只能保留最优一个;
  • 金额归零后,某些折扣反而不该继续出现;
  • 某些 program 需要基于最新订单金额再判断;
  • 配送优惠还会改变运费行。

所以奖励应用后的关键动作不是“加一条 line”,而是“重新验证整套促销状态”。


五、为什么配送费会在优惠应用后再次重算

_apply_reward() 里有个很容易被忽略的逻辑:

  • 如果当前 carrier 有 free_over 规则,且这次 reward 不是 payment program,Odoo 会重新调用 rate_shipment(),成功就重设 delivery line,失败则移除配送行。

这一步非常像真实电商。

因为很多优惠活动会让订单金额跨过某个门槛:

  • 原本满额包邮,打折后不满;
  • 原本不包邮,凑单或奖励后又满足;
  • 不同奖励对运费基数的影响也不一样。

如果系统应用优惠后不重算运费,用户看到的总价就可能是错的。

所以 Odoo 在这里坚持“促销和配送必须联动”。


六、为什么删购物车行、改数量、换配送方式,奖励也会跟着变

sale_order.py 里,website_sale_loyalty 接管了多个关键节点:

  • _update_programs_and_rewards()
  • _verify_cart_after_update()
  • _set_delivery_method()
  • _remove_delivery_line()
  • _recompute_cart()

也就是说,购物车只要发生商业意义上的变化:

  • 商品数量变化;
  • 行被删掉;
  • 配送方式变化;
  • 运费行变化;

Odoo 都会把 program 和 reward 重新刷新一遍。

这点是网站促销是否靠谱的分水岭。

如果只是“输入 code 时算一次”,后面购物车再变化,优惠状态就很容易失真。

Odoo 选择的是更稳妥但更复杂的路:

  • 把优惠引擎嵌进购物车生命周期。

七、为什么前台看到的是“合并后的折扣行”

源码里 _compute_website_order_line() 会把同一 reward 产生的多条 discount line 在网站上“视觉合并”。

为什么要这样?

因为后台为了税务和金额精确,可能需要按不同税率拆成多条奖励行。

但前台购物车如果把这些技术细节全展示出来,用户会非常困惑。

于是 Odoo 采取了一个典型的“后台精确、前台简化”策略:

  • 后端保留真实计算结构;
  • 网站展示层把同一 reward 汇总成一条更好理解的折扣项。

这也是为什么网站购物车里的促销展示,经常比后台订单行看起来更“干净”。


八、做促销定制时最容易踩的坑

1)只在输入 code 的地方改逻辑

这样一旦用户后续改购物车、改运费、删商品,你的自定义折扣就会和 Odoo 主链路脱节。

2)把 reward line 当普通商品行处理

这会破坏 _cart_find_product_line()、数量统计和前台展示。

3)忽略网站维度

源码明确把 website 维度带进了 program domain。多网站场景下,一个活动不一定应该跨站生效。

4)忽略 session 里的 pending coupon

你会发现营销链接落地页“怎么没有自动带券”,其实不是 Odoo 不支持,而是定制时把这条链路绕丢了。


最后一句

Odoo 电商优惠活动真正厉害的地方,不在于它能“输入优惠码”,而在于它把:

待应用 code、自动奖励、购物车重算、配送联动、网站范围和过期校验

放进了一套持续更新的购物车规则引擎里。

所以理解 website_sale_loyalty,关键不是盯着优惠码输入框,而是看懂“购物车一变,奖励就得跟着重新判定”这条主线。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。