feat(events): harden registration checkout lifecycle

This commit is contained in:
2026-10-01 12:26:49 +03:30
parent 1daa755543
commit 962238c32c
29 changed files with 3048 additions and 207 deletions

View File

@@ -81,6 +81,12 @@ class Payment(BaseModel):
FAILED = 3, "Failed"
CANCELED = 4, "Canceled"
class RefundStatusChoices(models.TextChoices):
NONE = "none", "No refund required"
PENDING = "pending", "Refund pending"
SUCCEEDED = "succeeded", "Refund completed"
MANUAL_REVIEW = "manual_review", "Refund needs review"
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT, related_name='payments', editable=False)
event = models.ForeignKey(Event, on_delete=models.PROTECT, related_name='payments', editable=False)
@@ -104,6 +110,24 @@ class Payment(BaseModel):
card_pan = models.CharField(max_length=32, null=True, blank=True, editable=False)
card_hash = models.CharField(max_length=128, null=True, blank=True, editable=False)
verified_at = models.DateTimeField(null=True, blank=True, editable=False)
gateway_paid_at = models.DateTimeField(null=True, blank=True, editable=False)
verification_attempts = models.PositiveSmallIntegerField(default=0, editable=False)
verification_last_attempt_at = models.DateTimeField(null=True, blank=True, editable=False)
verification_next_attempt_at = models.DateTimeField(null=True, blank=True, editable=False)
verification_lease_until = models.DateTimeField(null=True, blank=True, editable=False)
verification_dispatch_expires_at = models.DateTimeField(null=True, blank=True, editable=False)
verification_manual_review_at = models.DateTimeField(null=True, blank=True, editable=False)
refund_status = models.CharField(
max_length=20,
choices=RefundStatusChoices.choices,
default=RefundStatusChoices.NONE,
editable=False,
)
refund_attempts = models.PositiveSmallIntegerField(default=0, editable=False)
refund_last_attempt_at = models.DateTimeField(null=True, blank=True, editable=False)
refund_next_attempt_at = models.DateTimeField(null=True, blank=True, editable=False)
refund_reference = models.CharField(max_length=64, null=True, blank=True, editable=False)
refund_last_error = models.CharField(max_length=255, blank=True, default='', editable=False)
class Meta:
constraints = [
@@ -127,5 +151,24 @@ class Payment(BaseModel):
"""Human-readable label for the payment status."""
return self.get_status_display()
@property
def verification_state(self):
"""Server-owned verification state for callback and support screens."""
if self.status == self.OrderStatusChoices.PAID or self.verified_at is not None:
return "verified"
if self.status == self.OrderStatusChoices.FAILED:
return "failed"
if self.status == self.OrderStatusChoices.CANCELED:
return "canceled"
if self.verification_manual_review_at is not None or not self.authority:
return "manual_review"
now = timezone.now()
if self.verification_lease_until and self.verification_lease_until > now:
return "checking"
if self.verification_next_attempt_at:
return "retry_scheduled"
return "awaiting_verification"
def __str__(self):
return f"{self.user.email}:{self.event} - {self.get_status_display()}"