很多人对 Odoo POS 在线支付的理解是:
顾客点个链接,支付成功,订单就算收款了。
这句话危险的地方在于,它把三个本来不同的对象压扁成了一件事:
- 支付交易;
- 会计付款;
- POS 支付行。
而 Odoo 恰恰不愿意把这三件事混成一坨。
结论先说:Odoo POS 在线支付的核心,不是“让 POS 也能跳支付页”,而是建立一条从 payment.transaction → account.payment → pos.payment 的桥接链,并且在 draft 订单阶段严格限制谁能创建付款行。 这就是它比“网页支付回调改订单状态”严谨得多的地方。
为什么 Odoo 不直接在 POS 订单上写一个“已在线支付”字段
在 pos_online_payment 里,你会看到三个对象同时存在:
payment.transactionaccount.paymentpos.payment
其中 payment.transaction 增加了 pos_order_id,而 pos.payment 增加了 online_account_payment_id。
这表示 Odoo 的思路不是“POS 订单自己记住支付成功”,而是:
- 支付世界先用交易对象确认网关状态;
- 需要会计落地时,生成
account.payment; - 最后再把结果桥接成 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,或者使用的是在线支付方式,那么以下关键字段不可随意改:
amountpayment_datepayment_method_idonline_account_payment_idpos_order_id
这说明 Odoo 认定在线支付不是普通现金行,不能像本地收款那样想改就改。因为一旦你动这些字段,就等于破坏了:
- 网关交易记录;
- 会计付款记录;
- POS 订单支付记录。
换句话说,系统保护的不是某一张 POS 单,而是跨模块一致性。
为什么会出现“支付成功了,但 POS 还没结单”
这类问题很常见,但不能只用一句“异步”带过。 源码里真正的顺序是:
- 交易进入
authorized或done; - 交易后处理执行;
- 创建或确认
account.payment; - 生成
pos.payment; - 检查订单是否已满足
_is_pos_order_paid(); - 满足后才
_process_saved_order(False); - 再通过 bus 通知前端刷新。
所以“支付成功但 POS 还没变 paid”通常要排查的是:
- 交易状态是否已进入可处理状态;
- 会计付款是否成功创建;
- POS online payment method 是否正确找到;
- 该笔支付金额是否足以覆盖 unpaid amount;
- 前端是否已收到刷新通知。
最容易误解的四件事
误区一:在线支付成功就等于 POS 支付行已经存在
不对。
交易成功只是起点,后面还要桥接成 account.payment 和 pos.payment。
误区二:前端把付款行传上来就最省事
不对。 这正是 Odoo 刻意阻止的做法,因为它会让浏览器变成支付真相来源。
误区三:POS 在线支付和门店现金支付只是支付方式不同
不对。 在线支付多了交易对象、后处理、会计付款与跨模块一致性约束。
误区四:支付后不能改,是因为界面太保守
不对。 本质是为了保护网关、会计、POS 三层数据一致。
实战排错顺序
如果你遇到“支付成功但订单没 paid / 重复付款 / 前端能传上奇怪支付行 / 在线支付方式不生效”,建议按这个顺序查:
- 交易
payment.transaction是否已到authorized或done; _post_process()是否触发_process_pos_online_payment();account.payment是否已创建成功;pos_order.online_payment_method_id是否能正确算出;pos.payment是否带着online_account_payment_id正确创建;- 该订单
amount_unpaid与付款金额是否匹配; _is_pos_order_paid()是否返回真;- 前端是否收到
ONLINE_PAYMENTS_NOTIFICATION并刷新状态。
最后的结论
Odoo POS 在线支付的关键,不在“多了个支付按钮”,而在于它非常克制地分离了三层事实:
- 支付网关看到的交易事实;
- 会计看到的付款事实;
- POS 订单看到的收款事实。
因此真正该记住的不是“支付成功就算结束”,而是:
在线支付要先在支付世界成立,再被安全地翻译回 POS 世界。
DISCUSSION
评论区