[IMP] l10n_th: add PromptPay QR code payment for invoice

Impacted Version:

- 17.0 and above

This commit improve below features:

- Add PromptPay QR code payment for invoice based on below github repository
https://github.com/kittinan/thai-qr-payment

task-3165449

closes odoo/odoo#133304

Signed-off-by: Nicolas Viseur (vin) <vin@odoo.com>
This commit is contained in:
tong-odoo
2023-09-07 05:55:59 +00:00
parent f893eef973
commit 1b7bb665e9
5 changed files with 140 additions and 1 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ Thai accounting chart and localization.
'author': 'Almacom (http://almacom.co.th/)',
'website': 'https://www.odoo.com/documentation/master/applications/finance/fiscal_localizations.html',
'depends': [
'account',
'account_qr_code_emv',
],
'data': [
'data/account_tax_report_data.xml',
+1
View File
@@ -3,3 +3,4 @@ from . import template_th
from . import res_partner
from . import account_move
from . import ir_actions_report
from . import res_bank
+61
View File
@@ -0,0 +1,61 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import re
from odoo import _, api, fields, models
from odoo.exceptions import ValidationError
class ResPartnerBank(models.Model):
_inherit = 'res.partner.bank'
proxy_type = fields.Selection(selection_add=[('ewallet_id', 'Ewallet ID'),
('merchant_tax_id', 'Merchant Tax ID'),
('mobile', "Mobile Number")],
ondelete={'ewallet_id': 'set default', 'merchant_tax_id': 'set default', 'mobile': 'set default'})
@api.constrains('proxy_type', 'proxy_value', 'partner_id')
def _check_th_proxy(self):
tax_id_re = re.compile(r'^[0-9]{13}$')
mobile_re = re.compile(r'^[0-9]{10}$')
for bank in self.filtered(lambda b: b.country_code == 'TH'):
if bank.proxy_type not in ['ewallet_id', 'merchant_tax_id', 'mobile', 'none', False]:
raise ValidationError(_("The QR Code Type must be either Ewallet ID, Merchant Tax ID or Mobile Number to generate a Thailand Bank QR code for account number %s.", bank.acc_number))
if bank.proxy_type == 'merchant_tax_id' and (not bank.proxy_value or not tax_id_re.match(bank.proxy_value)):
raise ValidationError(_("The Merchant Tax ID must be in the format 1234567890123 for account number %s.", bank.acc_number))
if bank.proxy_type == 'mobile' and (not bank.proxy_value or not mobile_re.match(bank.proxy_value)):
raise ValidationError(_("The Mobile Number must be in the format 0812345678 for account number %s.", bank.acc_number))
@api.depends('country_code')
def _compute_display_qr_setting(self):
bank_th = self.filtered(lambda b: b.country_code == 'TH')
bank_th.display_qr_setting = self.env.company.qr_code
super(ResPartnerBank, self - bank_th)._compute_display_qr_setting()
def _get_merchant_account_info(self):
if self.country_code == 'TH':
proxy_type_mapping = {
'mobile': 1,
'merchant_tax_id': 2,
'ewallet_id': 3,
}
proxy_value = re.sub(r"^0", "66", self.proxy_value).zfill(13) if self.proxy_type == 'mobile' else self.proxy_value
vals = [
(0, 'A000000677010111'),
(proxy_type_mapping[self.proxy_type], proxy_value),
]
return (29, ''.join([self._serialize(*val) for val in vals]))
return super()._get_merchant_account_info()
def _get_error_messages_for_qr(self, qr_method, debtor_partner, currency):
if qr_method == 'emv_qr' and self.country_code == 'TH':
if currency.name not in ['THB']:
return _("Can't generate a PayNow QR code with a currency other than THB.")
return None
return super()._get_error_messages_for_qr(qr_method, debtor_partner, currency)
def _check_for_qr_code_errors(self, qr_method, amount, currency, debtor_partner, free_communication, structured_communication):
if qr_method == 'emv_qr' and self.country_code == 'TH' and self.proxy_type not in ['ewallet_id', 'merchant_tax_id', 'mobile']:
return _("The PayNow Type must be either Ewallet ID, Merchant Tax ID or Mobile Number to generate a Thailand Bank QR code")
return super()._check_for_qr_code_errors(qr_method, amount, currency, debtor_partner, free_communication, structured_communication)
+1
View File
@@ -0,0 +1 @@
from . import test_l10n_th_emv_qr
@@ -0,0 +1,76 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.exceptions import UserError
from odoo.fields import Command
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
from odoo.tests import tagged
@tagged('post_install_l10n', 'post_install', '-at_install')
class TestL10nTHEmvQrCode(AccountTestInvoicingCommon):
""" Test the generation of the EMV QR Code on invoices """
@classmethod
def setUpClass(cls, chart_template_ref='th'):
super().setUpClass(chart_template_ref=chart_template_ref)
cls.company_data['company'].qr_code = True
cls.company_data['company'].partner_id.update({
'country_id': cls.env.ref('base.th').id,
'city': 'Thailand',
})
cls.acc_emv_th = cls.env['res.partner.bank'].create({
'acc_number': '123456789012345678',
'partner_id': cls.company_data['company'].partner_id.id,
'proxy_type': 'mobile',
'proxy_value': '0810704060',
})
cls.acc_emv_th_without_promptpay_info = cls.env['res.partner.bank'].create({
'acc_number': '1234567890',
'partner_id': cls.company_data['company'].partner_id.id,
})
cls.emv_qr_invoice = cls.env['account.move'].create({
'move_type': 'out_invoice',
'partner_id': cls.partner_a.id,
'currency_id': cls.env.ref('base.THB').id,
'partner_bank_id': cls.acc_emv_th.id,
'company_id': cls.company_data['company'].id,
'invoice_line_ids': [Command.create({'quantity': 1, 'price_unit': 100})],
})
def test_emv_qr_code_generation(self):
self.emv_qr_invoice.qr_code_method = 'emv_qr'
self.emv_qr_invoice._generate_qr_code()
# Using invoice currency other than THB should fail
self.emv_qr_invoice.currency_id = self.env.ref('base.USD')
with self.assertRaises(UserError, msg="The chosen QR-code type is not eligible for this invoice."):
self.emv_qr_invoice._generate_qr_code()
# Without company partner city should fail
self.emv_qr_invoice.currency_id = self.env.ref('base.THB')
self.company_data['company'].partner_id.city = False
with self.assertRaises(UserError, msg="Missing Merchant City."):
self.emv_qr_invoice._generate_qr_code()
# Without PromptPay infomation should fail
self.company_data['company'].partner_id.city = 'Thailand'
self.emv_qr_invoice.partner_bank_id = self.acc_emv_th_without_promptpay_info
with self.assertRaises(UserError, msg="The account receiving the payment must have a Proxy type and a Proxy value set."):
self.emv_qr_invoice._generate_qr_code()
def test_emv_qr_vals(self):
self.emv_qr_invoice.qr_code_method = 'emv_qr'
unstruct_ref = 'INV/TEST/0001'
emv_qr_vals = self.emv_qr_invoice.partner_bank_id._get_qr_vals(
qr_method=self.emv_qr_invoice.qr_code_method,
amount=self.emv_qr_invoice.amount_residual,
currency=self.emv_qr_invoice.currency_id,
debtor_partner=self.emv_qr_invoice.partner_id,
free_communication=unstruct_ref,
structured_communication=self.emv_qr_invoice.payment_reference,
)
# Check the whole qr code string
self.assertEqual(emv_qr_vals, '00020101021229370016A0000006770101110113006681070406052040000530376454031005802TH5914company_1_data6008Thailand63048B76')