From 807df01f5d083f8baafa2c1ed57fda4473a2f421 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 8 Aug 2025 15:56:35 +0200 Subject: [PATCH] Checkout: Delete invoice address if no longer required (Z#23203488) (#5358) --- src/pretix/base/models/orders.py | 18 +++++++++++ src/pretix/presale/checkoutflow.py | 8 +++++ src/tests/presale/test_checkout.py | 51 ++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 96f1b53268..c058128b9d 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -3314,6 +3314,24 @@ class InvoiceAddress(models.Model): kwargs['update_fields'] = {'name_cached', 'name_parts'}.union(kwargs['update_fields']) super().save(**kwargs) + def clear(self, except_name=False): + self.is_business = False + if not except_name: + self.name_cached = "" + self.name_parts = {} + self.company = "" + self.street = "" + self.zipcode = "" + self.city = "" + self.country_old = "" + self.country = "" + self.state = "" + self.vat_id = "" + self.vat_id_validated = False + self.custom_field = None + self.internal_reference = "" + self.beneficiary = "" + def describe(self): parts = [ self.company, diff --git a/src/pretix/presale/checkoutflow.py b/src/pretix/presale/checkoutflow.py index 5fce2b631c..a637e7afad 100644 --- a/src/pretix/presale/checkoutflow.py +++ b/src/pretix/presale/checkoutflow.py @@ -959,6 +959,10 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep): d['phone'] = str(d['phone']) self.cart_session['contact_form_data'] = d if self.address_asked or self.request.event.settings.invoice_name_required: + if not self.address_asked: + # Invoice address was there, but is no longer asked for, however, name is still required + self.invoice_form.instance.clear(except_name=True) + addr = self.invoice_form.save() if self.cart_customer and self.invoice_form.cleaned_data.get('save'): @@ -997,6 +1001,10 @@ class QuestionsStep(QuestionsViewMixin, CartMixin, TemplateFlowStep): 'rate to your purchase and the price of the products in your cart has ' 'changed accordingly.')) return redirect_to_url(self.get_next_url(request) + '?open_cart=true') + elif 'invoice_address' in self.cart_session: + # Invoice address was there, but is no longer asked for + self.invoice_address.delete() + del self.cart_session['invoice_address'] try: validate_memberships_in_order(self.cart_customer, self.positions, self.request.event, lock=False, diff --git a/src/tests/presale/test_checkout.py b/src/tests/presale/test_checkout.py index 978eac9c4d..9eb0dc9565 100644 --- a/src/tests/presale/test_checkout.py +++ b/src/tests/presale/test_checkout.py @@ -1337,6 +1337,57 @@ class CheckoutTestCase(BaseCheckoutTestCase, TimemachineTestMixin, TestCase): self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), target_status_code=200) + def test_invoice_address_discarded_for_free(self): + self.event.settings.invoice_address_asked = True + self.event.settings.invoice_address_required = True + self.event.settings.invoice_address_not_asked_free = True + self.event.settings.set('name_scheme', 'title_given_middle_family') + + with scopes_disabled(): + cp = CartPosition.objects.create( + event=self.event, cart_id=self.session_key, item=self.ticket, + price=23, expires=now() + timedelta(minutes=10) + ) + response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True) + doc = BeautifulSoup(response.content.decode(), "lxml") + self.assertEqual(len(doc.select('input[name="city"]')), 1) + + # Corrected request + response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), { + 'is_business': 'business', + 'company': 'Foo', + 'name_parts_0': 'Mr', + 'name_parts_1': 'John', + 'name_parts_2': '', + 'name_parts_3': 'Kennedy', + 'street': 'Baz', + 'zipcode': '12345', + 'city': 'Here', + 'country': 'DE', + 'vat_id': 'DE123456', + 'email': 'admin@localhost' + }, follow=True) + self.assertRedirects(response, '/%s/%s/checkout/payment/' % (self.orga.slug, self.event.slug), + target_status_code=200) + with scopes_disabled(): + assert InvoiceAddress.objects.exists() + + cp.price = Decimal("0.00") + cp.save() + + response = self.client.get('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), follow=True) + doc = BeautifulSoup(response.content.decode(), "lxml") + self.assertEqual(len(doc.select('input[name="city"]')), 0) + + # Corrected request + response = self.client.post('/%s/%s/checkout/questions/' % (self.orga.slug, self.event.slug), { + 'email': 'admin@localhost' + }, follow=True) + self.assertRedirects(response, '/%s/%s/checkout/confirm/' % (self.orga.slug, self.event.slug), + target_status_code=200) + with scopes_disabled(): + assert not InvoiceAddress.objects.exists() + def test_invoice_address_optional(self): self.event.settings.invoice_address_asked = True self.event.settings.invoice_address_required = False