diff --git a/src/pretix/plugins/paypal2/payment.py b/src/pretix/plugins/paypal2/payment.py index 85cb1a8c58..01714d143b 100644 --- a/src/pretix/plugins/paypal2/payment.py +++ b/src/pretix/plugins/paypal2/payment.py @@ -30,6 +30,7 @@ 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 @@ -54,6 +55,7 @@ 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 ( @@ -585,6 +587,9 @@ 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 ' @@ -617,7 +622,13 @@ 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 ' diff --git a/src/pretix/plugins/paypal2/views.py b/src/pretix/plugins/paypal2/views.py index e92226b234..2245d82b80 100644 --- a/src/pretix/plugins/paypal2/views.py +++ b/src/pretix/plugins/paypal2/views.py @@ -477,29 +477,35 @@ 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) \ - and 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 + 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: + 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.info = json.dumps(sale.dict()) + payment.save(update_fields=['info']) + payment.confirm() + except Quota.QuotaExceededException: + pass + elif sale['status'] == 'APPROVED': + request.session['payment_paypal_oid'] = payment.info_data['id'] try: - payment.info = json.dumps(sale.dict()) - payment.save(update_fields=['info']) - payment.confirm() - except Quota.QuotaExceededException: - pass + payment.payment_provider.execute_payment(request, payment) + except PaymentException as e: + logger.exception('PayPal2 - Could not capture/execute_payment from Webhook: {}'.format(str(e))) return HttpResponse(status=200)