From 059ff6c99b1897545a4339a021ec61fbe039b579 Mon Sep 17 00:00:00 2001 From: pajowu Date: Mon, 13 Apr 2026 19:32:33 +0200 Subject: [PATCH] Allow buttons to reuse cart (Z#23226853) (#6047) * Allow buttons to reuse cart (Z#23226853) * Always keep cart of buttons with items set --- src/pretix/presale/urls.py | 3 + src/pretix/presale/views/cart.py | 12 +++ .../static/pretixpresale/js/widget/widget.js | 81 +++++++++++++------ 3 files changed, 72 insertions(+), 24 deletions(-) diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index 2fe4fc398..94df03d95 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -91,6 +91,9 @@ event_patterns = [ re_path(r'w/(?P[a-zA-Z0-9]{16})/cart/add', csrf_exempt(pretix.presale.views.cart.CartAdd.as_view()), name='event.cart.add'), + re_path(r'w/(?P[a-zA-Z0-9]{16})/cart/create', + csrf_exempt(pretix.presale.views.cart.CartCreate.as_view()), + name='event.cart.create'), re_path(r'unlock/(?P[a-z0-9]{64})/$', pretix.presale.views.user.UnlockHashView.as_view(), name='event.payment.unlock'), diff --git a/src/pretix/presale/views/cart.py b/src/pretix/presale/views/cart.py index 99af19b19..c6f566371 100644 --- a/src/pretix/presale/views/cart.py +++ b/src/pretix/presale/views/cart.py @@ -555,6 +555,18 @@ class CartClear(EventViewMixin, CartActionMixin, AsyncAction, View): request.sales_channel.identifier, time_machine_now(default=None)) +@method_decorator(allow_cors_if_namespaced, 'dispatch') +class CartCreate(EventViewMixin, CartActionMixin, View): + def get(self, request, *args, **kwargs): + if 'ajax' in self.request.GET: + cart_id = get_or_create_cart_id(self.request, create=True) + return JsonResponse({ + 'cart_id': cart_id, + }) + else: + return redirect_to_url(self.get_success_url()) + + @method_decorator(allow_frame_if_namespaced, 'dispatch') class CartExtendReservation(EventViewMixin, CartActionMixin, AsyncAction, View): task = extend_cart_reservation diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index f7e7bedae..b24f1efca 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -110,6 +110,10 @@ var setCookie = function (cname, cvalue, exdays) { var d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); var expires = "expires=" + d.toUTCString(); + if (!cvalue) { + var expires = "expires=Thu, 01 Jan 1970 00:00:00 GMT"; + cvalue = ""; + } document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; }; var getCookie = function (name) { @@ -726,17 +730,16 @@ var shared_methods = { buy_callback: function (data) { if (data.redirect) { if (data.cart_id) { - this.$root.cart_id = data.cart_id; - setCookie(this.$root.cookieName, data.cart_id, 30); + this.$root.set_cart_id(data.cart_id); } if (data.redirect.substr(0, 1) === '/') { data.redirect = this.$root.target_url.replace(/^([^\/]+:\/\/[^\/]+)\/.*$/, "$1") + data.redirect; } var url = data.redirect; if (url.indexOf('?')) { - url = url + '&iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id; + url = url + '&iframe=1&locale=' + lang + '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id()); } else { - url = url + '?iframe=1&locale=' + lang + '&take_cart_id=' + this.$root.cart_id; + url = url + '?iframe=1&locale=' + lang + '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id()); } url += this.$root.consent_parameter; if (this.$root.additionalURLParams) { @@ -779,15 +782,24 @@ var shared_methods = { } }, resume: function () { + if (!this.$root.get_cart_id() && this.$root.keep_cart) { + // create an empty cart whose id we can persist + this.$root.create_cart(this.resume) + return; + } var redirect_url; redirect_url = this.$root.target_url + 'w/' + widget_id + '/'; - if (this.$root.subevent && !this.$root.cart_id) { + if (this.$root.subevent && this.$root.is_button && this.$root.items.length === 0) { // button with subevent but no items redirect_url += this.$root.subevent + '/'; } redirect_url += '?iframe=1&locale=' + lang; - if (this.$root.cart_id) { - redirect_url += '&take_cart_id=' + this.$root.cart_id; + if (this.$root.get_cart_id()) { + redirect_url += '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id()); + if (this.$root.keep_cart) { + // make sure the cart-id is used, even if the cart is currently empty + redirect_url += '&ajax=1' + } } if (this.$root.widget_data) { redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json); @@ -1864,12 +1876,11 @@ var shared_root_methods = { if (this.$root.variation_filter) { url += '&variations=' + encodeURIComponent(this.$root.variation_filter); } - var cart_id = getCookie(this.cookieName); if (this.$root.voucher_code) { url += '&voucher=' + encodeURIComponent(this.$root.voucher_code); } - if (cart_id) { - url += "&cart_id=" + encodeURIComponent(cart_id); + if (this.$root.get_cart_id()) { + url += "&cart_id=" + encodeURIComponent(this.$root.get_cart_id()); } if (this.$root.date !== null) { url += "&date=" + this.$root.date.substr(0, 7); @@ -1939,7 +1950,6 @@ var shared_root_methods = { root.display_add_to_cart = data.display_add_to_cart; root.waiting_list_enabled = data.waiting_list_enabled; root.show_variations_expanded = data.show_variations_expanded || !!root.variation_filter; - root.cart_id = cart_id; root.cart_exists = data.cart_exists; root.vouchers_exist = data.vouchers_exist; root.has_seating_plan = data.has_seating_plan; @@ -2004,8 +2014,8 @@ var shared_root_methods = { if (this.$root.voucher_code) { redirect_url += '&voucher=' + encodeURIComponent(this.$root.voucher_code); } - if (this.$root.cart_id) { - redirect_url += '&take_cart_id=' + this.$root.cart_id; + if (this.$root.get_cart_id()) { + redirect_url += '&take_cart_id=' + encodeURIComponent(this.$root.get_cart_id()); } if (this.$root.widget_data) { redirect_url += '&widget_data=' + encodeURIComponent(this.$root.widget_data_json); @@ -2027,7 +2037,28 @@ var shared_root_methods = { this.$root.subevent = event.subevent; this.$root.loading++; this.$root.reload(); - } + }, + create_cart: function(callback) { + var url = this.$root.target_url + 'w/' + widget_id + '/cart/create?ajax=1'; + + this.$root.overlay.frame_loading = true; + api._getJSON(url, (data) => { + this.$root.set_cart_id(data.cart_id); + this.$root.overlay.frame_loading = false; + callback() + }, () => { + this.$root.overlay.error_message = strings['cart_error']; + this.$root.overlay.frame_loading = false; + }) + }, + get_cart_id: function() { + if (this.$root.keep_cart) { + return getCookie(this.$root.cookieName); + } + }, + set_cart_id: function(newValue) { + setCookie(this.$root.cookieName, newValue, 30); + }, }; var shared_root_computed = { @@ -2049,9 +2080,8 @@ var shared_root_computed = { }, voucherFormTarget: function () { var form_target = this.target_url + 'w/' + widget_id + '/redeem?iframe=1&locale=' + lang; - var cookie = getCookie(this.cookieName); - if (cookie) { - form_target += "&take_cart_id=" + cookie; + if (this.get_cart_id()) { + form_target += "&take_cart_id=" + encodeURIComponent(this.get_cart_id()); } if (this.subevent) { form_target += "&subevent=" + this.subevent; @@ -2091,9 +2121,8 @@ var shared_root_computed = { checkout_url += '?' + this.$root.additionalURLParams; } var form_target = this.target_url + 'w/' + widget_id + '/cart/add?iframe=1&next=' + encodeURIComponent(checkout_url); - var cookie = getCookie(this.cookieName); - if (cookie) { - form_target += "&take_cart_id=" + cookie; + if (this.get_cart_id()) { + form_target += "&take_cart_id=" + encodeURIComponent(this.get_cart_id()); } form_target += this.$root.consent_parameter return form_target @@ -2329,6 +2358,7 @@ var create_widget = function (element, html_id=null) { has_seating_plan: false, has_seating_plan_waitinglist: false, meta_filter_fields: [], + keep_cart: true, } }, created: function () { @@ -2366,6 +2396,7 @@ var create_button = function (element, html_id=null) { var raw_items = element.attributes.items ? element.attributes.items.value : ""; var skip_ssl = element.attributes["skip-ssl-check"] ? true : false; var disable_iframe = element.attributes["disable-iframe"] ? true : false; + var keep_cart = element.attributes["keep-cart"] ? true : false; var button_text = element.innerHTML; var widget_data = JSON.parse(JSON.stringify(window.PretixWidget.widget_data)); for (var i = 0; i < element.attributes.length; i++) { @@ -2417,7 +2448,8 @@ var create_button = function (element, html_id=null) { widget_data: widget_data, widget_id: 'pretix-widget-' + widget_id, html_id: html_id, - button_text: button_text + button_text: button_text, + keep_cart: keep_cart || items.length > 0, } }, created: function () { @@ -2426,7 +2458,7 @@ var create_button = function (element, html_id=null) { observer.observe(this.$el, observerOptions); }, computed: shared_root_computed, - methods: shared_root_methods + methods: shared_root_methods, }); create_overlay(app); return app; @@ -2492,13 +2524,14 @@ window.PretixWidget.open = function (target_url, voucher, subevent, items, widge frame_dismissed: false, widget_data: all_widget_data, widget_id: 'pretix-widget-' + widget_id, - button_text: "" + button_text: "", + keep_cart: true } }, created: function () { }, computed: shared_root_computed, - methods: shared_root_methods + methods: shared_root_methods, }); create_overlay(app); app.$nextTick(function () {