企业版在线付款

Odoo 企业版在线批量付款为什么不是“点发送=银行已处理”:SEPA 批次、KYC、Webhook 与状态机链路讲透

很多人看到 Odoo 企业版把 SEPA 批量付款接到在线银行链路后,会以为 validate 一次就等于银行已执行。可 `account_online_payment` 真正做的是把“可否在线支付”“是否完成 KYC”“是否已发起签署”“银行是否回传状态”拆成多层状态:link 激活、batch initiation、sign、webhook、cron 查询、draft 锁定,各自回答不同问题。

企业 会计
进阶 开发者 3 分钟阅读
0 评论 0 点赞 0 收藏 7 阅读

很多财务团队第一次看到 Odoo 企业版把 SEPA 批量付款 接进在线银行链路,都会下意识地把它理解成:

  • 批次建好;
  • 点一次 Validate;
  • 银行那边就算处理完成。

但如果你去读 /home/ubuntu/odoo-temp/enterprise/account_online_payment 的源码,会发现官方压根没把它设计成一个“点完即 done”的按钮。

它真正拆开的,是至少五层问题:

  1. 这条 bank link 是否允许 payments?
  2. payments 能力是否已激活,KYC 是否走完?
  3. 这个 batch 目前只是发起,还是已经签署?
  4. 银行/聚合服务是否已经回传最新状态?
  5. 一旦送行,底层 payment 还能不能再退回 draft 修改?

本文主要基于:

  • account_online_payment/models/account_online_link.py
  • account_online_payment/models/account_batch_payment.py
  • account_online_payment/models/account_payment.py
  • account_online_payment/controllers/odoofin_webhooks.py
  • account_online_payment/tests/test_account_online_payment_activation.py
  • account_online_payment/tests/test_account_online_payment_webhook.py

先说结论:Odoo 企业版在线批量付款不是“验证即完成”,而是把连接启用、支付激活、KYC、批次发起、签署、银行回传、后续锁定拆成多层状态机。你在界面上看到的一次点击,源码里通常只是把批次送进下一阶段。


先看 models/account_online_link.py

企业版在 account.online.link 上新增了两个字段:

  • is_payment_enabled
  • is_payment_activated

这两个字段看起来很像,但语义完全不同。

is_payment_enabled:这条连接有没有付款能力

_activate_payments() 一上来先检查:

  • 如果 not self.is_payment_enabled,直接报错;
  • 错误文案明确写着,必须在连接银行账户时先启用 payments。

也就是说:

并不是所有在线银行连接,天然都能走在线付款。

如果连接时就没勾上 payments,这条 link 后面根本不会进入激活流程。

is_payment_activated:能力开了,但是否已真正激活

同一个方法还会拦第二层:

  • 如果已经激活,就报 “Payments are already activated.”

所以源码明确区分:

  • 具备资格 去激活;
  • 已经完成 激活。

这就是很多财务用户会迷惑的第一层原因:

  • “不是都连上银行了吗,为什么还不能在线发款?”

答案是:

连上银行账户,不等于付款能力已开;付款能力已开,也不等于激活手续完成。


二、激活 payments 不是本地改个布尔值,而是会进入外部 KYC/跳转流程

_activate_payments() 的实现不是简单写 is_payment_activated = True

它会反复请求:

  • /proxy/v1/activate_payments

如果响应里带 next_data,就继续下一轮请求;直到没有 next_data 为止。最后返回的是:

  • redirect_url

并用 ir.actions.act_url 打开新窗口。

这说明 Odoo 在这里做的不是“本地开户”,而是:

把用户送到外部支付/KYC 流程去完成下一步。

更关键的是 _success_link()

连 bank link 成功后,Odoo 还会尝试 _activate_payments()。如果拿到了跳转链接,它会:

  • 发 activation 邮件模板;
  • 给当前用户排一个 Todo activity;
  • 弹出通知,提示去完成 KYC;
  • 文案明确说明:没完成 KYC,还不能直接从 Odoo 处理付款。

这一步非常关键,因为它说明:

在线付款能力不是“银行连接成功”时就算完整开通,KYC 是一个被单独建模、并且可能持续几天的异步流程。

这也是为什么很多团队会以为系统卡住,实际只是还在等合规链路走完。


三、在线批量付款只在特定条件下接管 validate_batch()

再看 models/account_batch_payment.py

validate_batch() 并不是无条件走在线支付。

如果满足任一条件,Odoo 就回退到父类原逻辑:

  • payment_method_code != 'sepa_ct'
  • not account_online_link_payments_enabled
  • 上下文里带 xml_export

这三条的意思其实很清楚:

  1. 不是 SEPA Credit Transfer 批次,就别走这条在线链路;
  2. journal 背后的在线 link 没开 payment,也别走;
  3. 如果用户明确要求 XML 导出,就继续传统导出流。

所以在线付款不是在取代 XML,而是在特定前提下接管 SEPA 批次。

为什么这点特别重要?

因为很多人会误会:

  • “装了这个模块,以后批量付款都必须在线走。”

源码给出的答案恰好相反:

Odoo 保留了传统 XML 导出路径,把在线付款作为可条件触发的增强流,而不是暴力替换。


四、发起批次后,不代表已签署,更不代表银行已执行

这是这个模块最值得讲清的一点。

AccountBatchPayment 新增了:

  • payment_identifier
  • payment_online_status

状态集合包括:

  • uninitiated
  • unsigned
  • pending
  • accepted
  • canceled
  • rejected

这串状态已经在告诉你:

线上批次付款不是 done / not done 两态,而是一个多阶段状态机。

第一次 validate 做了什么?

当在线付款条件满足时,validate_batch() 会:

  1. _check_batch_validity()
  2. 组装 _prepare_payment_data()
  3. /proxy/v1/initiate_payment
  4. 若响应带 next_data,继续请求直到结束;
  5. 然后分两种结果。

情况 A:触发 kyc_flow

如果响应里有 kyc_flow,Odoo 不会假装批次已送行,而是:

  • 用超级用户在 chatter 发一条消息;
  • 明确告诉你:需要 KYC,可能要几天;
  • meantime 请先使用 SEPA XML export。

这段逻辑很漂亮,因为它没有强行失败,也没有假装成功,而是明确告诉你:

在线通道暂时不能继续,但业务可以退回到 XML 备份路径。

情况 B:没有 kyc_flow

这时 Odoo 才会:

  • _send_after_validation()
  • 写入 payment_identifier
  • 写入 payment_online_status
  • 打开 redirect_url

注意,这里也只是“发起并记录外部识别号 + 当前在线状态”,不是“银行已经完成执行”。


五、unsigned 不是失败,而是等待真正签署

initiate_payment() 里还有一个特别容易被忽略的分支。

如果批次当前是:

  • payment_online_status == 'unsigned'
  • state == 'sent'

那么 Odoo 不会重复普通 validate,而是:

  1. check_online_payment_status() 刷一下最新状态;
  2. 如果已经不是 unsigned,弹个 warning 通知并 soft reload;
  3. 如果仍是 unsigned,走 _sign_payment()

这背后表达的是:

“已发起”与“已签署”是两个动作。

也就是说,用户第一次点完之后,批次可能只是进入了一个待签署的在线状态,而不是已经完成银行确认。

_sign_payment() 又做了什么?

它会调用:

  • /proxy/v1/sign_payment

并继续处理 next_data,然后刷新:

  • payment_online_status
  • payment_identifier

再跳转到外部 URL。

所以从业务视角看,一笔在线批量付款至少可能经历:

  • 建批次;
  • 发起;
  • 签署;
  • 等银行处理;
  • 接收状态回传。

而不是一个单按钮动作。


六、Webhook 和 cron 共同负责“外部状态回写”,不是靠用户一直手点刷新

在线支付最难的一件事,是 状态不在 Odoo 本地闭环

源码里对此给了两种回写机制。

1)Webhook:用于 payment activation 完成回写

控制器 controllers/odoofin_webhooks.py 暴露:

  • /webhook/odoofin/payment_activated

逻辑很简单但很关键:

  • client_id 找到 account.online.link
  • 发成功邮件;
  • is_payment_activated 写成 True。

配套测试 test_account_online_payment_webhook.py 也验证了:

  • client 存在时,状态会更新;
  • client 不存在时,Webhook 仍返回成功,但不会误改别的 link。

这说明激活状态不是靠用户手工勾选,而是:

外部服务完成流程后,再异步回写 Odoo。

2)cron / 手工查询:用于批次付款状态跟进

check_online_payment_status() 会按批次调用:

  • /proxy/v1/get_payment_status

然后把返回值写回 payment_online_status

_cron_check_payment_status() 又会定时扫描:

  • 未 reconciled
  • sepa_ct
  • journal link 已 payment activated
  • 状态属于 unsignedpending

的批次,自动刷新状态。

这说明官方很清楚:

银行或支付聚合器的状态变化,本来就可能晚于用户点击动作,所以系统必须有异步回查机制。


七、一旦送行到一定阶段,底层 payment 就不允许再回 draft 修改

models/account_payment.py 只扩了一小段,但业务意义特别重。

action_draft() 里,如果某笔 payment:

  • 挂在 batch 上;
  • 且 payment method 是 sepa_ct
  • 且 batch 的 payment_online_status 属于 pendingaccepted

就直接抛错:

  • You cannot modify a payment that has already been sent to the bank.

这条规则非常重要,因为它在说:

一旦 Odoo 认为这笔付款已进入银行处理链,就不能再把底层 payment 当成本地草稿单随意重写。

这不是界面限制,而是资金控制边界。


八、为什么导出 XML 时,系统还会提醒“有些已签署付款可能已经送行”

export_batch_payment() 里还有个很容易忽略的安全提示。

对已经可在线付款的 sepa_ct 批次,如果不是 xml_export 场景,Odoo 会跳过常规导出逻辑。

而当确实执行导出后,如果有批次的 payment_online_status 属于:

  • pending
  • accepted

系统会发消息提醒:

  • 已签署付款可能已经被发送到银行。

这相当于给财务一个非常现实的防重警告:

在线送行和 XML 导出不是绝对隔离的两个宇宙;如果状态已推进到 pending/accepted,再导出就可能造成重复处理风险。

这也是为什么企业版保留 fallback,但又不断提醒你注意状态边界。


九、_prepare_payment_data() 说明 Odoo 发给外部的不只是金额

很多人以为在线付款只是把总额扔给银行。

_prepare_payment_data() 实际上传的是一整包结构化付款信息,每笔 payment 包含:

  • amount
  • account_number
  • account_type(IBAN)
  • creditor_name
  • currency
  • date
  • reference
  • structured_reference
  • end_to_end_uuid

批次层还会带上:

  • account_id
  • batch_booking
  • date
  • payment_type = bulk
  • reference = self.name

特别值得注意的是:

  • structured_reference 会调用 is_valid_structured_reference(payment.memo)

也就是说,Odoo 在发起前就区分了普通备注和结构化参考号。

这说明这条集成链不是“导个总文件”而已,而是:

把单笔付款级别的银行字段结构化后,再交给外部支付服务。


十、实战里最容易误解的 6 件事

1. 以为银行连接成功就等于在线付款已可用

不对。

还要看 is_payment_enabledis_payment_activated

2. 以为 validate 一次就等于银行执行完成

不对。

那往往只是 initiate 或进入待签署状态。

3. 以为 unsigned 是失败状态

不对。

它通常表示批次还没完成签署。

4. 以为 KYC 只是开户时一次性动作

不对。

源码明确把它视为可能阻塞在线付款、且需异步完成的流程。

5. 以为在线付款开了,XML 导出路径就不存在了

不对。

xml_export 仍然是保留的 fallback。

6. 以为 payment 已挂进 batch,就还能随时回 draft 改资料

不对。

当状态进入 pending / accepted,底层 payment 会被锁住。


十一、如果你要二开这条链,最该尊重哪些边界?

如果你准备在企业版在线付款上做扩展,我建议优先守住这些边界:

1. 不要把 is_payment_enabledis_payment_activated 混成一个字段

资格、启用、激活,是三层不同语义。

2. 不要把 initiate 和 sign 合并成“一个完成动作”

源码明确允许批次停在 unsigned,这层过渡很关键。

3. 不要取消 KYC 分支里的 XML fallback

这是官方留给业务连续性的后备通道。

4. 不要忽略 webhook / cron 回写

在线支付状态天然是异步的,本地强行同步化反而会失真。

5. 不要放开 pending / accepted 后的 draft 回退

这会直接破坏送行后的资金控制边界。


总结

account_online_payment 最值得学的一点,是它没有把“在线批量付款”做成一个假装全能的单按钮,而是老老实实拆成一条跨系统状态链:

  1. link 是否允许 payments
  2. payments 是否已激活,KYC 是否完成
  3. batch 是否已 initiate
  4. batch 是否已 sign
  5. 外部服务是否通过 webhook / status query 回写最新结果
  6. 送行后的 payment 是否应禁止再改

所以在 Odoo 企业版里,“我点了发送”通常只代表批次进入下一阶段,不代表银行已经处理完成;而真正让这条链可靠的,恰恰是那些看起来繁琐的激活、KYC、状态查询、Webhook 和修改限制。

如果把这一点看懂,你在做财务实施、培训用户、设计操作 SOP,或者做支付集成二开时,就不会再把在线批量付款理解成“XML 导出换了个按钮皮肤”。

DISCUSSION

评论区

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