fix(payments): make checkout attempts idempotent and reconcilable
This commit is contained in:
73
apps/payments/tasks.py
Normal file
73
apps/payments/tasks.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Bounded retries for payment verification after an inconclusive callback."""
|
||||
|
||||
import logging
|
||||
|
||||
from celery import shared_task
|
||||
from django.shortcuts import get_object_or_404
|
||||
|
||||
from apps.payments.models import Payment
|
||||
from apps.payments.services import PaymentVerificationUncertain, request_payment_verification
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@shared_task(bind=True, max_retries=5, name="apps.payments.reconcile_payment")
|
||||
def reconcile_payment(self, payment_id: int):
|
||||
payment = get_object_or_404(
|
||||
Payment.objects.select_related("event", "user"),
|
||||
pk=payment_id,
|
||||
)
|
||||
if payment.status in (Payment.OrderStatusChoices.PAID, Payment.OrderStatusChoices.FAILED):
|
||||
return payment.get_status_display().lower()
|
||||
if not payment.authority:
|
||||
logger.error(
|
||||
"Payment %s remains pending without a gateway authority; manual review is required",
|
||||
payment.id,
|
||||
)
|
||||
return "pending_manual_review"
|
||||
|
||||
# Imported at task runtime to avoid a view/task import cycle.
|
||||
from apps.payments.api.views import (
|
||||
_mark_payment_paid,
|
||||
_mark_payment_pending,
|
||||
_mark_payment_unsuccessful,
|
||||
_notify_payment_status,
|
||||
)
|
||||
|
||||
try:
|
||||
code, data = request_payment_verification(payment)
|
||||
except PaymentVerificationUncertain as exc:
|
||||
_mark_payment_pending(payment.id)
|
||||
if self.request.retries >= self.max_retries:
|
||||
logger.error(
|
||||
"Payment %s verification remains uncertain after bounded retries; manual review is required",
|
||||
payment.id,
|
||||
exc_info=exc,
|
||||
)
|
||||
return "pending_manual_review"
|
||||
countdown = min(60 * (2 ** self.request.retries), 15 * 60)
|
||||
raise self.retry(exc=exc, countdown=countdown)
|
||||
|
||||
if code in (100, 101):
|
||||
payment, newly_verified = _mark_payment_paid(payment.id, data)
|
||||
if newly_verified:
|
||||
_notify_payment_status(
|
||||
payment,
|
||||
title=f"پرداخت رویداد «{payment.event.title}» تأیید شد",
|
||||
message="پرداخت شما با موفقیت ثبت شد و ثبتنام رویداد تکمیل شده است.",
|
||||
level="success",
|
||||
)
|
||||
return "paid"
|
||||
|
||||
payment, result, transitioned = _mark_payment_unsuccessful(
|
||||
payment.id,
|
||||
Payment.OrderStatusChoices.FAILED,
|
||||
)
|
||||
if transitioned:
|
||||
_notify_payment_status(
|
||||
payment,
|
||||
title=f"پرداخت رویداد «{payment.event.title}» ناموفق بود",
|
||||
message="تراکنش شما توسط درگاه تأیید نشد. در صورت نیاز دوباره پرداخت را انجام دهید.",
|
||||
level="error",
|
||||
)
|
||||
return result
|
||||
Reference in New Issue
Block a user