支付前校验

Odoo 电商支付页为什么总在“重新检查”:配送可用性、零金额订单与付款前重算主链路讲透

Odoo 网站支付页不是简单列出 payment provider。进入支付前,系统还会重查地址是否完整、配送方式是否可用、购物车是否需要重算,以及零金额订单能否直接确认。

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

先说结论

很多人把网站支付页理解成这样:

  • 地址填完;
  • 到支付页;
  • 选支付方式;
  • 下单。

但 Odoo 的 website_sale 并不相信“前一步通过了,后一步就一定没问题”。

它在支付页前后会反复检查几件事:

  1. 购物车本身是否仍有效;
  2. 地址是不是完整;
  3. 当前地址下是否还有可用配送方式;
  4. 购物车改动后,优惠、运费、总价是否需要重算;
  5. 如果应付金额已经是 0,是否可以跳过真实支付直接确认订单;
  6. 用户从支付渠道跳回时,系统还能不能正确找回这张单。

所以 Odoo 的支付页,本质上不是“展示 payment provider 列表”,而是:

付款前最后一道商业一致性闸门。


一、为什么进入 /shop/payment 前还要再跑一次地址校验

shop_payment() 里第一件事不是取支付方式,而是:

  • _check_cart_and_addresses(order_sudo)

这很有代表性。

因为在真实网站环境里,地址虽然看似已经填完,但并不代表此刻仍然有效。比如:

  • 用户在别的标签页改过购物车;
  • 配送商品发生变化;
  • 地址其实只填了一半;
  • 某个地址刚被切换成了另一个子联系人;
  • 之前可配送,现在这个国家或州下已经无合适配送商。

Odoo 的态度是:

  • 只要还没真正付款,订单就仍然可能变化。

所以支付页不是“承接前一步结果”,而是再做一次确认。


二、为什么支付页打开时还会 _recompute_cart()

通过地址检查后,shop_payment() 还会执行:

  • order_sudo._recompute_cart()

这个动作特别关键。

因为网站购物车在用户看来像静态页面,但对 Odoo 来说,它仍是一张会受多种规则影响的动态销售单:

  • 优惠活动可能刚失效;
  • 自动奖励可能刚满足或不再满足;
  • 运费可能因地址或金额变化而变化;
  • 税额可能因配送地址更新而变化;
  • 某些 reward line 可能需要重新整理。

也就是说,支付页真正要面对的金额,不该依赖“上一步显示过什么”,而该依赖:

  • 此刻重算后的订单事实。

这也是为什么做支付前定制时,不能只改模板,不看 _recompute_cart()


三、为什么“没有可用配送方式”会直接阻断支付

_get_shop_payment_errors() 里有个非常实际的检查:

  • 如果订单有可配送商品,但当前地址下 _get_delivery_methods() 为空,就直接报错,禁止进入正常支付流程。

这说明在 Odoo 看来,支付资格不是只看 payment provider 是否可用,还要看订单是否具备可履约性

这是非常合理的。

因为如果一个地址根本没有配送方式,你让用户先付款,后面再人工解释“其实寄不到”,体验和风险都很差。

所以 Odoo 选择把这个问题前置到支付页。

这也是电商定制里一个很容易忽略的边界:

  • 支付不只是金融动作,还是履约承诺动作。

四、为什么零金额订单不一定要走支付渠道

/shop/payment/validate 里,Odoo 对两种场景分开处理:

场景一:有应付金额

  • 应该存在最后一笔 portal transaction;
  • 否则就回到商店路径,避免出现“没支付却当成成功”。

场景二:应付金额为 0

  • 如果没有 transaction,且订单尚未 sale;
  • 系统会先 _check_cart_is_ready_to_be_paid()
  • 然后直接 _validate_order()

这点很值得注意。

它说明 Odoo 并不把“支付页”机械等同于“必须发起支付交易”。

如果订单经过优惠、礼品卡、余额或其他机制后总额为零,真正需要的是:

  • 确认订单具备成立条件;
  • 然后直接确认。

这是一种很符合商业语义的设计。


五、为什么 sale_last_order_id 这么关键

源码在 checkout 和 express checkout 流程里都会把:

  • request.session['sale_last_order_id']

记下来。

这看似普通,实际上是网站支付回跳流程的关键保险丝。

因为真实世界里很容易出现:

  • 用户跳到第三方支付页;
  • 支付完又没按标准回跳;
  • 或浏览器会话里的当前 cart 标识已经被清掉;
  • 用户直接刷新确认页。

如果系统没有一个“最近正在处理的订单”指针,就很难把支付结果和正确订单重新对上。

所以 sale_last_order_id 的职责,本质上是:

  • 给支付后的页面恢复和确认流程兜底。

六、为什么支付页故意把 submit button 放到支付表单外面

_get_shop_payment_values() 里有个细节:

  • display_submit_button 被设成 False
  • 然后在网站模板外部重新放一个提交按钮。

这反映出 Odoo 网站支付页并不想完全照搬 portal 支付组件,而是保留 checkout 页面自己的布局和步骤感。

本质上这是两层体系的结合:

  • 底层复用 payment portal 的交易能力;
  • 上层保留 website_sale 自己的 checkout UI 节奏。

这对定制很重要。

因为你修改支付页时,往往不是只改 payment provider 列表,而是在改“电商结账页中的支付阶段”。


七、支付成功后为什么还要 sale_reset()

shop_payment_validate() 里,订单确认或校验通过后,会执行:

  • request.website.sale_reset()

它的意义很直接:

  • 这张购物车不再应被当前网站会话当成“仍可继续编辑的 cart”。

否则会出现很多经典问题:

  • 用户支付后刷新页面还在同一 cart 里;
  • 又继续改数量;
  • 或造成确认页和当前会话订单状态不一致。

所以支付后的 session 清理,不是小细节,而是 checkout 完整性的最后一步。


八、做网站支付定制时最容易踩的坑

1)只关心支付渠道,不关心履约条件

最后就会出现“能付钱但不能发货”的订单。

2)在支付页前跳过重算

优惠、运费、税、总价都有可能过期或变动,跳过重算等于接受脏数据下单。

3)忽略零金额订单

很多项目只测正常付款,却没测折扣后 0 元订单,最终会卡在“没有 transaction 也不确认”。

4)破坏 sale_last_order_id 或支付后 reset 流程

支付回跳、确认页和购物车清理很容易因此全部乱套。


最后一句

Odoo 电商支付页真正负责的,不只是展示支付方式,而是把:

地址完整性、配送可行性、购物车重算、交易存在性、零金额确认与会话清理

串成一次付款前后的最终一致性检查。

把这一层看懂之后,你才会明白:Odoo 的网站支付页其实更像一扇闸门,而不是一个简单的支付方式选择器。

DISCUSSION

评论区

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