forked from CGM_Public/pretix_original
Improved logging and transaction handling around payment confirmations
This commit is contained in:
@@ -1285,6 +1285,9 @@ class OrderPayment(models.Model):
|
|||||||
locked_instance = OrderPayment.objects.select_for_update().get(pk=self.pk)
|
locked_instance = OrderPayment.objects.select_for_update().get(pk=self.pk)
|
||||||
if locked_instance.state == self.PAYMENT_STATE_CONFIRMED:
|
if locked_instance.state == self.PAYMENT_STATE_CONFIRMED:
|
||||||
# Race condition detected, this payment is already confirmed
|
# Race condition detected, this payment is already confirmed
|
||||||
|
logger.info('Confirmed payment {} but ignored due to likely race condition.'.format(
|
||||||
|
self.full_id,
|
||||||
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
locked_instance.state = self.PAYMENT_STATE_CONFIRMED
|
locked_instance.state = self.PAYMENT_STATE_CONFIRMED
|
||||||
@@ -1305,6 +1308,7 @@ class OrderPayment(models.Model):
|
|||||||
}, user=user, auth=auth)
|
}, user=user, auth=auth)
|
||||||
|
|
||||||
if self.order.status in (Order.STATUS_PAID, Order.STATUS_CANCELED):
|
if self.order.status in (Order.STATUS_PAID, Order.STATUS_CANCELED):
|
||||||
|
logger.info('Confirmed payment {} but order is in status {}.'.format(self.full_id, self.order.status))
|
||||||
return
|
return
|
||||||
|
|
||||||
payment_sum = self.order.payments.filter(
|
payment_sum = self.order.payments.filter(
|
||||||
@@ -1315,6 +1319,9 @@ class OrderPayment(models.Model):
|
|||||||
OrderRefund.REFUND_STATE_CREATED)
|
OrderRefund.REFUND_STATE_CREATED)
|
||||||
).aggregate(s=Sum('amount'))['s'] or Decimal('0.00')
|
).aggregate(s=Sum('amount'))['s'] or Decimal('0.00')
|
||||||
if payment_sum - refund_sum < self.order.total:
|
if payment_sum - refund_sum < self.order.total:
|
||||||
|
logger.info('Confirmed payment {} but payment sum is {} and refund sum is.'.format(
|
||||||
|
self.full_id, payment_sum, refund_sum
|
||||||
|
))
|
||||||
return
|
return
|
||||||
|
|
||||||
if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12)) or not lock:
|
if (self.order.status == Order.STATUS_PENDING and self.order.expires > now() + timedelta(hours=12)) or not lock:
|
||||||
|
|||||||
@@ -687,7 +687,7 @@ class BasePaymentProvider:
|
|||||||
On failure, you should raise a PaymentException.
|
On failure, you should raise a PaymentException.
|
||||||
"""
|
"""
|
||||||
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
payment.state = OrderPayment.PAYMENT_STATE_CANCELED
|
||||||
payment.save()
|
payment.save(update_fields=['state'])
|
||||||
|
|
||||||
def execute_refund(self, refund: OrderRefund):
|
def execute_refund(self, refund: OrderRefund):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from django.core.cache import cache
|
|||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.models import Exists, F, Max, Min, OuterRef, Q, Sum
|
from django.db.models import Exists, F, Max, Min, OuterRef, Q, Sum
|
||||||
from django.db.models.functions import Coalesce, Greatest
|
from django.db.models.functions import Coalesce, Greatest
|
||||||
|
from django.db.transaction import get_connection
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.timezone import make_aware, now
|
from django.utils.timezone import make_aware, now
|
||||||
@@ -1863,6 +1864,9 @@ def cancel_order(self, order: int, user: int=None, send_mail: bool=True, api_tok
|
|||||||
|
|
||||||
def change_payment_provider(order: Order, payment_provider, amount=None, new_payment=None, create_log=True,
|
def change_payment_provider(order: Order, payment_provider, amount=None, new_payment=None, create_log=True,
|
||||||
recreate_invoices=True):
|
recreate_invoices=True):
|
||||||
|
if not get_connection().in_atomic_block:
|
||||||
|
raise Exception('change_payment_provider should only be called in atomic transaction!')
|
||||||
|
|
||||||
oldtotal = order.total
|
oldtotal = order.total
|
||||||
e = OrderPayment.objects.filter(fee=OuterRef('pk'), state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED,
|
e = OrderPayment.objects.filter(fee=OuterRef('pk'), state__in=(OrderPayment.PAYMENT_STATE_CONFIRMED,
|
||||||
OrderPayment.PAYMENT_STATE_REFUNDED))
|
OrderPayment.PAYMENT_STATE_REFUNDED))
|
||||||
@@ -1883,34 +1887,32 @@ def change_payment_provider(order: Order, payment_provider, amount=None, new_pay
|
|||||||
new_fee = payment_provider.calculate_fee(
|
new_fee = payment_provider.calculate_fee(
|
||||||
order.pending_sum - old_fee if amount is None else amount
|
order.pending_sum - old_fee if amount is None else amount
|
||||||
)
|
)
|
||||||
with transaction.atomic():
|
if new_fee:
|
||||||
if new_fee:
|
fee.value = new_fee
|
||||||
fee.value = new_fee
|
fee.internal_type = payment_provider.identifier
|
||||||
fee.internal_type = payment_provider.identifier
|
fee._calculate_tax()
|
||||||
fee._calculate_tax()
|
fee.save()
|
||||||
fee.save()
|
else:
|
||||||
else:
|
if fee.pk:
|
||||||
if fee.pk:
|
fee.delete()
|
||||||
fee.delete()
|
fee = None
|
||||||
fee = None
|
|
||||||
|
|
||||||
open_payment = None
|
open_payment = None
|
||||||
if new_payment:
|
if new_payment:
|
||||||
lp = order.payments.exclude(pk=new_payment.pk).last()
|
lp = order.payments.select_for_update().exclude(pk=new_payment.pk).last()
|
||||||
else:
|
else:
|
||||||
lp = order.payments.last()
|
lp = order.payments.select_for_update().last()
|
||||||
if lp and lp.state not in (OrderPayment.PAYMENT_STATE_CONFIRMED, OrderPayment.PAYMENT_STATE_REFUNDED):
|
|
||||||
|
if lp and lp.state in (OrderPayment.PAYMENT_STATE_PENDING, OrderPayment.PAYMENT_STATE_CREATED):
|
||||||
open_payment = lp
|
open_payment = lp
|
||||||
|
|
||||||
if open_payment and open_payment.state in (OrderPayment.PAYMENT_STATE_PENDING,
|
if open_payment:
|
||||||
OrderPayment.PAYMENT_STATE_CREATED):
|
|
||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
open_payment.payment_provider.cancel_payment(open_payment)
|
||||||
open_payment.payment_provider.cancel_payment(open_payment)
|
order.log_action('pretix.event.order.payment.canceled', {
|
||||||
order.log_action('pretix.event.order.payment.canceled', {
|
'local_id': open_payment.local_id,
|
||||||
'local_id': open_payment.local_id,
|
'provider': open_payment.provider,
|
||||||
'provider': open_payment.provider,
|
})
|
||||||
})
|
|
||||||
except PaymentException as e:
|
except PaymentException as e:
|
||||||
order.log_action(
|
order.log_action(
|
||||||
'pretix.event.order.payment.canceled.failed',
|
'pretix.event.order.payment.canceled.failed',
|
||||||
|
|||||||
Reference in New Issue
Block a user