Compare commits

..
9 changed files with 68 additions and 55 deletions
+1 -1
View File
@@ -203,7 +203,7 @@ error_messages = {
'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.',
'min'
),
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
'addon_no_multi': gettext_lazy('You can select every add-on from the category %(cat)s for the product %(base)s at most once.'),
'addon_only': gettext_lazy('One of the products you selected can only be bought as an add-on to another product.'),
'bundled_only': gettext_lazy('One of the products you selected can only be bought part of a bundle.'),
'seat_required': gettext_lazy('You need to select a specific seat.'),
+1 -1
View File
@@ -197,7 +197,7 @@ error_messages = {
'You need to select at least %(min)s add-ons from the category %(cat)s for the product %(base)s.',
'min'
),
'addon_no_multi': gettext_lazy('You can select every add-ons from the category %(cat)s for the product %(base)s at most once.'),
'addon_no_multi': gettext_lazy('You can select every add-on from the category %(cat)s for the product %(base)s at most once.'),
'addon_already_checked_in': gettext_lazy('You cannot remove the position %(addon)s since it has already been checked in.'),
}
-11
View File
@@ -31,7 +31,6 @@ from django import forms
from django.conf import settings
from django.contrib import messages
from django.core.cache import cache
from django.db import transaction
from django.http import HttpRequest
from django.template.loader import get_template
from django.templatetags.static import static
@@ -56,7 +55,6 @@ from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
from pretix.base.payment import BasePaymentProvider, PaymentException
from pretix.base.services.mail import SendMailException
from pretix.base.settings import SettingsSandbox
from pretix.helpers import OF_SELF
from pretix.helpers.urls import build_absolute_uri as build_global_uri
from pretix.multidomain.urlreverse import build_absolute_uri, eventreverse
from pretix.plugins.paypal2.client.core.environment import (
@@ -588,9 +586,6 @@ class PaypalMethod(BasePaymentProvider):
},
})
response = self.client.execute(paymentreq)
if payment:
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment, reference=response.result.id)
except IOError as e:
if "RESOURCE_NOT_FOUND" in str(e):
messages.error(request, _('Your payment has failed due to a known issue within PayPal. Please try '
@@ -623,13 +618,7 @@ class PaypalMethod(BasePaymentProvider):
}
return template.render(ctx)
@transaction.atomic
def execute_payment(self, request: HttpRequest, payment: OrderPayment):
payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=payment.pk)
if payment.state == OrderPayment.PAYMENT_STATE_CONFIRMED:
logger.warning('payment is already confirmed; possible return-view/webhook race-condition')
return
try:
if request.session.get('payment_paypal_oid', '') == '':
raise PaymentException(_('We were unable to process your payment. See below for details on how to '
+20 -26
View File
@@ -482,35 +482,29 @@ def webhook(request, *args, **kwargs):
amount=payment.amount - known_sum
)
elif payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED,
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED):
if sale['status'] == 'COMPLETED':
any_captures = False
all_captures_completed = True
for purchaseunit in sale['purchase_units']:
for capture in purchaseunit['payments']['captures']:
try:
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment,
reference=capture['id'])
except ReferencedPayPalObject.MultipleObjectsReturned:
pass
if capture['status'] not in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
all_captures_completed = False
else:
any_captures = True
if any_captures and all_captures_completed:
OrderPayment.PAYMENT_STATE_CANCELED, OrderPayment.PAYMENT_STATE_FAILED) \
and sale['status'] == 'COMPLETED':
any_captures = False
all_captures_completed = True
for purchaseunit in sale['purchase_units']:
for capture in purchaseunit['payments']['captures']:
try:
payment.info = json.dumps(sale.dict())
payment.save(update_fields=['info'])
payment.confirm()
except Quota.QuotaExceededException:
ReferencedPayPalObject.objects.get_or_create(order=payment.order, payment=payment,
reference=capture['id'])
except ReferencedPayPalObject.MultipleObjectsReturned:
pass
elif sale['status'] == 'APPROVED':
request.session['payment_paypal_oid'] = payment.info_data['id']
if capture['status'] not in ('COMPLETED', 'REFUNDED', 'PARTIALLY_REFUNDED'):
all_captures_completed = False
else:
any_captures = True
if any_captures and all_captures_completed:
try:
payment.payment_provider.execute_payment(request, payment)
except PaymentException as e:
logger.exception('PayPal2 - Could not capture/execute_payment from Webhook: {}'.format(str(e)))
payment.info = json.dumps(sale.dict())
payment.save(update_fields=['info'])
payment.confirm()
except Quota.QuotaExceededException:
pass
return HttpResponse(status=200)
@@ -6,13 +6,13 @@
{% load eventsignal %}
{% load rich_text %}
{% for c in form.categories %}
<fieldset>
<fieldset data-addon-max-count="{{ c.max_count }}"{% if c.multi_allowed %} data-addon-multi-allowed{% endif %}>
<legend>{{ c.category.name }}</legend>
{% if c.category.description %}
{{ c.category.description|rich_text }}
{% endif %}
{% if c.min_count == c.max_count %}
<p>
<p class="addon-count-desc">
{% blocktrans trimmed count min_count=c.min_count %}
You need to choose exactly one option from this category.
{% plural %}
@@ -21,7 +21,7 @@
</p>
{% elif c.min_count == 0 and c.max_count >= c.items|length and not c.multi_allowed %}
{% elif c.min_count == 0 %}
<p>
<p class="addon-count-desc">
{% blocktrans trimmed count max_count=c.max_count %}
You can choose {{ max_count }} option from this category.
{% plural %}
@@ -29,7 +29,7 @@
{% endblocktrans %}
</p>
{% else %}
<p>
<p class="addon-count-desc">
{% blocktrans trimmed with min_count=c.min_count max_count=c.max_count %}
You can choose between {{ min_count }} and {{ max_count }} options from
this category.
@@ -193,7 +193,6 @@
{% endif %}
id="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
name="cp_{{ form.pos.pk }}_variation_{{ item.id }}_{{ var.id }}"
data-exclusive-prefix="cp_{{ form.pos.pk }}_variation_{{ item.id }}_"
aria-label="{% blocktrans with item=item.name var=var %}Add {{ item }}, {{ var }} to cart{% endblocktrans %}">
<i class="fa fa-shopping-cart" aria-hidden="true"></i>
{% trans "Select" context "checkbox" %}
@@ -239,7 +239,7 @@
<ul>
{% for i in invoices %}
<li>
<a href="{% eventurl event "presale:event.invoice.download" invoice=i.pk secret=order.secret order=order.code %}">
<a href="{% eventurl event "presale:event.invoice.download" invoice=i.pk secret=order.secret order=order.code %}" target="_blank">
{% if i.is_cancellation %}{% trans "Cancellation" context "invoice" %}{% else %}{% trans "Invoice" %}{% endif %}
{{ i.number }}</a> ({{ i.date|date:"SHORT_DATE_FORMAT" }})
</li>
@@ -625,6 +625,7 @@ var form_handlers = function (el) {
el.find('[data-model-select2=event]').each(function () {
var $s = $(this);
$s.select2({
closeOnSelect: !this.hasAttribute('multiple'),
theme: "bootstrap",
delay: 100,
allowClear: !$s.prop("required"),
@@ -904,8 +904,8 @@ details {
}
}
.select2-container [aria-multiselectable] .select2-results__option span strike::before,
.select2-container [aria-multiselectable] .select2-results__option span span::before {
.select2-container [aria-multiselectable] .select2-results__option > span > strike:first-child::before,
.select2-container [aria-multiselectable] .select2-results__option > span > span:first-child::before {
content: "";
font-family: FontAwesome;
display: inline-block;
@@ -913,8 +913,8 @@ details {
width: 1.28571em;
text-align: center;
}
.select2-container [aria-multiselectable] .select2-results__option[aria-selected=true] span strike::before,
.select2-container [aria-multiselectable] .select2-results__option[aria-selected=true] span span::before {
.select2-container [aria-multiselectable] .select2-results__option[aria-selected=true] > span > strike:first-child::before,
.select2-container [aria-multiselectable] .select2-results__option[aria-selected=true] > span > span:first-child::before {
content: ""
}
+36 -6
View File
@@ -123,7 +123,7 @@ var form_handlers = function (el) {
var controls = document.getElementById(this.getAttribute("data-controls"));
var currentValue = parseFloat(controls.value);
controls.value = Math.max(controls.min, Math.min(controls.max || Number.MAX_SAFE_INTEGER, (currentValue || 0) + step));
controls.dispatchEvent(new Event("change"));
controls.dispatchEvent(new Event("change", { bubbles: true }));
});
el.find(".btn-checkbox input").on("change", function (e) {
$(this).closest(".btn-checkbox")
@@ -149,11 +149,41 @@ var form_handlers = function (el) {
).find("canvas").attr("role", "img").attr("aria-label", this.getAttribute("data-desc"));
});
el.find("input[data-exclusive-prefix]").each(function () {
var $others = $("input[name^=" + $(this).attr("data-exclusive-prefix") + "]:not([name=" + $(this).attr("name") + "])");
$(this).on('click change', function () {
if ($(this).prop('checked')) {
$others.prop('checked', false);
el.find("fieldset[data-addon-max-count]").each(function() {
// usually addons are only allowed once one per item
var multipleAllowed = this.hasAttribute("data-addon-multi-allowed");
var $inputs = $(".availability-box input", this);
var max = parseInt(this.getAttribute("data-addon-max-count"));
var desc = $(".addon-count-desc", this).text().trim();
this.addEventListener("change", function (e) {
var variations = e.target.closest(".variations");
if (variations && !multipleAllowed && e.target.checked) {
// uncheck all other checkboxes inside this variations
$(".availability-box input:checked", variations).not(e.target).prop("checked", false).trigger("change");
}
if (max === 1) {
if (e.target.checked) {
$inputs.filter(":checked").not(e.target).prop("checked", false).trigger("change");
}
return;
}
var total = $inputs.toArray().reduce(function(a, e) {
return a + (e.type == "checkbox" ? (e.checked ? parseInt(e.value) : 0) : parseInt(e.value) || 0);
}, 0);
if (total > max) {
if (e.target.type == "checkbox") {
e.target.checked = false;
} else {
e.target.value = e.target.value - (total - max);
}
$(e.target).trigger("change").closest(".availability-box").tooltip({
"title": desc,
}).tooltip('show');
e.preventDefault();
} else {
$(".availability-box", this).tooltip('destroy')
}
});
});