From bcf594e92b02a2cf26ab3f6d0bb3513c8b00beab Mon Sep 17 00:00:00 2001 From: sbejaoui Date: Wed, 2 Jun 2021 11:05:18 +0200 Subject: [PATCH 01/11] [ADD] - add new module Account Payment Terminal --- account_payment_terminal/__init__.py | 2 + account_payment_terminal/__manifest__.py | 21 +++ account_payment_terminal/models/__init__.py | 3 + .../models/account_journal.py | 18 ++ .../models/account_payment_terminal.py | 26 +++ .../models/oca_payment_terminal_form_mixin.py | 72 ++++++++ account_payment_terminal/readme/CONFIGURE.rst | 12 ++ .../readme/CONTRIBUTORS.rst | 1 + .../readme/DESCRIPTION.rst | 1 + account_payment_terminal/readme/USAGE.rst | 5 + .../security/account_payment_terminal.xml | 23 +++ .../payment_terminal_form_controller.js | 160 ++++++++++++++++++ .../payment_terminal_form_view.js | 31 ++++ .../views/account_journal.xml | 14 ++ .../views/account_payment_terminal.xml | 46 +++++ .../views/oca_payment_terminal_form_mixin.xml | 17 ++ .../views/webclient_templates.xml | 15 ++ account_payment_terminal/wizards/__init__.py | 1 + .../wizards/account_payment_register.py | 41 +++++ .../wizards/account_payment_register.xml | 45 +++++ 20 files changed, 554 insertions(+) create mode 100644 account_payment_terminal/__init__.py create mode 100644 account_payment_terminal/__manifest__.py create mode 100644 account_payment_terminal/models/__init__.py create mode 100644 account_payment_terminal/models/account_journal.py create mode 100644 account_payment_terminal/models/account_payment_terminal.py create mode 100644 account_payment_terminal/models/oca_payment_terminal_form_mixin.py create mode 100644 account_payment_terminal/readme/CONFIGURE.rst create mode 100644 account_payment_terminal/readme/CONTRIBUTORS.rst create mode 100644 account_payment_terminal/readme/DESCRIPTION.rst create mode 100644 account_payment_terminal/readme/USAGE.rst create mode 100644 account_payment_terminal/security/account_payment_terminal.xml create mode 100644 account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_controller.js create mode 100644 account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_view.js create mode 100644 account_payment_terminal/views/account_journal.xml create mode 100644 account_payment_terminal/views/account_payment_terminal.xml create mode 100644 account_payment_terminal/views/oca_payment_terminal_form_mixin.xml create mode 100644 account_payment_terminal/views/webclient_templates.xml create mode 100644 account_payment_terminal/wizards/__init__.py create mode 100644 account_payment_terminal/wizards/account_payment_register.py create mode 100644 account_payment_terminal/wizards/account_payment_register.xml diff --git a/account_payment_terminal/__init__.py b/account_payment_terminal/__init__.py new file mode 100644 index 000000000000..aee8895e7a31 --- /dev/null +++ b/account_payment_terminal/__init__.py @@ -0,0 +1,2 @@ +from . import models +from . import wizards diff --git a/account_payment_terminal/__manifest__.py b/account_payment_terminal/__manifest__.py new file mode 100644 index 000000000000..5d2fc3110878 --- /dev/null +++ b/account_payment_terminal/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +{ + "name": "Account Payment Terminal", + "summary": """This addon allows to pay invoices using payment terminal""", + "version": "14.0.1.0.0", + "license": "AGPL-3", + "author": "ACSONE SA/NV, Odoo Community Association (OCA)", + "maintainers": ["sbejaoui"], + "website": "https://github.com/OCA/account-payment", + "depends": ["account"], + "data": [ + "security/account_payment_terminal.xml", + "views/webclient_templates.xml", + "views/account_payment_terminal.xml", + "views/account_journal.xml", + "views/oca_payment_terminal_form_mixin.xml", + "wizards/account_payment_register.xml", + ], +} diff --git a/account_payment_terminal/models/__init__.py b/account_payment_terminal/models/__init__.py new file mode 100644 index 000000000000..171db14cd9c2 --- /dev/null +++ b/account_payment_terminal/models/__init__.py @@ -0,0 +1,3 @@ +from . import account_journal +from . import account_payment_terminal +from . import oca_payment_terminal_form_mixin diff --git a/account_payment_terminal/models/account_journal.py b/account_payment_terminal/models/account_journal.py new file mode 100644 index 000000000000..a85a008bd281 --- /dev/null +++ b/account_payment_terminal/models/account_journal.py @@ -0,0 +1,18 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, fields, models + + +class AccountJournal(models.Model): + + _inherit = "account.journal" + + use_payment_terminal = fields.Selection( + selection=lambda self: self._get_payment_terminal_selection(), + string="Use a Payment Terminal", + help="Record payments with a terminal on this journal.", + ) + + def _get_payment_terminal_selection(self): + return [("oca_payment_terminal", _("OCA Payment Terminal"))] diff --git a/account_payment_terminal/models/account_payment_terminal.py b/account_payment_terminal/models/account_payment_terminal.py new file mode 100644 index 000000000000..7aab0b4f275e --- /dev/null +++ b/account_payment_terminal/models/account_payment_terminal.py @@ -0,0 +1,26 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class AccountPaymentTerminal(models.Model): + + _name = "account.payment.terminal" + _description = "Account Payment Terminal" + _inherit = "mail.thread" + + name = fields.Char(required=True) + proxy_ip = fields.Char( + string="IP Address", + size=45, + help="The hostname or ip address of the hardware proxy", + required=True, + ) + oca_payment_terminal_id = fields.Char( + string="Terminal identifier", + help=( + "The identifier of the terminal as known by the hardware proxy. " + "Leave empty if the proxy has only one terminal connected." + ), + ) diff --git a/account_payment_terminal/models/oca_payment_terminal_form_mixin.py b/account_payment_terminal/models/oca_payment_terminal_form_mixin.py new file mode 100644 index 000000000000..f66decca20c9 --- /dev/null +++ b/account_payment_terminal/models/oca_payment_terminal_form_mixin.py @@ -0,0 +1,72 @@ +# Copyright 2021 ACSONE SA/NV +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class OcaPaymentTerminalFormMixin(models.AbstractModel): + + _name = "oca.payment.terminal.form.mixin" + _description = "OCA Payment Terminal Form Mixin" + + account_payment_terminal_id = fields.Many2one( + comodel_name="account.payment.terminal", string="Payment Terminal" + ) + + def action_payment_terminal_transaction_start(self): + self.ensure_one() + if not hasattr(self, "journal_id"): + # the object must be linked to journal in order to get the payment + # terminal type + return {} + if ( + self.journal_id.use_payment_terminal + and self.account_payment_terminal_id + and hasattr( + self, self._get_payment_terminal_transaction_start_method_name() + ) + ): + return getattr( + self, self._get_payment_terminal_transaction_start_method_name() + )() + raise UserError( + _( + "Payment terminal is not correctly configured. " + "Please contact your administrator." + ) + ) + + def _get_payment_terminal_transaction_start_method_name(self): + return "_%s_transaction_start" % self.journal_id.use_payment_terminal or "" + + def _oca_payment_terminal_transaction_start(self): + self.ensure_one() + view_id = self._get_payment_terminal_form_view_id() + return { + "name": _("Payment Terminal"), + "type": "ir.actions.act_window", + "view_mode": "oca_payment_terminal_form", + "res_model": self._name, + "res_id": self.id, + "target": "new", + "view_id": view_id, + "context": self.env.context, + } + + @api.model + def _get_payment_terminal_form_view_id(self): + return self.env.ref( + "account_payment_terminal.oca_payment_terminal_form_mixin_form_view" + ).id + + # To define in the heir model + def get_payment_info(self): + return { + "proxy_ip": self.account_payment_terminal_id.proxy_ip, + "oca_payment_terminal_id": self.account_payment_terminal_id.oca_payment_terminal_id, + "payment_mode": "card", # TODO: Add check mode? + } + + def action_confirm_payment(self, payment_reference): + pass diff --git a/account_payment_terminal/readme/CONFIGURE.rst b/account_payment_terminal/readme/CONFIGURE.rst new file mode 100644 index 000000000000..a486700f63e2 --- /dev/null +++ b/account_payment_terminal/readme/CONFIGURE.rst @@ -0,0 +1,12 @@ +To configure a bank journal as allowed to use payment terminals: + +#. Go to the menu Invoicing > Configuration > Journals. +#. Select or create a new bank journal. +#. Set the field 'Use a Payment Terminal' to your terminal type. + +Now you should create the payment terminals: + +#. Go to the menu Invoicing > Configuration > Payment terminals. +#. Create a new terminal by setting: + #. The hostname or ip address of the hardware proxy. + #. The identifier of the terminal as known by the hardware proxy (Leave empty if the proxy has only one terminal connected). diff --git a/account_payment_terminal/readme/CONTRIBUTORS.rst b/account_payment_terminal/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..50e6298db563 --- /dev/null +++ b/account_payment_terminal/readme/CONTRIBUTORS.rst @@ -0,0 +1 @@ +* Souheil Bejaoui diff --git a/account_payment_terminal/readme/DESCRIPTION.rst b/account_payment_terminal/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..2018a1a86d50 --- /dev/null +++ b/account_payment_terminal/readme/DESCRIPTION.rst @@ -0,0 +1 @@ +This addon allows to pay invoices using payment terminal. diff --git a/account_payment_terminal/readme/USAGE.rst b/account_payment_terminal/readme/USAGE.rst new file mode 100644 index 000000000000..befbf382c79f --- /dev/null +++ b/account_payment_terminal/readme/USAGE.rst @@ -0,0 +1,5 @@ +#. Go to Invoicing > Customers > Invoices and select an open invoice. +#. Register a payment. +#. Select the bank journal configured to use payment terminals. +#. Select a terminal. +#. Confirm. diff --git a/account_payment_terminal/security/account_payment_terminal.xml b/account_payment_terminal/security/account_payment_terminal.xml new file mode 100644 index 000000000000..efa33bfe9690 --- /dev/null +++ b/account_payment_terminal/security/account_payment_terminal.xml @@ -0,0 +1,23 @@ + + + + + account.payment.terminal access user + + + + + + + + + account.payment.terminal access manager + + + + + + + + diff --git a/account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_controller.js b/account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_controller.js new file mode 100644 index 000000000000..f5827978c7cd --- /dev/null +++ b/account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_controller.js @@ -0,0 +1,160 @@ +odoo.define("account_payment_terminal.OCAPaymentTerminalFormController", function ( + require +) { + "use strict"; + + var rpc = require("web.rpc"); + var FormController = require("web.FormController"); + var core = require("web.core"); + + var _lt = core._lt; + + var OCAPaymentTerminalFormController = FormController.extend({ + init: function () { + this.transaction_id = null; + this.oca_payment_terminal_id = null; + this.proxy_url = null; + this._super.apply(this, arguments); + }, + _show_error: function (error_message) { + this.$el.find("p").text(error_message); + this.$el.find("p").css("color", "red"); + console.error(error_message); + }, + _show_success: function (success_message) { + this.$el.find("p").text(success_message); + this.$el.find("p").css("color", "green"); + }, + _proxy_call: function (service, data, timeout) { + return $.ajax({ + url: this.proxy_url + "/hw_proxy/" + service, + dataType: "json", + contentType: "application/json;charset=utf-8", + data: JSON.stringify(data), + method: "POST", + timeout: timeout, + }); + }, + _get_payment_info: function () { + return rpc.query({ + model: this.model.loadParams.modelName, + method: "get_payment_info", + args: [[this.model.loadParams.res_id]], + }); + }, + _confirm_payment: function (transaction) { + return rpc.query({ + model: this.model.loadParams.modelName, + method: "action_confirm_payment", + args: [[this.model.loadParams.res_id], transaction.reference], + }); + }, + _get_payment_terminal_transaction_start_data: function (payment_info) { + this.proxy_url = payment_info.proxy_ip; + var cid = Math.floor(Math.random() * 1000 * 1000 * 1000); + var params = { + payment_info: JSON.stringify(payment_info), + }; + return { + jsonrpc: "2.0", + method: "call", + params: params, + id: cid, + }; + }, + _set_transaction_status: function (transaction, timerId) { + var self = this; + if (transaction.success) { + clearInterval(timerId); + self._confirm_payment(transaction) + .then(function () { + self._show_success(_lt("Payment Successful")); + }) + .catch(() => { + self._show_error( + _lt("Payment Successful but couldn't confirm it in odoo") + ); + }); + } else if (transaction.success === false) { + clearInterval(timerId); + self._show_error(_lt("Transaction cancelled")); + } + }, + _poll_for_transaction_status: function () { + var self = this; + var timerId = setInterval(() => { + var status_params = {}; + if (self.oca_payment_terminal_id) { + status_params.terminal_id = self.oca_payment_terminal_id; + } + this._proxy_call("status_json", {params: status_params}, 1000) + .done((drivers_status) => { + if ("result" in drivers_status) { + var d_status = drivers_status.result; + for (var driver_name in d_status) { + var driver = d_status[driver_name]; + if ( + !driver.is_terminal || + !("transactions" in driver) + ) { + continue; + } + for (var transaction_id in driver.transactions) { + var transaction = + driver.transactions[transaction_id]; + if ( + transaction.transaction_id === + self.transaction_id + ) { + self._set_transaction_status( + transaction, + timerId + ); + } + } + } + } + }) + .fail(() => { + self._show_error(_lt("Error querying terminal driver status")); + clearInterval(timerId); + }); + }, 1000); + }, + start: function () { + var self = this; + return this._super.apply(this, arguments).then(function () { + return self._get_payment_info().then(function (payment_info) { + self.oca_payment_terminal_id = payment_info.oca_payment_terminal_id; + var data = self._get_payment_terminal_transaction_start_data( + payment_info + ); + self._proxy_call("payment_terminal_transaction_start", data, 10000) + .done(function (response) { + if (response === false) { + self._show_error( + _lt( + "Failed to send the amount to pay to the payment terminal. Press the red button on the payment terminal and try again." + ) + ); + return false; + } else if ( + response instanceof Object && + "result" in response && + "transaction_id" in response.result + ) { + self.transaction_id = response.result.transaction_id; + self._poll_for_transaction_status(); + } + }) + .fail(function () { + self._show_error(_lt("Error starting payment transaction")); + return false; + }); + }); + }); + }, + }); + + return OCAPaymentTerminalFormController; +}); diff --git a/account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_view.js b/account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_view.js new file mode 100644 index 000000000000..31c74f4643bc --- /dev/null +++ b/account_payment_terminal/static/src/js/views/payment_terminal_form/payment_terminal_form_view.js @@ -0,0 +1,31 @@ +odoo.define("account_payment_terminal.OCAPaymentTerminalFormView", function (require) { + "use strict"; + + var core = require("web.core"); + var BasicView = require("web.BasicView"); + var FormView = require("web.FormView"); + var viewRegistry = require("web.view_registry"); + var FormRenderer = require("web.FormRenderer"); + var OCAPaymentTerminalFormController = require("account_payment_terminal.OCAPaymentTerminalFormController"); + + var _lt = core._lt; + + var OCAPaymentTerminalFormView = FormView.extend({ + config: _.extend({}, BasicView.prototype.config, { + Renderer: FormRenderer, + Controller: OCAPaymentTerminalFormController, + }), + display_name: _lt("Payment Terminal Form"), + icon: "fa-edit", + multi_record: false, + withSearchBar: false, + searchMenuTypes: [], + viewType: "oca_payment_terminal_form", + init: function () { + this._super.apply(this, arguments); + }, + }); + + viewRegistry.add("oca_payment_terminal_form", OCAPaymentTerminalFormView); + return OCAPaymentTerminalFormView; +}); diff --git a/account_payment_terminal/views/account_journal.xml b/account_payment_terminal/views/account_journal.xml new file mode 100644 index 000000000000..83f76d175c52 --- /dev/null +++ b/account_payment_terminal/views/account_journal.xml @@ -0,0 +1,14 @@ + + + + + account.journal + + + + + + + + diff --git a/account_payment_terminal/views/account_payment_terminal.xml b/account_payment_terminal/views/account_payment_terminal.xml new file mode 100644 index 000000000000..d8468ca459bf --- /dev/null +++ b/account_payment_terminal/views/account_payment_terminal.xml @@ -0,0 +1,46 @@ + + + + + account.payment.terminal + +
+ + + + + + + +
+ + +
+
+
+
+ + account.payment.terminal + + + + + + + + + + Payment Terminals + account.payment.terminal + tree,form + [] + {} + + + Payment Terminals + + + + +
diff --git a/account_payment_terminal/views/oca_payment_terminal_form_mixin.xml b/account_payment_terminal/views/oca_payment_terminal_form_mixin.xml new file mode 100644 index 000000000000..508a901b961a --- /dev/null +++ b/account_payment_terminal/views/oca_payment_terminal_form_mixin.xml @@ -0,0 +1,17 @@ + + + + + oca.payment.terminal.form.mixin + 9999 + +
+ +

Please check the payment terminal

+
+