很多人看到 ISO 20022 导出,会自然把它想成“把付款记录吐成一个 XML 文件”。但企业版 account_iso20022 的实现告诉你:真正难的不是 XML 语法,而是付款在导出前是否合规、标识是否稳定、journal 如何按银行契约拼装文档结构。
一、付款对象先过合规门槛,才谈导出
account_payment.py 里的 _check_sepa_bank_account() 和 _check_sepa_currency() 很直接:SEPA 付款必须有正确的 IBAN,且币种必须是 EUR。也就是说,导出不是出错时才校验,而是在付款对象层就先挡掉明显不合法的数据。
这很像框架里的“前置契约检查”:只要付款本身不满足银行网络要求,后面的 XML 生成就不该开始。
二、稳定标识不是装饰品,而是银行链路必需品
_compute_end_to_end_uuid() 和 _compute_iso20022_uetr() 会为符合条件的付款自动生成 end-to-end id 与 UETR。它们的意义不在“多两个字段”,而在于:
- 银行处理链路需要稳定的交易标识;
- 后续对账、追踪、问题排查要靠这些 id 回溯;
- import 场景下又不能乱重算,源码特地在
create()里对导入文件做了 compute 取消处理。
这说明 Odoo 很清楚:谁有权生成标识、何时生成标识,本身就是协议的一部分。
三、journal 侧决定你导出的到底是哪一种 pain 契约
account_journal.py 里的 _compute_sepa_pain_version() 会根据银行账号国家或公司 fiscal country 选择默认 pain 版本。接着 create_iso20022_credit_transfer() / create_iso20022_credit_transfer_content() 负责建立整个 Document、GrpHdr、PmtInf 等节点。
这意味着 Odoo 并不是“把付款单循环输出”。它先确定:
- 本次使用哪个 namespace / pain 版本;
- 组头怎么写;
- 控制总额怎么算;
- 优先级、服务级别、本地指令等参数如何拼进
PmtTpInf。
四、逐笔交易节点才是银行契约真正落地的地方
_get_CdtTrfTxInf() 会把付款名称、EndToEndId、金额、币种、债务方/债权方账户等信息写成单笔节点。你可以把它理解成“每笔付款最终对银行说的话”。
也正因为如此,很多线上问题根本不是 XML 结构错,而是逐笔字段不符合银行预期:
- 指令 id 太长或格式不对;
- 币种/金额精度不匹配;
- 债权方账户信息不完整;
- charge bearer 或 priority 不符合银行支持范围。
五、主链路其实是“付款校验 → 标识生成 → journal 组文档”
把两组源码接起来,主链路可以理解为:
- payment 在模型层先满足 SEPA / ISO 约束;
- 自动生成协议级交易标识;
- journal 决定 pain 版本与 XML 文档骨架;
- 每笔付款再被映射到
CdtTrfTxInf节点。
所以导出 XML 只是最后一步,可真正决定能不能被银行接受的是前面的契约守卫。
六、新手误区
1. 以为只要 XML 验证通过,银行就一定收
错。银行更在意业务字段、版本契约和合规约束。
2. 以为 UUID 只是“系统内部主键”
这些标识直接影响链路追踪和对账。
3. 以为 pain 版本随便配
不同国家/银行对版本的接受度不一样,源码已经尽量给出默认策略。
七、实战注意事项
- 先校验 journal 的银行账号和公司国家信息,再测试导出;
- 对接银行前确认 pain 版本和支持字段,不要只看 Odoo 默认值;
- 导入历史付款时,注意不要误生成新的 end-to-end 标识;
- 排查失败时,逐笔看
CdtTrfTxInf,往往比只看整份 XML 更有用。
结语
企业版 ISO 20022 模块真正处理的,不是“把付款导出成 XML”,而是把付款对象、银行协议、稳定标识和文档拼装对齐成一条可投递的支付契约链。理解这一层,才知道它为什么是框架问题,而不是格式问题。
DISCUSSION
评论区