Payment

Odoo Payment 为什么不只是“支付成功 / 失败”两种结果:交易状态机、后处理与落单边界讲透

很多支付排错最后都卡在一句“网关回调成功了”。但 Odoo payment 真正复杂的地方在 transaction 状态机、后处理轮询、令牌化、授权捕获和订单落地时机。本文把这条平台链路讲透。

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

先说结论

Odoo 的 Payment 模块并不是“支付网关回个 success,订单就结束了”。

/home/ubuntu/odoo-temp/addons/payment/models/payment_transaction.pypayment_provider.pycontrollers/post_processing.py 可以看出,官方真正搭的是一套平台状态机:

  • 前面由 provider 决定可用能力
  • 中间用 payment.transaction 保存追踪与状态
  • 后面通过 /payment/status 和轮询后处理完成业务落地

所以最短结论是:

在 Odoo 里,支付结果不是一个瞬时布尔值,而是一条会继续演化的事务状态链。


为什么 payment.transaction 有这么多状态

源码里的状态至少包括:

  • draft
  • pending
  • authorized
  • done
  • cancel
  • error

很多团队会本能地觉得:

  • 成功 / 失败不就够了?

但支付世界不是这么工作的。

authorized 为什么单独存在

因为有些支付提供商支持先授权、后捕获。

也就是说:

  • 钱还没最终入账
  • 但持卡人的可支付能力已被冻结或确认

如果系统把它粗暴当成“done”,后面就会在发货、撤销、部分捕获这些环节把业务语义搞乱。

pending 为什么也不能省

因为真实支付链路经常有这些情况:

  • 用户跳出到第三方支付页
  • 银行 / 风控还在处理中
  • 网关先给了“受理中”
  • 后台 webhook 和前台回跳并不同步

这时候系统必须承认:

  • 交易存在
  • 但结论还没最终确定

所以 pending 不是拖延,它是现实。


为什么 transaction 要复制 partner 信息

create() 里会把 partner 的:

  • 名称
  • 语言
  • 邮箱
  • 地址
  • 国家 / 省州
  • 电话

复制到 transaction 上。

这不是重复存储的坏味道,而是支付追踪的典型做法。

因为支付不是单纯的当前实时对象关系,它还承担审计责任。

如果以后 partner 主数据被改了,平台仍然需要知道:

  • 这笔交易当时是以什么客户信息送去支付提供商的

换句话说,transaction 记录的是:

发起支付那一刻的业务快照,而不只是今天还能从 partner 上读到什么。


provider 和 transaction 的职责为什么要拆开

payment.provider 负责的是能力与可用性边界,例如:

  • 是否处于 disabled / test / enabled
  • 是否允许 tokenization
  • 是否支持 manual capture
  • 是否支持 refund
  • 是否支持 express checkout
  • 对国家、币种、金额有没有限制

payment.transaction 负责的是单笔交易追踪,例如:

  • 本次金额与币种
  • 当前状态
  • 操作类型 operation
  • 提供商参考号
  • 是否已 post-process
  • 是否要 token 化
  • 最终落地页面 landing_route

这个拆分很重要。

因为 provider 是“平台能力配置”,transaction 是“单笔业务事实”。

如果把两者混在一起,最后会出现两种灾难:

  • 平台配置变动污染历史交易理解
  • 历史交易状态又反过来影响平台能力判断

operation 为什么比很多人想得更关键

源码里的 operation 包括:

  • online_redirect
  • online_direct
  • online_token
  • validation
  • offline
  • refund

这说明 Odoo 不把所有支付都视为同一种动作。

例如:

  • “在线跳转支付一次”
  • “用保存的 token 复用扣款”
  • “只是验证支付方式,不真正扣钱”
  • “这是退款,不是新收款”

这些动作如果都混成“支付”,后续的账务、风控、客服解释都会开始混乱。

所以 operation 本质上是平台对业务语义的显式建模。


/payment/status 为什么不是装饰页面,而是后处理枢纽

post_processing.py 里的注释写得很直白:

  • 所有支付流都应该在某个时点经过 /payment/status
  • 用户在这里查看交易状态
  • 同时系统会触发 post-processing

这说明支付完成不是“回调到了,就万事大吉”。

它往往还差最后一段:

  • 事务是否落库
  • token 是否生成
  • 订单 / 单据是否跳去最终落地页
  • 页面向用户展示什么状态

所以 /payment/status 更像一个“支付尾声协调器”。

它一边给用户看进度,一边替平台补完最后那段易抖动的状态收敛。


为什么要轮询 poll_status()

poll_status() 会:

  • 取出当前 session 监控的 transaction
  • 如果还没 is_post_processed
  • 就调用 _post_process()
  • 若数据库提交出错,回滚并让前端重试

这件事非常关键,因为现实支付链路经常会遇到:

  • provider 已经说成功
  • 但 Odoo 这边还没完全完成业务后处理
  • 或提交过程中出现瞬时数据库问题

如果平台没有一个“可重试的后处理收敛层”,最后用户只会看到一种很糟糕的产品体验:

  • 钱扣了
  • 页面却像没成功
  • 订单也没落下来

所以轮询不是多余,而是把不稳定世界和稳定业务状态隔开的一层缓冲。


is_post_processed 为什么这么重要

这字段看起来只是一个布尔值,但它实际回答的是:

  • 这笔交易的支付结果,是否已经被 Odoo 内部业务逻辑完整承接

注意,这和“网关是不是成功”不是一回事。

真正的业务世界里,你要同时满足:

  • 外部支付有结论
  • 内部状态也收敛

is_post_processed 正是在描述第二件事。

所以一笔交易即便 provider 已经成功,也可能还没完全变成“平台层意义上的完成”。


为什么 capture_manually、refund、tokenization 都是平台能力而不是临时按钮

payment.provider 会计算:

  • support_manual_capture
  • support_refund
  • support_tokenization
  • support_express_checkout

这说明在 Odoo 眼里,这些都不是页面随便显示一下的功能点,而是:

该 provider 在平台契约层是否具备某种能力。

一旦被建模成能力,平台就可以稳定地问:

  • 当前 provider 是否允许我把交易推进到 authorized
  • 当前 provider 是否允许我后续发起部分退款
  • 当前 provider 是否允许我生成支付 token 给下次复用

这就是为什么支付平台一定要做能力建模,而不是只堆集成开关。


新手最容易误解的 5 件事

1. 误以为支付就只有成功和失败

现实链路里至少还有 pending、authorized、cancel、error,这些都是真实业务状态。

2. 误以为 provider 回调成功就等于订单落地完成

没有后处理收敛,这件事根本没结束。

3. 误以为 tokenization 是“顺手存一下卡”

它其实影响后续复用扣款、风险边界和客户体验,是平台能力之一。

4. 误以为授权和捕获是同一件事

对支持 manual capture 的 provider,这两步是不同业务承诺。

5. 误以为 /payment/status 只是一个用户提示页

它实际上承担着状态监控和后处理触发的枢纽角色。


实战排错顺序

如果你碰到“支付成功但订单没正确落地”这类问题,建议按这个顺序查:

  1. 先看 transaction 当前状态是 pending / authorized / done / error 哪一种
  2. 确认 provider 是否支持当前你想要的动作,例如 manual capture 或 refund
  3. 检查 session 里监控的 transaction 是否正确挂到了 /payment/status
  4. is_post_processed 是否一直没有完成
  5. 如果 post-process 出错,再看是不是数据库提交异常导致前端一直在重试

这样查,比只看“网关回调日志”有效得多。


一句话记忆

Odoo Payment 的本质不是“收款成功 / 失败”,而是用 provider 能力、transaction 状态机和可重试后处理,把外部支付世界收敛成内部可追踪的业务事实。

DISCUSSION

评论区

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