PayPal: Prevent race condition between refund and incoming webhook (Z#23185186) (#4911)

This commit is contained in:
Raphael Michel
2025-03-24 18:10:58 +01:00
committed by GitHub
parent de9a86c614
commit 8ec8b69755
2 changed files with 67 additions and 60 deletions

View File

@@ -916,7 +916,9 @@ class PaypalMethod(BasePaymentProvider):
def execute_refund(self, refund: OrderRefund): def execute_refund(self, refund: OrderRefund):
self.init_api() self.init_api()
with transaction.atomic():
# Lock payment that we are creating refund for to prevent race condition with incoming webhook
OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=refund.payment_id)
try: try:
pp_payment = None pp_payment = None
payment_info_data = None payment_info_data = None

View File

@@ -38,6 +38,7 @@ from decimal import Decimal
from django.contrib import messages from django.contrib import messages
from django.core import signing from django.core import signing
from django.core.cache import cache from django.core.cache import cache
from django.db import transaction
from django.db.models import Sum from django.db.models import Sum
from django.http import ( from django.http import (
Http404, HttpResponse, HttpResponseBadRequest, JsonResponse, Http404, HttpResponse, HttpResponseBadRequest, JsonResponse,
@@ -62,6 +63,7 @@ from pretix.base.payment import PaymentException
from pretix.base.services.cart import add_payment_to_cart, get_fees from pretix.base.services.cart import add_payment_to_cart, get_fees
from pretix.base.settings import GlobalSettingsObject from pretix.base.settings import GlobalSettingsObject
from pretix.control.permissions import event_permission_required from pretix.control.permissions import event_permission_required
from pretix.helpers import OF_SELF
from pretix.helpers.http import redirect_to_url from pretix.helpers.http import redirect_to_url
from pretix.multidomain.urlreverse import eventreverse from pretix.multidomain.urlreverse import eventreverse
from pretix.plugins.paypal2.client.customer.partners_merchantintegrations_get_request import ( from pretix.plugins.paypal2.client.customer.partners_merchantintegrations_get_request import (
@@ -450,6 +452,9 @@ def webhook(request, *args, **kwargs):
logger.exception('PayPal error on webhook. Event data: %s' % str(event_json)) logger.exception('PayPal error on webhook. Event data: %s' % str(event_json))
return HttpResponse('Refund not found', status=500) return HttpResponse('Refund not found', status=500)
with transaction.atomic():
# Lock payment in case a refund is currently still running
payment = OrderPayment.objects.select_for_update(of=OF_SELF).get(pk=payment.pk)
known_refunds = {r.info_data.get('id'): r for r in payment.refunds.all()} known_refunds = {r.info_data.get('id'): r for r in payment.refunds.all()}
if refund['id'] not in known_refunds: if refund['id'] not in known_refunds:
payment.create_external_refund( payment.create_external_refund(