[FIX] website_sale: forbid payment if an error is displayed

steps to reproduce:
- as admin, go to /shop and add a storable product to cart
- disable all delivery methods
- use a coupon to set the price to 0

before this commit:
- the payment button is clickable even if there is a big red
warning saying "Sorry, we are unable to ship your order"

after this commit:
- the payment button is hidden if an error is displayed and
the route /shop/payment/validate is blocked if there is an
error displayed

opw-3582207-nda

closes odoo/odoo#146251

X-original-commit: 6db61c5cc0ca49a31252985fa2d690cb6514e7f7
Related: odoo/enterprise#52753
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
Signed-off-by: Nicolas Danhier (nda) <nda@odoo.com>
This commit is contained in:
nda
2023-12-26 18:08:20 +00:00
parent 4f9d623d41
commit 9988231286
4 changed files with 50 additions and 16 deletions
+25 -8
View File
@@ -1679,7 +1679,7 @@ class WebsiteSale(payment_portal.PaymentPortal):
def _get_shop_payment_values(self, order, **kwargs):
checkout_page_values = {
'website_sale_order': order,
'errors': [],
'errors': self._get_shop_payment_errors(order),
'partner': order.partner_invoice_id,
'order': order,
'submit_button_label': _("Pay now"),
@@ -1702,13 +1702,6 @@ class WebsiteSale(payment_portal.PaymentPortal):
has_storable_products = any(
line.product_id.type in ['consu', 'product'] for line in order.order_line
)
if not order._get_delivery_methods() and has_storable_products:
values['errors'].append((
_('Sorry, we are unable to ship your order'),
_('No shipping method is available for your current order and shipping address.'
' Please contact us for more information.')
))
if has_storable_products:
if order.carrier_id and not order.delivery_rating_success:
order._remove_delivery_line()
@@ -1722,6 +1715,24 @@ class WebsiteSale(payment_portal.PaymentPortal):
return values
def _get_shop_payment_errors(self, order):
""" Check that there is no error that should block the payment.
:param sale.order order: The sales order to pay
:return: A list of errors (error_title, error_message)
:rtype: list[tuple]
"""
has_storable_products = any(line.product_id.type in ['consu', 'product'] for line in order.order_line)
errors = []
if not order._get_delivery_methods() and has_storable_products:
errors.append((
_('Sorry, we are unable to ship your order'),
_('No shipping method is available for your current order and shipping address. '
'Please contact us for more information.'),
))
return errors
@http.route('/shop/payment', type='http', auth='public', website=True, sitemap=False)
def shop_payment(self, **post):
""" Payment step. This page proposes several payment means based on available
@@ -1780,6 +1791,12 @@ class WebsiteSale(payment_portal.PaymentPortal):
order = request.env['sale.order'].sudo().browse(sale_order_id)
assert order.id == request.session.get('sale_last_order_id')
errors = self._get_shop_payment_errors(order)
if errors:
first_error = errors[0] # only display first error
error_msg = f"{first_error[0]}\n{first_error[1]}"
raise ValidationError(error_msg)
tx_sudo = order.get_portal_last_transaction() if order else order.env['payment.transaction']
if not order or (order.amount_total and not tx_sudo):
@@ -2,8 +2,8 @@
from unittest.mock import patch
from odoo.exceptions import UserError
from odoo.fields import Command
from odoo.exceptions import UserError, ValidationError
from odoo.tests import tagged
from odoo.addons.payment.tests.common import PaymentCommon
@@ -110,3 +110,23 @@ class TestWebsiteSaleDeliveryController(PaymentCommon, SaleCommon):
self.assertEqual(
self.empty_order._get_delivery_methods().mapped('name'), ['Under 300', 'Fixed']
)
def test_validate_payment_with_no_available_delivery_method(self):
"""
An error should be raised if you try to validate an order with a storable
product without any delivery method available
"""
storable_product = self.env['product.product'].create({
'name': 'Storable Product',
'sale_ok': True,
'type': 'product',
'website_published': True,
})
carriers = self.env['delivery.carrier'].search([])
carriers.write({'website_published': False})
with MockRequest(self.env, website=self.website):
self.website.sale_get_order(force_create=True)
self.Controller.cart_update_json(product_id=storable_product.id, add_qty=1)
with self.assertRaises(ValidationError):
self.Controller.shop_payment_validate()
+2 -2
View File
@@ -1759,7 +1759,7 @@
<div t-attf-class="#{_container_classes} d-flex #{_form_send_navigation and 'flex-column flex-lg-row align-items-lg-center' or 'flex-column'} pt-4">
<t t-if="website_sale_order and website_sale_order.website_order_line">
<t t-if="xmlid == 'website_sale.payment'">
<div t-if="not website_sale_order.amount_total"
<div t-if="not errors and not website_sale_order.amount_total"
name="o_website_sale_free_cart">
<form name="o_wsale_confirm_order"
class="d-flex flex-column"
@@ -2127,7 +2127,7 @@
</div>
<div class="oe_structure clearfix mt-3" id="oe_structure_website_sale_payment_1"/>
<t t-if="website_sale_order.amount_total">
<t t-if="not errors and website_sale_order.amount_total" name="website_sale_non_free_cart">
<div class="mt-4">
<h3>Pay with</h3>
</div>
@@ -43,7 +43,8 @@
</template>
<template id="payment_delivery" name="Delivery Costs" inherit_id="website_sale.payment">
<xpath expr="//t[@t-if='website_sale_order.amount_total']" position="before">
<!-- //t[@t-if='website_sale_order.amount_total'] should be removed in master -->
<xpath expr="//t[@name='website_sale_non_free_cart'] | //t[@t-if='website_sale_order.amount_total']" position="before">
<div t-if="deliveries" id="delivery_carrier">
<t t-set="delivery_nb" t-value="len(deliveries)"/>
<h4>Choose a delivery method</h4>
@@ -58,10 +59,6 @@
</div>
</div>
</xpath>
<!-- we shouldn't be able to pay if there is no way to deliver -->
<xpath expr="//div[@id='payment_method']" position="attributes">
<attribute name="t-att-style">'display: none!important' if not deliveries and delivery_has_storable else ''</attribute>
</xpath>
</template>
</odoo>