mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Stripe: Lock payment object while processing refund
This commit is contained in:
@@ -11,6 +11,7 @@ from django import forms
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
|
from django.db import transaction
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@@ -491,10 +492,12 @@ class StripeMethod(BasePaymentProvider):
|
|||||||
}
|
}
|
||||||
return template.render(ctx)
|
return template.render(ctx)
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
def execute_refund(self, refund: OrderRefund):
|
def execute_refund(self, refund: OrderRefund):
|
||||||
self._init_api()
|
self._init_api()
|
||||||
|
|
||||||
payment_info = refund.payment.info_data
|
payment_info = refund.payment.info_data
|
||||||
|
OrderPayment.objects.select_for_update().get(pk=refund.payment.pk)
|
||||||
|
|
||||||
if not payment_info:
|
if not payment_info:
|
||||||
raise PaymentException(_('No payment information found.'))
|
raise PaymentException(_('No payment information found.'))
|
||||||
|
|||||||
@@ -238,66 +238,67 @@ def charge_webhook(event, event_json, charge_id, rso):
|
|||||||
return HttpResponse('Order not found', status=200)
|
return HttpResponse('Order not found', status=200)
|
||||||
payment = None
|
payment = None
|
||||||
|
|
||||||
if not payment:
|
with transaction.atomic():
|
||||||
payment = order.payments.filter(
|
if not payment:
|
||||||
info__icontains=charge['id'],
|
payment = order.payments.filter(
|
||||||
provider__startswith='stripe',
|
info__icontains=charge['id'],
|
||||||
amount=prov._amount_to_decimal(charge['amount']),
|
provider__startswith='stripe',
|
||||||
).last()
|
amount=prov._amount_to_decimal(charge['amount']),
|
||||||
if not payment:
|
).select_for_update().last()
|
||||||
payment = order.payments.create(
|
if not payment:
|
||||||
state=OrderPayment.PAYMENT_STATE_CREATED,
|
payment = order.payments.create(
|
||||||
provider=SOURCE_TYPES.get(charge['source'].get('type', charge['source'].get('object', 'card')), 'stripe'),
|
state=OrderPayment.PAYMENT_STATE_CREATED,
|
||||||
amount=prov._amount_to_decimal(charge['amount']),
|
provider=SOURCE_TYPES.get(charge['source'].get('type', charge['source'].get('object', 'card')), 'stripe'),
|
||||||
info=str(charge),
|
amount=prov._amount_to_decimal(charge['amount']),
|
||||||
)
|
info=str(charge),
|
||||||
|
)
|
||||||
|
|
||||||
if payment.provider != prov.identifier:
|
if payment.provider != prov.identifier:
|
||||||
prov = payment.payment_provider
|
prov = payment.payment_provider
|
||||||
prov._init_api()
|
prov._init_api()
|
||||||
|
|
||||||
order.log_action('pretix.plugins.stripe.event', data=event_json)
|
order.log_action('pretix.plugins.stripe.event', data=event_json)
|
||||||
|
|
||||||
is_refund = charge['refunds']['total_count'] or charge['dispute']
|
is_refund = charge['refunds']['total_count'] or charge['dispute']
|
||||||
if is_refund:
|
if is_refund:
|
||||||
known_refunds = [r.info_data.get('id') for r in payment.refunds.all()]
|
known_refunds = [r.info_data.get('id') for r in payment.refunds.all()]
|
||||||
migrated_refund_amounts = [r.amount for r in payment.refunds.all() if not r.info_data.get('id')]
|
migrated_refund_amounts = [r.amount for r in payment.refunds.all() if not r.info_data.get('id')]
|
||||||
for r in charge['refunds']['data']:
|
for r in charge['refunds']['data']:
|
||||||
a = prov._amount_to_decimal(r['amount'])
|
a = prov._amount_to_decimal(r['amount'])
|
||||||
if r['status'] in ('failed', 'canceled'):
|
if r['status'] in ('failed', 'canceled'):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if a in migrated_refund_amounts:
|
|
||||||
migrated_refund_amounts.remove(a)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if r['id'] not in known_refunds:
|
|
||||||
payment.create_external_refund(
|
|
||||||
amount=a,
|
|
||||||
info=str(r)
|
|
||||||
)
|
|
||||||
if charge['dispute']:
|
|
||||||
if charge['dispute']['status'] != 'won' and charge['dispute']['id'] not in known_refunds:
|
|
||||||
a = prov._amount_to_decimal(charge['dispute']['amount'])
|
|
||||||
if a in migrated_refund_amounts:
|
if a in migrated_refund_amounts:
|
||||||
migrated_refund_amounts.remove(a)
|
migrated_refund_amounts.remove(a)
|
||||||
else:
|
continue
|
||||||
|
|
||||||
|
if r['id'] not in known_refunds:
|
||||||
payment.create_external_refund(
|
payment.create_external_refund(
|
||||||
amount=a,
|
amount=a,
|
||||||
info=str(charge['dispute'])
|
info=str(r)
|
||||||
)
|
)
|
||||||
elif charge['status'] == 'succeeded' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING,
|
if charge['dispute']:
|
||||||
OrderPayment.PAYMENT_STATE_CREATED,
|
if charge['dispute']['status'] != 'won' and charge['dispute']['id'] not in known_refunds:
|
||||||
OrderPayment.PAYMENT_STATE_CANCELED,
|
a = prov._amount_to_decimal(charge['dispute']['amount'])
|
||||||
OrderPayment.PAYMENT_STATE_FAILED):
|
if a in migrated_refund_amounts:
|
||||||
try:
|
migrated_refund_amounts.remove(a)
|
||||||
payment.confirm()
|
else:
|
||||||
except LockTimeoutException:
|
payment.create_external_refund(
|
||||||
return HttpResponse("Lock timeout, please try again.", status=503)
|
amount=a,
|
||||||
except Quota.QuotaExceededException:
|
info=str(charge['dispute'])
|
||||||
pass
|
)
|
||||||
elif charge['status'] == 'failed' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
|
elif charge['status'] == 'succeeded' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING,
|
||||||
payment.fail(info=str(charge))
|
OrderPayment.PAYMENT_STATE_CREATED,
|
||||||
|
OrderPayment.PAYMENT_STATE_CANCELED,
|
||||||
|
OrderPayment.PAYMENT_STATE_FAILED):
|
||||||
|
try:
|
||||||
|
payment.confirm()
|
||||||
|
except LockTimeoutException:
|
||||||
|
return HttpResponse("Lock timeout, please try again.", status=503)
|
||||||
|
except Quota.QuotaExceededException:
|
||||||
|
pass
|
||||||
|
elif charge['status'] == 'failed' and payment.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
|
||||||
|
payment.fail(info=str(charge))
|
||||||
|
|
||||||
return HttpResponse(status=200)
|
return HttpResponse(status=200)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user