先说结论
如果你把 Odoo 支付的难点只理解成“交易成功后状态怎么变”,那你只看到了后半段。
payment_provider.py 里的 _get_compatible_providers() 告诉我们,真正决定用户能不能在前台看到某个支付方式的,往往是建单之前的一连串资格筛选:
- 这个 provider 属不属于当前公司
- 它有没有启用、有没有发布给外部用户
- 付款方国家是否被支持
- 金额是否超过最大金额
- 当前币种是否兼容
- 这次流程是否强制 tokenization
- 是不是 express checkout
所以一句话概括:
Odoo 的支付接入,首先是 provider 路由问题,其次才是交易状态问题。
_get_compatible_providers() 像什么
这个方法本质上就是一个支付可用性裁决器。
它不是问“理论上谁能收款”,而是问:
- 在当前公司
- 针对当前 partner
- 用这个金额和币种
- 在这次支付意图下
到底有哪些 provider 真的能被拿出来给用户选。
这类设计非常平台层。因为一旦支付方式已经被前台展示,用户会默认“这条路应该能走通”。如果你把大量不兼容 provider 也展示出来,后面即使交易状态机再漂亮,用户体验依然会像坏掉了一样。
第一道闸:公司、启用状态、外部可见性
方法一开始就按公司 domain 和 state in ['enabled', 'test'] 搜 provider。
这个动作很基础,但很多定制团队会忽略它的重要性。
支付方式不是全局按钮,而是公司维度的可运营能力。尤其多公司场景下:
- A 公司配置了 Stripe
- B 公司只开了转账或本地收单
这不是 UI 偏好问题,而是实际资金路由与结算主体问题。
随后,若当前用户不是内部用户,provider 还必须 is_published。
这又是一条常被低估的边界:
- “已启用”不等于“对外开放”
- 测试中、灰度中、内测中的 provider 可以存在,但不该被外部结账页看见
第二道闸:国家匹配不是装饰字段
源码接着按 partner 国家过滤 available_country_ids。
这说明 available_country_ids 不是“给运营看着舒服”的元数据,而是真正参与裁决的接入条件。
为什么它这么重要?因为现实中的支付渠道往往受这些约束:
- 只支持特定国家发卡行
- 只允许某些地区商户主体使用
- 某些钱包只面向指定市场
Odoo 把这种差异放在 provider 选择阶段解决,而不是让用户点进去后才报“当前地区不支持”。这就是平台层该做的事:尽早失败,最好是根本别展示。
第三道闸:最大金额判断为什么先换算成公司币种
当有币种时,源码会先把支付金额换算成公司币种,再与 provider 的 maximum_amount 比较。
这个细节非常关键。因为 provider 的运营阈值通常是按结算主体或财务主体定义的,不一定和前台支付币种一致。
如果你直接拿展示币种去比,就会遇到两个问题:
- 同一个 provider 的上限在不同币种下不可直接比较
- 金额阈值与公司财务口径脱节
Odoo 先转到公司币种,等于先回到“这个商户主体怎么看这笔钱”再做判断。这种思路很适合多币种支付。
第四道闸:币种兼容比你想的更早发生
接下来是 available_currency_ids。
很多实施项目喜欢把币种兼容理解成:
- 建单后交给 provider 报错
- 或者让 provider API 自己兜底
但 Odoo 显然不这么设计。它把币种过滤前置到了 provider 选择层。
这有两个直接收益:
- 结账页不会把注定失败的方式展示给用户
- 运营团队能更清楚地看到“这个 provider 当前为什么没出现”
源码里还支持 report 记录 unavailable reason,这意味着 Odoo 不只是过滤,还尝试给出可解释的不可用性。这对排障和运营非常有价值。
Tokenization 真正的入口不是“支付成功后建 token”
很多人谈 tokenization,只盯着 transaction 完成后有没有生成 token。
但从 provider 侧源码看,更前面的关键问题是:
这次支付流程是否应该只保留支持 tokenization 的 provider。
_get_compatible_providers() 里只要:
force_tokenization=True- 或
_is_tokenization_required(**kwargs)返回真
就会把不支持 allow_tokenization 的 provider 直接过滤掉。
这说明 tokenization 首先是渠道准入条件,然后才是交易后处理能力。
举个最实战的例子:
- 你要做订阅自动扣款
- 你要做首单绑卡、后续自动续费
- 你要做先验证支付工具、以后再扣款
这些都要求 provider 在一开始就支持 tokenization。否则你前台给了用户错误选择,后面根本接不上业务链路。
_is_tokenization_required() 为什么默认返回 False
基类默认返回 False,其实是一种很清晰的平台声明:
- Odoo 不预设所有支付都必须 tokenization
- 具体业务上下文要由上层模块或 provider 自己决定
这让平台具备扩展性。
有些流程只是一笔普通在线支付;有些流程却天然要求“支付关系可复用”。把这个判断抽象成可 override 方法,比把逻辑散落在控制器和前端模板里健壮得多。
Validation amount / currency 在解决什么问题
_get_validation_amount() 默认返回 0.0,而 _get_validation_currency() 会在 provider 支持币种、支付方式支持币种和公司币种之间选一个合适值。
这套设计对应的是:
- 不是所有 tokenization 都要真实扣款
- 有些 provider 会走 0 元验证
- 有些则要做一笔小额验证
- 验证币种也不一定等于用户下单币种
很多团队容易把验证交易看成“小测试单”。其实它不是测试,而是支付工具可复用性的前置建档动作。
既然是平台动作,就必须把金额和币种约束明确下来,而不能交给 provider 端“临场发挥”。
Express checkout 为什么也是 provider 过滤问题
如果 is_express_checkout=True,Odoo 会进一步要求 provider allow_express_checkout。
这说明 express checkout 不是简单地“把普通支付按钮换个位置”。它意味着渠道要满足另一套交互契约:
- 能否快速拿到付款信息
- 是否支持更短路径的确认流程
- 是否适合在当前页面、当前上下文下直出
所以 Express 能不能开,不是产品经理拍板,而是 provider 能力与当前业务流共同决定。
实战最容易踩的 5 个坑
坑 1:把不可用 provider 也展示出来
用户一旦看到按钮,就会认为应该能付。过滤做晚了,失败体验就会直接暴露给终端用户。
坑 2:多公司场景下把 provider 当全局资源
支付配置天然跟公司主体、结算主体、法务责任边界绑定。
坑 3:把 tokenization 当交易完成后的附属功能
实际上很多业务里,它是支付渠道能否被选中的前置条件。
坑 4:忽略 validation amount / currency
绑卡、小额验证、0 元验证是否可行,都会影响你能不能安全建立可复用支付令牌。
坑 5:把 express checkout 当 UI 开关
它本质上是 provider 能力过滤,不只是前端样式变化。
结语
如果你想真正看懂 Odoo Payment,建议把视角从“状态机如何收尾”往前挪一步,看渠道如何在交易创建前被筛选和解释。
因为平台层最重要的能力,往往不是把已经开始的交易处理好,而是让不该开始的交易根本不要开始。
_get_compatible_providers() 做的,正是这件事。
DISCUSSION
评论区