EMV 二维码

Odoo 发票上的付款二维码怎么长出来:account_qr_code_emv 的方法选择、TLV 组包与国家扩展边界讲透

很多人以为发票二维码只是报表上塞一张图片,但从 account_move.py、res_partner_bank.py 和 account_qr_code_emv 的实现看,Odoo 真正做的是先挑可用的 QR 方法,再校验银行账户是否满足国家规则,最后把金额、币种、商户信息和引用组装成 EMV 字符串并交给条码引擎渲染。

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

很多人看到 Odoo 发票上有付款二维码,会把它理解成一个很轻的功能:

报表模板里插一张二维码图片,不就完了吗?

但从 /home/ubuntu/odoo-temp/addons/accountaccount_qr_code_emv 的源码看,官方设计其实更像一条 支付信息编码管线

它至少分成四层:

  1. 发票决定自己要不要显示二维码;
  2. 系统从银行账户支持的方法里挑一个可用的二维码标准;
  3. 对应标准模块把金额、币种、商户信息、附言等组装成规范字符串;
  4. 最后再交给条码引擎渲染成真正可显示的 QR 图像。

所以二维码不是模板层的小花活,而是会计、银行账户配置、国家标准和报表输出一起协作的结果。

一、入口不在报表,而在 account.move_generate_qr_code()

account_move.py 里,真正的主入口是 _generate_qr_code()

它的大致顺序是:

  • 先判断 display_qr_code
  • 看发票上有没有手工指定 qr_code_method
  • 如果没有,就去银行账户支持的方法列表里找第一个“可用”的;
  • 然后调用 partner_bank_id.build_qr_code_base64(...) 生成二维码;
  • 成功后再把选中的 qr_code_method 回写到发票上。

这里最值得注意的是最后一步。

源码里特意等生成成功之后,才把 qr_code_method 写回去。原因也写得很直白:

如果提前写,万一生成失败,数据库状态和界面显示就可能不一致。

这说明官方把二维码生成看成一个 有可能失败的业务动作,而不是无脑渲染。

二、真正决定“能不能生成”的,不是发票,而是 res.partner.bank

account/models/res_partner_bank.py 提供了统一的 QR 能力框架。

核心方法是 _build_qr_code_vals()

它会:

  • 拿到当前银行账户;
  • 枚举所有可用的二维码方法;
  • 对每个方法先跑 _get_error_messages_for_qr(),看它在当前国家、当前账户、当前币种下是否“理论上适用”;
  • 再跑 _check_for_qr_code_errors(),看必填数据是不是齐全;
  • 只有都通过,才返回构造二维码需要的参数。

这一步很像面试筛选:

  • 第一关看“资格是否匹配”;
  • 第二关看“资料是否齐全”;
  • 两关都过,才真的去生成二维码。

所以你在业务现场看到“发票上没出二维码”,不一定是报表模板问题,更可能是:

  • 当前银行账户不支持该国家的 QR 方案;
  • 金额、币种、商户信息或引用数据不符合要求;
  • 系统找不到任何 eligible 的方法。

三、account_qr_code_emv 不是直接给你一个国家方案,而是先提供一套 EMV 骨架

account_qr_code_emv 最值得注意的地方,是它并没有把所有国家规则都写死。

相反,它先做了一层通用 EMV Merchant-Presented QR 的骨架:

  • _get_available_qr_methods() 里新增 emv_qr
  • _get_qr_vals() 负责把各字段序列化成 EMV 字符串;
  • _get_qr_code_generation_params() 把结果交给条码引擎,指定 barcode_type='QR'
  • _get_crc16() 负责最后的校验码。

但与此同时,它又把几个关键点留成了扩展钩子:

  • _get_merchant_account_info()
  • _get_additional_data_field()
  • _compute_display_qr_setting()

而这些默认实现里,很多其实返回的是空值或 False

这说明一个非常重要的设计事实:

account_qr_code_emv 更像“EMV 容器层”,真正落地到某国支付二维码,还需要继承模块补上国家规则。

所以不是装了它就一定能出二维码。

四、EMV 不是“随便拼个 JSON”,而是 TLV 结构 + CRC16

models/res_bank.py 里,_serialize() 把每一段内容编码成:

  • 两位 header
  • 两位长度
  • 再加实际值

这就是典型的 TLV(Tag-Length-Value)思路。

接着 _get_qr_code_vals_list() 会把这些字段按规范排好:

  • Payload Format Indicator
  • 动态二维码标识
  • Merchant Account Information
  • Merchant Category Code
  • Transaction Currency
  • Transaction Amount
  • Country Code
  • Merchant Name
  • Merchant City
  • Additional Data Field

最后 _get_qr_vals() 会把这些字段串起来,再补上 6304 这个 CRC 段头,然后调用 _get_crc16() 算最终校验值。

这意味着二维码内容不是“图片资产”,而是一段严格编码后的支付指令字符串。

五、为什么币种、商户名、城市和附言都不是随便放

account_qr_code_emv/const.py 里有 CURRENCY_MAPPING,把 Odoo 货币代码映射成 EMV 规范里的数字编码。

同时,_get_qr_code_vals_list() 还做了很多清洗:

  • 金额为 0 时可以置空;
  • 商户名和城市会去重音并截断长度;
  • comment 会过滤成允许字符集合;
  • 只有 include_reference 为真时,才尝试放 additional data。

这些细节很能说明问题。

二维码不是“你有个文本就塞进去”,而是:

  • 要符合长度限制;
  • 要符合字符集限制;
  • 要符合支付标准约束;
  • 还要保证扫码方能稳定解析。

六、为什么“模块装了但没法用”往往是设计使然,不是 bug

_check_for_qr_code_errors()emv_qr 额外检查了几件事:

  • Merchant Account Information 是否存在;
  • 商户城市是否有值;
  • proxy_type 是否存在;
  • proxy_value 是否存在。

_get_error_messages_for_qr() 又明确写着:

  • 没有银行账户就不能生成;
  • 如果当前国家没有任何可用 EMV 实现,就提示该账户所属国家没有可用 QR。

也就是说,官方并不想“尽量猜一个二维码出来”,而是宁可不给,也不要给出一张看起来有图、实际上不可支付的假二维码。

这是很典型的会计/支付模块思路:

可缺省,不可乱猜。

七、对实施顾问和开发最重要的边界:显示逻辑与国家适配是两回事

很多人会把“发票上显示二维码”理解成单一配置问题,但这里至少有两层:

1)显示层

account.move 侧判断当前发票是否应该展示 QR。

2)标准层

res.partner.bankaccount_qr_code_emv 判断当前账户、国家、币种、配置能不能生成符合规范的 EMV 内容。

所以你在项目里排查时,不该只问:

  • 为什么模板没显示?

还该问:

  • 当前银行账户属于哪个国家?
  • 有没有对应国家扩展实现 merchant account info?
  • 城市、引用、代理字段是否满足规则?
  • 发票 residual、币种、partner bank 是否完整?

八、新手最容易误解的 4 件事

1)二维码就是报表图片

不对。图片只是最后一步,前面还有方法选择、资格校验、数据组包。

2)装了 account_qr_code_emv 就所有国家都能扫

不对。这个模块本身更像 EMV 框架层,很多国家规则需要继承扩展补充。

3)发票自己决定二维码长什么样

不对。真正的编码逻辑主要在银行账户模型及其扩展上。

4)只要银行账号有值就能生成

不对。商户城市、代理信息、国家匹配、币种映射、引用格式都可能成为拦截条件。

总结

Odoo 发票二维码的实现思路,不是“模板插图”,而是:

  • 发票先决定要不要展示;
  • 银行账户框架再挑一个符合条件的 QR 方法;
  • account_qr_code_emv 把支付信息编码成 EMV TLV 字符串并附上 CRC16;
  • 最后才由报表/条码引擎渲染成二维码。

如果只记一句,可以记这句:

在 Odoo 里,付款二维码不是 UI 功能,而是“发票 → 银行账户 → 国家标准 → 条码渲染”的编码链路。

DISCUSSION

评论区

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