fix(events): keep registration status reads nonblocking
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
"""Registration checkout lifecycle rules shared by API views and workers."""
|
||||
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
|
||||
from django.db import transaction
|
||||
@@ -10,16 +9,6 @@ from apps.events.models import Event, Registration
|
||||
|
||||
|
||||
CHECKOUT_TIMEOUT = timedelta(minutes=15)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _dispatch_reconciliation(payment_id):
|
||||
from apps.payments.tasks import reconcile_payment
|
||||
|
||||
try:
|
||||
reconcile_payment.apply_async(args=(payment_id,), countdown=0)
|
||||
except Exception:
|
||||
logger.exception("Unable to dispatch checkout payment %s for reconciliation", payment_id)
|
||||
|
||||
|
||||
def mark_checkout_cancelled(registration, reason):
|
||||
@@ -38,7 +27,7 @@ def mark_checkout_cancelled(registration, reason):
|
||||
|
||||
|
||||
def queue_checkout_reconciliation(registration):
|
||||
"""Reconcile each unresolved payment after a checkout is cancelled."""
|
||||
"""Mark no-authority attempts for review; the recovery sweep dispatches the rest."""
|
||||
from apps.payments.models import Payment
|
||||
|
||||
payments = list(
|
||||
@@ -51,11 +40,7 @@ def queue_checkout_reconciliation(registration):
|
||||
).only("id", "authority")
|
||||
)
|
||||
for payment in payments:
|
||||
if payment.authority:
|
||||
transaction.on_commit(
|
||||
lambda payment_id=payment.id: _dispatch_reconciliation(payment_id)
|
||||
)
|
||||
else:
|
||||
if not payment.authority:
|
||||
# The payment request may have reached the gateway before its
|
||||
# authority was persisted. Keep it visible for review until its
|
||||
# request response arrives or an administrator resolves it.
|
||||
@@ -69,7 +54,6 @@ def queue_checkout_reconciliation(registration):
|
||||
).update(verification_manual_review_at=timezone.now())
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def expire_stale_registration_checkouts(*, now=None, event_id=None):
|
||||
"""Release seats for new-format pending checkouts whose deadline passed.
|
||||
|
||||
@@ -85,27 +69,48 @@ def expire_stale_registration_checkouts(*, now=None, event_id=None):
|
||||
if event_id is not None:
|
||||
candidates = candidates.filter(event_id=event_id)
|
||||
|
||||
expired_count = 0
|
||||
for registration_id in candidates.values_list("id", flat=True).iterator():
|
||||
registration = (
|
||||
Registration.objects.select_for_update()
|
||||
.filter(pk=registration_id)
|
||||
.first()
|
||||
with transaction.atomic():
|
||||
batch = list(
|
||||
candidates.select_for_update(skip_locked=True)
|
||||
.order_by("checkout_expires_at", "pk")[:50]
|
||||
)
|
||||
if registration is None or registration.status != Registration.StatusChoices.PENDING:
|
||||
continue
|
||||
expired_count = 0
|
||||
for registration in batch:
|
||||
if registration.status != Registration.StatusChoices.PENDING or registration.checkout_expires_at > now:
|
||||
continue
|
||||
reason = (
|
||||
Registration.CancellationReasonChoices.EVENT_CANCELLED
|
||||
if registration.event.status == Event.StatusChoices.CANCELLED
|
||||
else Registration.CancellationReasonChoices.CHECKOUT_EXPIRED
|
||||
)
|
||||
if mark_checkout_cancelled(registration, reason):
|
||||
expired_count += 1
|
||||
queue_checkout_reconciliation(registration)
|
||||
return expired_count
|
||||
|
||||
|
||||
def expire_user_registration_checkouts(*, event, user, now):
|
||||
"""Expire only this user's rows while the caller holds the event lock."""
|
||||
registrations = (
|
||||
Registration.objects.select_for_update()
|
||||
.filter(
|
||||
event=event,
|
||||
user=user,
|
||||
is_deleted=False,
|
||||
status=Registration.StatusChoices.PENDING,
|
||||
checkout_expires_at__lte=now,
|
||||
)
|
||||
.order_by("pk")
|
||||
)
|
||||
for registration in registrations:
|
||||
reason = (
|
||||
Registration.CancellationReasonChoices.EVENT_CANCELLED
|
||||
if registration.event.status == Event.StatusChoices.CANCELLED
|
||||
if event.status == Event.StatusChoices.CANCELLED
|
||||
else Registration.CancellationReasonChoices.CHECKOUT_EXPIRED
|
||||
)
|
||||
if mark_checkout_cancelled(registration, reason):
|
||||
expired_count += 1
|
||||
queue_checkout_reconciliation(registration)
|
||||
|
||||
return expired_count
|
||||
|
||||
|
||||
@transaction.atomic
|
||||
def cancel_event_checkouts(event_id):
|
||||
|
||||
Reference in New Issue
Block a user