diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/payment/wizards | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/payment/wizards')
| -rw-r--r-- | addons/payment/wizards/__init__.py | 5 | ||||
| -rw-r--r-- | addons/payment/wizards/payment_acquirer_onboarding_wizard.py | 161 | ||||
| -rw-r--r-- | addons/payment/wizards/payment_link_wizard.py | 94 | ||||
| -rw-r--r-- | addons/payment/wizards/payment_link_wizard_views.xml | 43 |
4 files changed, 303 insertions, 0 deletions
diff --git a/addons/payment/wizards/__init__.py b/addons/payment/wizards/__init__.py new file mode 100644 index 00000000..938cc12d --- /dev/null +++ b/addons/payment/wizards/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import payment_acquirer_onboarding_wizard +from . import payment_link_wizard diff --git a/addons/payment/wizards/payment_acquirer_onboarding_wizard.py b/addons/payment/wizards/payment_acquirer_onboarding_wizard.py new file mode 100644 index 00000000..24b6696a --- /dev/null +++ b/addons/payment/wizards/payment_acquirer_onboarding_wizard.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import api, models, fields, _ +from odoo.exceptions import UserError + + +class PaymentWizard(models.TransientModel): + _name = 'payment.acquirer.onboarding.wizard' + _description = 'Payment acquire onboarding wizard' + + payment_method = fields.Selection([ + ('paypal', "PayPal"), + ('stripe', "Credit card (via Stripe)"), + ('other', "Other payment acquirer"), + ('manual', "Custom payment instructions"), + ], string="Payment Method", default=lambda self: self._get_default_payment_acquirer_onboarding_value('payment_method')) + + paypal_user_type = fields.Selection([ + ('new_user', "I don't have a Paypal account"), + ('existing_user', 'I have a Paypal account')], string="Paypal User Type", default='new_user') + paypal_email_account = fields.Char("Email", default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_email_account')) + paypal_seller_account = fields.Char("Merchant Account ID", default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_seller_account')) + paypal_pdt_token = fields.Char("PDT Identity Token", default=lambda self: self._get_default_payment_acquirer_onboarding_value('paypal_pdt_token')) + + stripe_secret_key = fields.Char(default=lambda self: self._get_default_payment_acquirer_onboarding_value('stripe_secret_key')) + stripe_publishable_key = fields.Char(default=lambda self: self._get_default_payment_acquirer_onboarding_value('stripe_publishable_key')) + + manual_name = fields.Char("Method", default=lambda self: self._get_default_payment_acquirer_onboarding_value('manual_name')) + journal_name = fields.Char("Bank Name", default=lambda self: self._get_default_payment_acquirer_onboarding_value('journal_name')) + acc_number = fields.Char("Account Number", default=lambda self: self._get_default_payment_acquirer_onboarding_value('acc_number')) + manual_post_msg = fields.Html("Payment Instructions") + + _data_fetched = fields.Boolean(store=False) + + @api.onchange('journal_name', 'acc_number') + def _set_manual_post_msg_value(self): + self.manual_post_msg = _( + '<h3>Please make a payment to: </h3><ul><li>Bank: %s</li><li>Account Number: %s</li><li>Account Holder: %s</li></ul>', + self.journal_name or _("Bank"), + self.acc_number or _("Account"), + self.env.company.name + ) + + _payment_acquirer_onboarding_cache = {} + + def _get_manual_payment_acquirer(self, env=None): + if env is None: + env = self.env + module_id = env.ref('base.module_payment_transfer').id + return env['payment.acquirer'].search([('module_id', '=', module_id), + ('company_id', '=', env.company.id)], limit=1) + + def _get_default_payment_acquirer_onboarding_value(self, key): + if not self.env.is_admin(): + raise UserError(_("Only administrators can access this data.")) + + if self._data_fetched: + return self._payment_acquirer_onboarding_cache.get(key, '') + + self._data_fetched = True + + self._payment_acquirer_onboarding_cache['payment_method'] = self.env.company.payment_onboarding_payment_method + + installed_modules = self.env['ir.module.module'].sudo().search([ + ('name', 'in', ('payment_paypal', 'payment_stripe')), + ('state', '=', 'installed'), + ]).mapped('name') + + if 'payment_paypal' in installed_modules: + acquirer = self.env.ref('payment.payment_acquirer_paypal') + self._payment_acquirer_onboarding_cache['paypal_email_account'] = acquirer['paypal_email_account'] or self.env.user.email or '' + self._payment_acquirer_onboarding_cache['paypal_seller_account'] = acquirer['paypal_seller_account'] + self._payment_acquirer_onboarding_cache['paypal_pdt_token'] = acquirer['paypal_pdt_token'] + + if 'payment_stripe' in installed_modules: + acquirer = self.env.ref('payment.payment_acquirer_stripe') + self._payment_acquirer_onboarding_cache['stripe_secret_key'] = acquirer['stripe_secret_key'] + self._payment_acquirer_onboarding_cache['stripe_publishable_key'] = acquirer['stripe_publishable_key'] + + manual_payment = self._get_manual_payment_acquirer() + journal = manual_payment.journal_id + + self._payment_acquirer_onboarding_cache['manual_name'] = manual_payment['name'] + self._payment_acquirer_onboarding_cache['manual_post_msg'] = manual_payment['pending_msg'] + self._payment_acquirer_onboarding_cache['journal_name'] = journal.name if journal.name != "Bank" else "" + self._payment_acquirer_onboarding_cache['acc_number'] = journal.bank_acc_number + + return self._payment_acquirer_onboarding_cache.get(key, '') + + def _install_module(self, module_name): + module = self.env['ir.module.module'].sudo().search([('name', '=', module_name)]) + if module.state not in ('installed', 'to install', 'to upgrade'): + module.button_immediate_install() + + def _on_save_payment_acquirer(self): + self._install_module('account_payment') + + def add_payment_methods(self): + """ Install required payment acquiers, configure them and mark the + onboarding step as done.""" + + if self.payment_method == 'paypal': + self._install_module('payment_paypal') + + if self.payment_method == 'stripe': + self._install_module('payment_stripe') + + if self.payment_method in ('paypal', 'stripe', 'manual', 'other'): + + self._on_save_payment_acquirer() + + self.env.company.payment_onboarding_payment_method = self.payment_method + + # create a new env including the freshly installed module(s) + new_env = api.Environment(self.env.cr, self.env.uid, self.env.context) + + if self.payment_method == 'paypal': + new_env.ref('payment.payment_acquirer_paypal').write({ + 'paypal_email_account': self.paypal_email_account, + 'paypal_seller_account': self.paypal_seller_account, + 'paypal_pdt_token': self.paypal_pdt_token, + 'state': 'enabled', + }) + if self.payment_method == 'stripe': + new_env.ref('payment.payment_acquirer_stripe').write({ + 'stripe_secret_key': self.stripe_secret_key, + 'stripe_publishable_key': self.stripe_publishable_key, + 'state': 'enabled', + }) + if self.payment_method == 'manual': + manual_acquirer = self._get_manual_payment_acquirer(new_env) + if not manual_acquirer: + raise UserError(_( + 'No manual payment method could be found for this company. ' + 'Please create one from the Payment Acquirer menu.' + )) + manual_acquirer.name = self.manual_name + manual_acquirer.pending_msg = self.manual_post_msg + manual_acquirer.state = 'enabled' + + journal = manual_acquirer.journal_id + if journal: + journal.name = self.journal_name + journal.bank_acc_number = self.acc_number + else: + raise UserError(_("You have to set a journal for your payment acquirer %s.", self.manual_name)) + + # delete wizard data immediately to get rid of residual credentials + self.sudo().unlink() + # the user clicked `apply` and not cancel so we can assume this step is done. + self._set_payment_acquirer_onboarding_step_done() + return {'type': 'ir.actions.act_window_close'} + + def _set_payment_acquirer_onboarding_step_done(self): + self.env.company.sudo().set_onboarding_step_done('payment_acquirer_onboarding_state') + + def action_onboarding_other_payment_acquirer(self): + self._set_payment_acquirer_onboarding_step_done() + action = self.env["ir.actions.actions"]._for_xml_id("payment.action_payment_acquirer") + return action diff --git a/addons/payment/wizards/payment_link_wizard.py b/addons/payment/wizards/payment_link_wizard.py new file mode 100644 index 00000000..54c03c5d --- /dev/null +++ b/addons/payment/wizards/payment_link_wizard.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +import hashlib +import hmac + +from werkzeug import urls + +from odoo import api, fields, models, _ +from odoo.exceptions import ValidationError +from odoo.tools import ustr, consteq, float_compare + + +class PaymentLinkWizard(models.TransientModel): + _name = "payment.link.wizard" + _description = "Generate Payment Link" + + @api.model + def default_get(self, fields): + res = super(PaymentLinkWizard, self).default_get(fields) + res_id = self._context.get('active_id') + res_model = self._context.get('active_model') + res.update({'res_id': res_id, 'res_model': res_model}) + amount_field = 'amount_residual' if res_model == 'account.move' else 'amount_total' + if res_id and res_model == 'account.move': + record = self.env[res_model].browse(res_id) + res.update({ + 'description': record.payment_reference, + 'amount': record[amount_field], + 'currency_id': record.currency_id.id, + 'partner_id': record.partner_id.id, + 'amount_max': record[amount_field], + }) + return res + + res_model = fields.Char('Related Document Model', required=True) + res_id = fields.Integer('Related Document ID', required=True) + amount = fields.Monetary(currency_field='currency_id', required=True) + amount_max = fields.Monetary(currency_field='currency_id') + currency_id = fields.Many2one('res.currency') + partner_id = fields.Many2one('res.partner') + partner_email = fields.Char(related='partner_id.email') + link = fields.Char(string='Payment Link', compute='_compute_values') + description = fields.Char('Payment Ref') + access_token = fields.Char(compute='_compute_values') + company_id = fields.Many2one('res.company', compute='_compute_company') + + @api.onchange('amount', 'description') + def _onchange_amount(self): + if float_compare(self.amount_max, self.amount, precision_rounding=self.currency_id.rounding or 0.01) == -1: + raise ValidationError(_("Please set an amount smaller than %s.") % (self.amount_max)) + if self.amount <= 0: + raise ValidationError(_("The value of the payment amount must be positive.")) + + @api.depends('amount', 'description', 'partner_id', 'currency_id') + def _compute_values(self): + secret = self.env['ir.config_parameter'].sudo().get_param('database.secret') + for payment_link in self: + token_str = '%s%s%s' % (payment_link.partner_id.id, payment_link.amount, payment_link.currency_id.id) + payment_link.access_token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest() + # must be called after token generation, obvsly - the link needs an up-to-date token + self._generate_link() + + @api.depends('res_model', 'res_id') + def _compute_company(self): + for link in self: + record = self.env[link.res_model].browse(link.res_id) + link.company_id = record.company_id if 'company_id' in record else False + + def _generate_link(self): + for payment_link in self: + record = self.env[payment_link.res_model].browse(payment_link.res_id) + link = ('%s/website_payment/pay?reference=%s&amount=%s¤cy_id=%s' + '&partner_id=%s&access_token=%s') % ( + record.get_base_url(), + urls.url_quote_plus(payment_link.description), + payment_link.amount, + payment_link.currency_id.id, + payment_link.partner_id.id, + payment_link.access_token + ) + if payment_link.company_id: + link += '&company_id=%s' % payment_link.company_id.id + if payment_link.res_model == 'account.move': + link += '&invoice_id=%s' % payment_link.res_id + payment_link.link = link + + @api.model + def check_token(self, access_token, partner_id, amount, currency_id): + secret = self.env['ir.config_parameter'].sudo().get_param('database.secret') + token_str = '%s%s%s' % (partner_id, amount, currency_id) + correct_token = hmac.new(secret.encode('utf-8'), token_str.encode('utf-8'), hashlib.sha256).hexdigest() + if consteq(ustr(access_token), correct_token): + return True + return False
\ No newline at end of file diff --git a/addons/payment/wizards/payment_link_wizard_views.xml b/addons/payment/wizards/payment_link_wizard_views.xml new file mode 100644 index 00000000..de6666b0 --- /dev/null +++ b/addons/payment/wizards/payment_link_wizard_views.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<odoo> + <record id="payment_link_wizard_view_form" model="ir.ui.view"> + <field name="name">payment.link.wizard.form</field> + <field name="model">payment.link.wizard</field> + <field name="arch" type="xml"> + <form string="Generate Payment Link"> + <group> + <group> + <field name="res_id" invisible="1"/> + <field name="res_model" invisible="1"/> + <field name="partner_id" invisible="1"/> + <field name="partner_email" invisible="1"/> + <field name="amount_max" invisible="1"/> + <field name="description"/> + <field name="amount"/> + <field name="currency_id" invisible="1"/> + <field name="access_token" invisible="1"/> + </group> + </group> + <group> + <field name="link" readonly="1" widget="CopyClipboardChar"/> + </group> + <group attrs="{'invisible':[('partner_email', '!=', False)]}"> + <p class="alert alert-warning font-weight-bold" role="alert">This partner has no email, which may cause issues with some payment acquirers. Setting an email for this partner is advised.</p> + </group> + <footer> + <button string="Close" class="btn-primary" special="cancel"/> + </footer> + </form> + </field> + </record> + + <record id="action_invoice_order_generate_link" model="ir.actions.act_window"> + <field name="name">Generate a Payment Link</field> + <field name="res_model">payment.link.wizard</field> + <field name="view_mode">form</field> + <field name="view_id" ref="payment_link_wizard_view_form"/> + <field name="target">new</field> + <field name="binding_model_id" ref="model_account_move"/> + <field name="binding_view_types">form</field> + </record> +</odoo> |
