POS 在线支付

Odoo POS 在线支付为什么不是“支付成功就完了”:交易桥接、draft 单保护与入账边界讲透

很多人以为 POS 在线支付只是给订单挂一个 payment link,但 Odoo 真正在做的是“交易对象先落支付模块,再桥接回 POS 支付行,并且在 draft 订单阶段严格防止前端乱写付款”。本文结合 pos_online_payment 源码,讲清 payment.transaction、account.payment、pos.payment 三层对象为什么分开,以及为何 draft 单的在线支付行不能直接从 UI 进来。

POS
进阶 开发者 2 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多人对 Odoo POS 在线支付的理解是:

顾客点个链接,支付成功,订单就算收款了。

这句话危险的地方在于,它把三个本来不同的对象压扁成了一件事:

  • 支付交易;
  • 会计付款;
  • POS 支付行。

而 Odoo 恰恰不愿意把这三件事混成一坨。

结论先说:Odoo POS 在线支付的核心,不是“让 POS 也能跳支付页”,而是建立一条从 payment.transactionaccount.paymentpos.payment 的桥接链,并且在 draft 订单阶段严格限制谁能创建付款行。 这就是它比“网页支付回调改订单状态”严谨得多的地方。

为什么 Odoo 不直接在 POS 订单上写一个“已在线支付”字段

pos_online_payment 里,你会看到三个对象同时存在:

  • payment.transaction
  • account.payment
  • pos.payment

其中 payment.transaction 增加了 pos_order_id,而 pos.payment 增加了 online_account_payment_id

这表示 Odoo 的思路不是“POS 订单自己记住支付成功”,而是:

  1. 支付世界先用交易对象确认网关状态;
  2. 需要会计落地时,生成 account.payment
  3. 最后再把结果桥接成 POS 可理解的 pos.payment

这条链的意义很大,因为三层各自回答不同问题:

  • payment.transaction:网关侧发生了什么;
  • account.payment:会计侧记了什么;
  • pos.payment:POS 订单视角下到底记了哪笔收款。

_post_process() 为什么这么关键

payment_transaction.py 里,模块覆写了 _post_process(),随后调用 _process_pos_online_payment()

这一步本质上是在说:

支付模块那边确认交易状态以后,才轮到 POS 把它接进自己的世界。

_process_pos_online_payment() 会做这些事:

  • 只处理 authorized / done 的交易;
  • 如果没有会计付款,则先 _create_payment()
  • 找到或创建 POS 对应的在线支付方式;
  • 调用 pos_order.add_payment() 写 POS 支付行;
  • 回填 account.payment 的 POS 关联字段;
  • 若订单已满足支付条件,再 _process_saved_order(False)
  • 通知 POS 前端刷新在线支付状态。

这说明 Odoo 并不是在 payment callback 里粗暴地把订单设成 paid,而是在逐层补齐“交易 → 付款 → POS 收款 → 订单状态”的桥梁。

为什么 draft 单要拦截来自前端的在线支付行

pos_order._process_order() 里有一段非常关键的保护:

如果订单还是 draft,且配置里存在 online payment method,那么前端传进来的在线支付行会被过滤掉。

这背后逻辑非常明确:

draft 阶段,前端不能自称“我已经付过在线款了”。

原因也很现实:

  • 浏览器不该成为支付真相来源;
  • 在线支付成功与否,应由支付模块和交易状态决定;
  • 如果允许 UI 自己写支付行,门店前台和支付网关就会双写、错写、重复写。

所以 Odoo 选择把在线支付从“POS 前端直接写付款”改成“交易后桥接付款”。这是一个非常典型的防篡改边界。

为什么 pos.payment 还要限制 Essential Data 不可修改

pos_payment.py 里,如果某笔支付带 online_account_payment_id,或者使用的是在线支付方式,那么以下关键字段不可随意改:

  • amount
  • payment_date
  • payment_method_id
  • online_account_payment_id
  • pos_order_id

这说明 Odoo 认定在线支付不是普通现金行,不能像本地收款那样想改就改。因为一旦你动这些字段,就等于破坏了:

  • 网关交易记录;
  • 会计付款记录;
  • POS 订单支付记录。

换句话说,系统保护的不是某一张 POS 单,而是跨模块一致性。

为什么会出现“支付成功了,但 POS 还没结单”

这类问题很常见,但不能只用一句“异步”带过。 源码里真正的顺序是:

  1. 交易进入 authorizeddone
  2. 交易后处理执行;
  3. 创建或确认 account.payment
  4. 生成 pos.payment
  5. 检查订单是否已满足 _is_pos_order_paid()
  6. 满足后才 _process_saved_order(False)
  7. 再通过 bus 通知前端刷新。

所以“支付成功但 POS 还没变 paid”通常要排查的是:

  • 交易状态是否已进入可处理状态;
  • 会计付款是否成功创建;
  • POS online payment method 是否正确找到;
  • 该笔支付金额是否足以覆盖 unpaid amount;
  • 前端是否已收到刷新通知。

最容易误解的四件事

误区一:在线支付成功就等于 POS 支付行已经存在

不对。 交易成功只是起点,后面还要桥接成 account.paymentpos.payment

误区二:前端把付款行传上来就最省事

不对。 这正是 Odoo 刻意阻止的做法,因为它会让浏览器变成支付真相来源。

误区三:POS 在线支付和门店现金支付只是支付方式不同

不对。 在线支付多了交易对象、后处理、会计付款与跨模块一致性约束。

误区四:支付后不能改,是因为界面太保守

不对。 本质是为了保护网关、会计、POS 三层数据一致。

实战排错顺序

如果你遇到“支付成功但订单没 paid / 重复付款 / 前端能传上奇怪支付行 / 在线支付方式不生效”,建议按这个顺序查:

  1. 交易 payment.transaction 是否已到 authorizeddone
  2. _post_process() 是否触发 _process_pos_online_payment()
  3. account.payment 是否已创建成功;
  4. pos_order.online_payment_method_id 是否能正确算出;
  5. pos.payment 是否带着 online_account_payment_id 正确创建;
  6. 该订单 amount_unpaid 与付款金额是否匹配;
  7. _is_pos_order_paid() 是否返回真;
  8. 前端是否收到 ONLINE_PAYMENTS_NOTIFICATION 并刷新状态。

最后的结论

Odoo POS 在线支付的关键,不在“多了个支付按钮”,而在于它非常克制地分离了三层事实:

  • 支付网关看到的交易事实;
  • 会计看到的付款事实;
  • POS 订单看到的收款事实。

因此真正该记住的不是“支付成功就算结束”,而是:

在线支付要先在支付世界成立,再被安全地翻译回 POS 世界。

DISCUSSION

评论区

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