diff --git a/addons/account_qr_code_emv/__init__.py b/addons/account_qr_code_emv/__init__.py new file mode 100644 index 00000000000..d6210b1285d --- /dev/null +++ b/addons/account_qr_code_emv/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import models diff --git a/addons/account_qr_code_emv/__manifest__.py b/addons/account_qr_code_emv/__manifest__.py new file mode 100644 index 00000000000..4fc1201e551 --- /dev/null +++ b/addons/account_qr_code_emv/__manifest__.py @@ -0,0 +1,13 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'account_qr_code_emv', + 'category': 'Accounting/Payment', + 'version': '1.0', + 'description': """ + Bridge module addings support for EMV Merchant-Presented QR-code generation for Payment System. + """, + 'author': 'Odoo SA', + 'depends': ['account'], + 'license': 'LGPL-3', +} diff --git a/addons/account_qr_code_emv/const.py b/addons/account_qr_code_emv/const.py new file mode 100644 index 00000000000..07d7534e078 --- /dev/null +++ b/addons/account_qr_code_emv/const.py @@ -0,0 +1,51 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +CURRENCY_MAPPING = { + 'ARS': '032', + 'AUD': '036', + 'BHD': '048', + 'KHR': '116', + 'CAD': '124', + 'LKR': '144', + 'CNY': '156', + 'HRK': '191', + 'CZK': '203', + 'DKK': '208', + 'HKD': '344', + 'HUF': '348', + 'ISK': '352', + 'INR': '356', + 'ILS': '376', + 'JPY': '392', + 'KRW': '410', + 'KWD': '414', + 'MYR': '458', + 'MUR': '480', + 'MXN': '484', + 'NPR': '524', + 'NZD': '554', + 'NOK': '578', + 'QAR': '634', + 'RUB': '643', + 'SAR': '682', + 'SGD': '702', + 'ZAR': '710', + 'SEK': '752', + 'CHF': '756', + 'THB': '764', + 'AED': '784', + 'TND': '788', + 'GBP': '826', + 'USD': '840', + 'TWD': '901', + 'RSD': '941', + 'RON': '946', + 'TRY': '949', + 'XOF': '952', + 'XPF': '953', + 'BGN': '975', + 'EUR': '978', + 'UAH': '980', + 'PLN': '985', + 'BRL': '986', +} diff --git a/addons/account_qr_code_emv/models/__init__.py b/addons/account_qr_code_emv/models/__init__.py new file mode 100644 index 00000000000..7cad87a0e7d --- /dev/null +++ b/addons/account_qr_code_emv/models/__init__.py @@ -0,0 +1,3 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import res_bank diff --git a/addons/account_qr_code_emv/models/res_bank.py b/addons/account_qr_code_emv/models/res_bank.py new file mode 100644 index 00000000000..e1c42729e6b --- /dev/null +++ b/addons/account_qr_code_emv/models/res_bank.py @@ -0,0 +1,73 @@ +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import _, api, models + +from odoo.addons.account_qr_code_emv.const import CURRENCY_MAPPING + + +class ResPartnerBank(models.Model): + _inherit = 'res.partner.bank' + + # CRC16 calculation with polynomial 0x1021 and initial value 0xFFFF + def _get_crc16(self, data, poly=0x1021, init=0xFFFF): + crc = init + for byte in data: + crc = crc ^ (byte << 8) + for __ in range(8): + if crc & 0x8000: + crc = (crc << 1) ^ poly + else: + crc = crc << 1 + return crc & 0xFFFF + + def _get_merchant_account_info(self): + return False + + def _get_qr_vals(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + if qr_method == 'emv_qr': + merchant_account_info = self._get_merchant_account_info() + currency_code = CURRENCY_MAPPING[currency.name] + merchant_name = self.partner_id.name and self.partner_id.name[:25] or 'NA' + merchant_city = self.partner_id.city and self.partner_id.city[:15] or '' + + qr_code_vals = [ + '000201', # Payload Format Indicator + '010212', # Dynamic QR Codes + f'26{len(merchant_account_info):02}{merchant_account_info}', # Merchant Account Information + '52040000', # Merchant Category Code + f'5303{currency_code}', # Transaction Currency + f'54{len(str(amount)):02}{amount}', # Transaction Amount + f'5802{self.partner_id.country_code}', # Country Code + f'59{len(merchant_name):02}{merchant_name}', # Merchant Name + f'60{len(merchant_city):02}{merchant_city}', # Merchant City + '6304', # CRC16 + ] + crc = self._get_crc16(bytes(''.join(qr_code_vals), 'utf-8')) + qr_code_vals.append(format(crc, '04x').upper()) + return qr_code_vals + return super()._get_qr_vals(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) + + def _get_qr_code_generation_params(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + if qr_method == 'emv_qr': + return { + 'barcode_type': 'QR', + 'width': 128, + 'height': 128, + 'humanreadable': 1, + 'value': ''.join(self._get_qr_vals(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)), + } + return super()._get_qr_code_generation_params(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) + + def _check_for_qr_code_errors(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication): + if qr_method == 'emv_qr': + if not self._get_merchant_account_info(): + return _("Missing Merchant Account Information.") + if not self.partner_id.city: + return _("Missing Merchant City.") + return super()._check_for_qr_code_errors(qr_method, amount, currency, debtor_partner, free_communication, structured_communication) + + @api.model + def _get_available_qr_methods(self): + rslt = super()._get_available_qr_methods() + rslt.append(('emv_qr', _("EMV Merchant-Presented QR-code"), 30)) + return rslt