mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-23 07:52:00 +02:00
payment
This commit is contained in:
parent
12c29a983b
commit
95fcc8bd63
189 changed files with 170858 additions and 0 deletions
|
|
@ -0,0 +1,4 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import payment_link_wizard
|
||||
from . import payment_onboarding_wizard
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug import urls
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
from odoo.tools import float_compare
|
||||
|
||||
from odoo.addons.payment import utils as payment_utils
|
||||
|
||||
|
||||
class PaymentLinkWizard(models.TransientModel):
|
||||
_name = 'payment.link.wizard'
|
||||
_description = "Generate Payment Link"
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
res = super().default_get(fields_list)
|
||||
res_id = self.env.context.get('active_id')
|
||||
res_model = self.env.context.get('active_model')
|
||||
if res_id and res_model:
|
||||
res.update({'res_model': res_model, 'res_id': res_id})
|
||||
res.update(
|
||||
self.env[res_model].browse(res_id)._get_default_payment_link_values()
|
||||
)
|
||||
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')
|
||||
description = fields.Char("Payment Ref")
|
||||
link = fields.Char(string="Payment Link", compute='_compute_link')
|
||||
company_id = fields.Many2one('res.company', compute='_compute_company_id')
|
||||
available_provider_ids = fields.Many2many(
|
||||
comodel_name='payment.provider',
|
||||
string="Payment Providers Available",
|
||||
compute='_compute_available_provider_ids',
|
||||
compute_sudo=True,
|
||||
)
|
||||
has_multiple_providers = fields.Boolean(
|
||||
string="Has Multiple Providers",
|
||||
compute='_compute_has_multiple_providers',
|
||||
)
|
||||
payment_provider_selection = fields.Selection(
|
||||
string="Allow Payment Provider",
|
||||
help="If a specific payment provider is selected, customers will only be allowed to pay "
|
||||
"via this one. If 'All' is selected, customers can pay via any available payment "
|
||||
"provider.",
|
||||
selection='_selection_payment_provider_selection',
|
||||
default='all',
|
||||
required=True,
|
||||
)
|
||||
|
||||
@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('res_model', 'res_id')
|
||||
def _compute_company_id(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
|
||||
|
||||
@api.depends('company_id', 'partner_id', 'currency_id')
|
||||
def _compute_available_provider_ids(self):
|
||||
for link in self:
|
||||
link.available_provider_ids = link._get_payment_provider_available(
|
||||
res_model=link.res_model,
|
||||
res_id=link.res_id,
|
||||
company_id=link.company_id.id,
|
||||
partner_id=link.partner_id.id,
|
||||
amount=link.amount,
|
||||
currency_id=link.currency_id.id,
|
||||
)
|
||||
|
||||
def _selection_payment_provider_selection(self):
|
||||
""" Specify available providers in the selection field.
|
||||
:return: The selection list of available providers.
|
||||
:rtype: list[tuple]
|
||||
"""
|
||||
defaults = self.default_get(['res_model', 'res_id'])
|
||||
selection = [('all', "All")]
|
||||
res_model, res_id = defaults.get('res_model'), defaults.get('res_id')
|
||||
if res_id and res_model in ['account.move', "sale.order"]:
|
||||
# At module install, the selection method is called
|
||||
# but the document context isn't specified.
|
||||
related_document = self.env[res_model].browse(res_id)
|
||||
company_id = related_document.company_id
|
||||
partner_id = related_document.partner_id
|
||||
currency_id = related_document.currency_id
|
||||
selection.extend(
|
||||
self._get_payment_provider_available(
|
||||
res_model=res_model,
|
||||
res_id=res_id,
|
||||
company_id=company_id.id,
|
||||
partner_id=partner_id.id,
|
||||
amount=related_document.amount_total,
|
||||
currency_id=currency_id.id,
|
||||
).name_get()
|
||||
)
|
||||
return selection
|
||||
|
||||
def _get_payment_provider_available(self, **kwargs):
|
||||
""" Select and return the providers matching the criteria.
|
||||
|
||||
:return: The compatible providers
|
||||
:rtype: recordset of `payment.provider`
|
||||
"""
|
||||
return self.env['payment.provider'].sudo()._get_compatible_providers(**kwargs)
|
||||
|
||||
@api.depends('available_provider_ids')
|
||||
def _compute_has_multiple_providers(self):
|
||||
for link in self:
|
||||
link.has_multiple_providers = len(link.available_provider_ids) > 1
|
||||
|
||||
def _get_access_token(self):
|
||||
self.ensure_one()
|
||||
return payment_utils.generate_access_token(
|
||||
self.partner_id.id, self.amount, self.currency_id.id
|
||||
)
|
||||
|
||||
@api.depends(
|
||||
'description', 'amount', 'currency_id', 'partner_id', 'company_id',
|
||||
'payment_provider_selection',
|
||||
)
|
||||
def _compute_link(self):
|
||||
for payment_link in self:
|
||||
related_document = self.env[payment_link.res_model].browse(payment_link.res_id)
|
||||
base_url = related_document.get_base_url() # Don't generate links for the wrong website
|
||||
url_params = {
|
||||
'reference': payment_link.description,
|
||||
'amount': self.amount,
|
||||
'access_token': self._get_access_token(),
|
||||
**self._get_additional_link_values(),
|
||||
}
|
||||
if payment_link.payment_provider_selection != 'all':
|
||||
url_params['provider_id'] = str(payment_link.payment_provider_selection)
|
||||
payment_link.link = f'{base_url}/payment/pay?{urls.url_encode(url_params)}'
|
||||
|
||||
def _get_additional_link_values(self):
|
||||
""" Return the additional values to append to the payment link.
|
||||
|
||||
Note: self.ensure_one()
|
||||
|
||||
:return: The additional payment link values.
|
||||
:rtype: dict
|
||||
"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'currency_id': self.currency_id.id,
|
||||
'partner_id': self.partner_id.id,
|
||||
'company_id': self.company_id.id,
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?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">
|
||||
<div class="alert alert-warning fw-bold"
|
||||
role="alert"
|
||||
attrs="{'invisible': [('partner_email', '!=', False)]}">
|
||||
This partner has no email, which may cause issues with some payment providers.
|
||||
Setting an email for this partner is advised.
|
||||
</div>
|
||||
<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="available_provider_ids" invisible="1"/>
|
||||
<field name="has_multiple_providers" invisible="1"/>
|
||||
<field name="description"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="payment_provider_selection"
|
||||
attrs="{'invisible':[('has_multiple_providers', '=', False)]}"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="link" readonly="1" widget="CopyClipboardChar"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Close" class="btn-primary" special="cancel" data-hotkey="z"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="payment_provider_onboarding_wizard_form" model="ir.ui.view">
|
||||
<field name="name">payment.provider.onboarding.wizard.form</field>
|
||||
<field name="model">payment.provider.onboarding.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Choose a payment method" class="o_onboarding_payment_provider_wizard">
|
||||
<div class="container">
|
||||
<div class="row align-items-start">
|
||||
<div class="col col-4" name="left-column">
|
||||
<field name="payment_method" widget="radio"/>
|
||||
</div>
|
||||
<div class="col" name="right-column">
|
||||
<div attrs="{'invisible': [('payment_method', '!=', 'paypal')]}">
|
||||
<group>
|
||||
<field name="paypal_email_account" attrs="{'required': [('payment_method', '=', 'paypal')]}" string="Email"/>
|
||||
<field name="paypal_pdt_token" password="True" attrs="{'required': [('payment_method', '=', 'paypal')]}" />
|
||||
</group>
|
||||
<p>
|
||||
<a href="https://www.odoo.com/documentation/16.0/applications/finance/payment_providers/paypal.html" target="_blank">
|
||||
<span><i class="fa fa-arrow-right"/> How to configure your PayPal account</span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div attrs="{'invisible': [('payment_method', '!=', 'manual')]}">
|
||||
<group>
|
||||
<field name="manual_name" attrs="{'required': [('payment_method', '=', 'manual')]}"/>
|
||||
<field name="journal_name" attrs="{'required': [('payment_method', '=', 'manual')]}"/>
|
||||
<field name="acc_number" attrs="{'required': [('payment_method', '=', 'manual')]}"/>
|
||||
<field name="manual_post_msg" attrs="{'required': [('payment_method', '=', 'manual')]}"/>
|
||||
</group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<button name="add_payment_methods" string="Apply" class="oe_highlight"
|
||||
type="object" data-hotkey="q" />
|
||||
<button special="cancel" data-hotkey="z" string="Cancel" />
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class PaymentWizard(models.TransientModel):
|
||||
_name = 'payment.provider.onboarding.wizard'
|
||||
_description = 'Payment provider onboarding wizard'
|
||||
|
||||
payment_method = fields.Selection([
|
||||
('stripe', "Credit & Debit card (via Stripe)"),
|
||||
('paypal', "PayPal"),
|
||||
('manual', "Custom payment instructions"),
|
||||
], string="Payment Method", default=lambda self: self._get_default_payment_provider_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_provider_onboarding_value('paypal_email_account'))
|
||||
paypal_seller_account = fields.Char("Merchant Account ID")
|
||||
paypal_pdt_token = fields.Char("PDT Identity Token", default=lambda self: self._get_default_payment_provider_onboarding_value('paypal_pdt_token'))
|
||||
|
||||
# Account-specific logic. It's kept here rather than moved in `account_payment` as it's not used by `account` module.
|
||||
manual_name = fields.Char("Method", default=lambda self: self._get_default_payment_provider_onboarding_value('manual_name'))
|
||||
journal_name = fields.Char("Bank Name", default=lambda self: self._get_default_payment_provider_onboarding_value('journal_name'))
|
||||
acc_number = fields.Char("Account Number", default=lambda self: self._get_default_payment_provider_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_provider_onboarding_cache = {}
|
||||
|
||||
def _get_manual_payment_provider(self, env=None):
|
||||
if env is None:
|
||||
env = self.env
|
||||
module_id = env.ref('base.module_payment_custom').id
|
||||
return env['payment.provider'].search([('module_id', '=', module_id),
|
||||
('company_id', '=', env.company.id)], limit=1)
|
||||
|
||||
def _get_default_payment_provider_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_provider_onboarding_cache.get(key, '')
|
||||
|
||||
self._data_fetched = True
|
||||
|
||||
self._payment_provider_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:
|
||||
provider = self.env['payment.provider'].search(
|
||||
[('company_id', '=', self.env.company.id), ('code', '=', 'paypal')], limit=1
|
||||
)
|
||||
self._payment_provider_onboarding_cache['paypal_email_account'] = provider['paypal_email_account'] or self.env.user.email or ''
|
||||
self._payment_provider_onboarding_cache['paypal_pdt_token'] = provider['paypal_pdt_token']
|
||||
|
||||
manual_payment = self._get_manual_payment_provider()
|
||||
journal = manual_payment.journal_id
|
||||
|
||||
self._payment_provider_onboarding_cache['manual_name'] = manual_payment['name']
|
||||
self._payment_provider_onboarding_cache['manual_post_msg'] = manual_payment['pending_msg']
|
||||
self._payment_provider_onboarding_cache['journal_name'] = journal.name if journal.name != "Bank" else ""
|
||||
self._payment_provider_onboarding_cache['acc_number'] = journal.bank_acc_number
|
||||
|
||||
return self._payment_provider_onboarding_cache.get(key, '')
|
||||
|
||||
def add_payment_methods(self):
|
||||
""" Install required payment providers, configure them and mark the
|
||||
onboarding step as done."""
|
||||
payment_method = self.payment_method
|
||||
|
||||
if self.payment_method == 'paypal':
|
||||
self.env.company._install_modules(['payment_paypal', 'account_payment'])
|
||||
elif self.payment_method == 'manual':
|
||||
self.env.company._install_modules(['account_payment'])
|
||||
|
||||
if self.payment_method in ('paypal', 'manual'):
|
||||
# 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':
|
||||
provider = new_env['payment.provider'].search(
|
||||
[('company_id', '=', self.env.company.id), ('code', '=', 'paypal')], limit=1
|
||||
)
|
||||
if not provider:
|
||||
base_provider = self.env.ref('payment.payment_provider_paypal')
|
||||
# Use sudo to access payment provider record that can be in different company.
|
||||
provider = base_provider.sudo().copy(default={'company_id':self.env.company.id})
|
||||
default_journal = new_env['account.journal'].search(
|
||||
[('type', '=', 'bank'), ('company_id', '=', new_env.company.id)], limit=1
|
||||
)
|
||||
provider.write({
|
||||
'paypal_email_account': self.paypal_email_account,
|
||||
'paypal_pdt_token': self.paypal_pdt_token,
|
||||
'state': 'enabled',
|
||||
'is_published': 'True',
|
||||
'journal_id': provider.journal_id or default_journal
|
||||
})
|
||||
elif self.payment_method == 'manual':
|
||||
manual_provider = self._get_manual_payment_provider(new_env)
|
||||
if not manual_provider:
|
||||
raise UserError(_(
|
||||
'No manual payment method could be found for this company. '
|
||||
'Please create one from the Payment Provider menu.'
|
||||
))
|
||||
manual_provider.name = self.manual_name
|
||||
manual_provider.pending_msg = self.manual_post_msg
|
||||
manual_provider.state = 'enabled'
|
||||
|
||||
journal = manual_provider.journal_id
|
||||
if journal:
|
||||
journal.name = self.journal_name
|
||||
journal.bank_acc_number = self.acc_number
|
||||
|
||||
if self.payment_method in ('paypal', 'manual', 'stripe'):
|
||||
self.env.company.payment_onboarding_payment_method = self.payment_method
|
||||
|
||||
# delete wizard data immediately to get rid of residual credentials
|
||||
self.sudo().unlink()
|
||||
|
||||
if payment_method == 'stripe':
|
||||
return self._start_stripe_onboarding()
|
||||
|
||||
# the user clicked `apply` and not cancel so we can assume this step is done.
|
||||
self._set_payment_provider_onboarding_step_done()
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def _set_payment_provider_onboarding_step_done(self):
|
||||
self.env.company.sudo().set_onboarding_step_done('payment_provider_onboarding_state')
|
||||
|
||||
def _start_stripe_onboarding(self):
|
||||
""" Start Stripe Connect onboarding. """
|
||||
menu = self.env.ref('account_payment.payment_provider_menu', False)
|
||||
menu_id = menu and menu.id # Only set if `account_payment` is installed.
|
||||
return self.env.company._run_payment_onboarding_step(menu_id)
|
||||
Loading…
Add table
Add a link
Reference in a new issue