网站卖订阅产品时,很多人只盯着“月付价格能不能显示出来”。但真正常把项目做坏的,往往是购物车规则:客户选了年付,再加一个月付;或者订阅商品和一次性商品乱混,最后后台根本解释不清订单语义。
这篇文章主要参考了以下企业版源码入口:
enterprise/website_sale_subscription/models/sale_order.pyenterprise/website_sale_subscription/controllers/variant.pyenterprise/website_sale_subscription/views/templates.xml
一、这篇功能真正解决什么问题
website_sale_subscription 的核心不是“电商也能卖 recurring product”,而是让网站购物车在前台阶段就守住订阅语义。一旦这里失守,后面的订阅合同、开票节奏、税务地址和支付页判断都会被拖乱。
二、核心链路怎么走
1. 加车时先确定订单到底挂哪个 plan
sale.order._cart_add(..., plan_id=None) 会先看当前商品是不是 recurring product。如果是,就尝试从产品模板的 recurring pricing 里找到适配的 plan_id,并把它写到订单上。这里的关键点是:计划不是前台展示字段,而是订单语义核心。
2. 不同订阅周期不能在同一订单里混放
当购物车已经有 plan_id 时,再加另一个周期的订阅商品,源码会直接抛 UserError。这不是官方“太保守”,而是因为一张订阅订单必须有一个明确 recurring cadence;混不同周期,后续 next_invoice_date 根本没有统一基准。
3. 支付页也要知道自己在处理订阅单
controllers/variant.py 里扩展了组合信息和支付值,_get_shop_payment_values() 会明确传出 is_subscription。这样支付模板和后续流程才知道当前订单不是普通一次性销售,而是带订阅含义的电商单。
三、新手最容易踩的坑
- 以为前台 plan 只是 UI 选项,后台无所谓。其实订单级
plan_id才是后续订阅链路的锚点。 - 以为 one-time sale 和 recurring 商品想混就混。源码里已经用
allow_one_time_sale、_has_one_time_sale()等逻辑精细区分。 - 以为支付页识别不到订阅也没关系。实际上支付页不知道自己在处理 subscription,很多提醒和模板逻辑都会偏掉。
四、实战落地时最该盯的点
- 先检查产品模板上的 recurring pricing 是否完整,否则前台选了计划也可能找不到适配 price。
- 再检查是否真有业务需要允许 one-time 附加项;有的话,要把规则讲清楚,不要让运营误以为任何组合都能混卖。
- 最后验证支付页上下文里
is_subscription是否稳定出现,尤其是自定义模板或第三方支付接管页面时。
五、结论
网站订阅的难点从来不只是前台展示价格,而是让计划、购物车、支付页在同一条 recurring 语义线上同步。Odoo 企业版把这条线守在 _cart_add() 这一层,恰恰说明它知道最容易出事故的地方在哪里。
DISCUSSION
评论区