[FIX] payment: accept invoice ids in payment links

This commit brings back the possibility to pass invoice ids in payment
links, a feature that was lost with commit 573ed74c. If such an id is
present in the payment link URL, the transaction that is created must
have a reference starting with the name of the invoice, and must be
linked to it.

task-2494916

closes odoo/odoo#72992

X-original-commit: 6c13a361400332955cdf5ac5a14bd1970b4f66ef
Signed-off-by: Victor Feyens (vfe) <vfe@odoo.com>
This commit is contained in:
Victor Feyens
2021-06-30 11:42:43 +00:00
parent 1b2c7cbb43
commit 21b5101319
5 changed files with 81 additions and 5 deletions
+15 -4
View File
@@ -5,6 +5,7 @@ import werkzeug
from odoo import _, http
from odoo.exceptions import UserError, ValidationError
from odoo.fields import Command
from odoo.http import request
from odoo.addons.payment import utils as payment_utils
@@ -41,7 +42,7 @@ class PaymentPortal(portal.CustomerPortal):
)
def payment_pay(
self, reference=None, amount=None, currency_id=None, partner_id=None, company_id=None,
acquirer_id=None, access_token=None, **kwargs
acquirer_id=None, access_token=None, invoice_id=None, **kwargs
):
""" Display the payment form with optional filtering of payment options.
@@ -62,14 +63,15 @@ class PaymentPortal(portal.CustomerPortal):
:param str company_id: The related company, as a `res.company` id
:param str acquirer_id: The desired acquirer, as a `payment.acquirer` id
:param str access_token: The access token used to authenticate the partner
:param str invoice_id: The account move for which a payment id made, as a `account.move` id
:param dict kwargs: Optional data. This parameter is not used here
:return: The rendered checkout form
:rtype: str
:raise: werkzeug.exceptions.NotFound if the access token is invalid
"""
# Cast numeric parameters as int or float and void them if their str value is malformed
currency_id, acquirer_id, partner_id, company_id = tuple(map(
self.cast_as_int, (currency_id, acquirer_id, partner_id, company_id)
currency_id, acquirer_id, partner_id, company_id, invoice_id = tuple(map(
self.cast_as_int, (currency_id, acquirer_id, partner_id, company_id, invoice_id)
))
amount = self.cast_as_float(amount)
@@ -140,6 +142,7 @@ class PaymentPortal(portal.CustomerPortal):
'transaction_route': '/payment/transaction',
'landing_route': '/payment/confirmation',
'partner_is_different': partner_is_different,
'invoice_id': invoice_id,
**self._get_custom_rendering_context_values(**kwargs),
}
return request.render('payment.pay', rendering_context)
@@ -222,7 +225,8 @@ class PaymentPortal(portal.CustomerPortal):
def _create_transaction(
self, payment_option_id, reference_prefix, amount, currency_id, partner_id, flow,
tokenization_requested, validation_route, landing_route, custom_create_values=None, **kwargs
tokenization_requested, validation_route, landing_route, invoice_id=None,
custom_create_values=None, **kwargs
):
""" Create a draft transaction based on the payment context and return it.
@@ -239,6 +243,7 @@ class PaymentPortal(portal.CustomerPortal):
:param str validation_route: The route the user is redirected to in order to refund a
validation transaction
:param str landing_route: The route the user is redirected to after the transaction
:param int|None invoice_id: The account move for which a payment id made, as a `account.move` id
:param dict custom_create_values: Additional create values overwriting the default ones
:param dict kwargs: Locally unused data passed to `_is_tokenization_required` and
`_compute_reference`
@@ -270,6 +275,12 @@ class PaymentPortal(portal.CustomerPortal):
raise UserError(
_("The payment should either be direct, with redirection, or made by a token.")
)
if invoice_id:
if custom_create_values is None:
custom_create_values = {}
custom_create_values['invoice_ids'] = [Command.set([int(invoice_id)])]
reference = request.env['payment.transaction']._compute_reference(
acquirer_sudo.provider,
prefix=reference_prefix,
@@ -297,6 +297,8 @@ odoo.define('payment.payment_form_mixin', require => {
'currency_id': this.txContext.currencyId
? parseInt(this.txContext.currencyId) : null,
'partner_id': parseInt(this.txContext.partnerId),
'invoice_id': this.txContext.invoiceId
? parseInt(this.txContext.invoiceId) : null,
'flow': flow,
'tokenization_requested': this.txContext.tokenizationRequested,
'validation_route': this.txContext.validationRoute
+59
View File
@@ -279,6 +279,65 @@ class TestFlows(PaymentCommon, PaymentHttpCommon):
response = self.portal_pay(**route_values)
self.assertEqual(response.status_code, 404)
def test_invoice_payment_flow(self):
"""Test the payment of an invoice through the payment/pay route"""
# Create a dummy invoice
account = self.env['account.account'].search([('company_id', '=', self.env.company.id)], limit=1)
invoice = self.env['account.move'].create({
'move_type': 'entry',
'date': '2019-01-01',
'line_ids': [
(0, 0, {
'account_id': account.id,
'currency_id': self.currency_euro.id,
'debit': 100.0,
'credit': 0.0,
'amount_currency': 200.0,
}),
(0, 0, {
'account_id': account.id,
'currency_id': self.currency_euro.id,
'debit': 0.0,
'credit': 100.0,
'amount_currency': -200.0,
}),
],
})
# Pay for this invoice (no impact even if amounts do not match)
route_values = self._prepare_pay_values()
route_values['invoice_id'] = invoice.id
tx_context = self.get_tx_checkout_context(**route_values)
self.assertEqual(tx_context['invoice_id'], invoice.id)
# payment/transaction
route_values = {
k: tx_context[k]
for k in [
'amount',
'currency_id',
'reference_prefix',
'partner_id',
'access_token',
'landing_route',
'invoice_id',
]
}
route_values.update({
'flow': 'direct',
'payment_option_id': self.acquirer.id,
'tokenization_requested': False,
'validation_route': False,
})
with mute_logger('odoo.addons.payment.models.payment_transaction'):
processing_values = self.get_processing_values(**route_values)
tx_sudo = self._get_tx(processing_values['reference'])
# Note: strangely, the check
# self.assertEqual(tx_sudo.invoice_ids, invoice)
# doesn't work, and cache invalidation doesn't work either.
invoice.invalidate_cache(['transaction_ids'])
self.assertEqual(invoice.transaction_ids, tx_sudo)
def test_transaction_wrong_flow(self):
transaction_values = self._prepare_pay_values()
transaction_values.update({
+4 -1
View File
@@ -17,6 +17,7 @@
- 'transaction_route' - The route used to create a transaction when the user clicks Pay
- 'landing_route' - The route the user is redirected to after the transaction
- 'footer_template_id' - The template id for the submit button. Optional
- 'invoice_id' - The id of the account move being paid. Optional
-->
<form name="o_payment_checkout"
class="o_payment_form mt-3 clearfix"
@@ -27,7 +28,8 @@
t-att-data-access-token="access_token"
t-att-data-transaction-route="transaction_route"
t-att-data-landing-route="landing_route"
t-att-data-allow-token-selection="True">
t-att-data-allow-token-selection="True"
t-att-data-invoice-id="invoice_id">
<t t-set="acquirer_count" t-value="len(acquirers) if acquirers else 0"/>
<t t-set="token_count" t-value="len(tokens) if tokens else 0"/>
@@ -166,6 +168,7 @@
t-att-data-reference-prefix="reference_prefix"
t-att-data-partner-id="partner_id"
t-att-data-access-token="access_token"
t-att-data-invoice-id="invoice_id"
t-att-data-transaction-route="transaction_route"
t-att-data-assign-token-route="assign_token_route"
t-att-data-validation-route="validation_route"
@@ -105,5 +105,6 @@ class PaymentLinkWizard(models.TransientModel):
f'&currency_id={payment_link.currency_id.id}' \
f'&partner_id={payment_link.partner_id.id}' \
f'&company_id={payment_link.company_id.id}' \
f'&invoice_id={payment_link.res_id}' \
f'{"&acquirer_id=" + str(payment_link.acquirer_id.id) if payment_link.acquirer_id else "" }' \
f'&access_token={payment_link.access_token}'