summaryrefslogtreecommitdiff
path: root/addons/payment/wizards
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/payment/wizards
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/payment/wizards')
-rw-r--r--addons/payment/wizards/__init__.py5
-rw-r--r--addons/payment/wizards/payment_acquirer_onboarding_wizard.py161
-rw-r--r--addons/payment/wizards/payment_link_wizard.py94
-rw-r--r--addons/payment/wizards/payment_link_wizard_views.xml43
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&currency_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>