feat(events): harden registration checkout lifecycle
This commit is contained in:
@@ -5,6 +5,8 @@ from ninja import ModelSchema, Schema
|
||||
from pydantic import field_validator
|
||||
from typing import Literal, Optional, List
|
||||
from datetime import datetime
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.blog.api.schemas import AuthorSchema
|
||||
from apps.events.models import Event, Registration
|
||||
@@ -85,7 +87,11 @@ class EventSchema(ModelSchema):
|
||||
|
||||
@staticmethod
|
||||
def resolve_registration_count(obj):
|
||||
return obj.registrations.filter(status__in=[Registration.StatusChoices.CONFIRMED, Registration.StatusChoices.ATTENDED]).count()
|
||||
now = timezone.now()
|
||||
return obj.registrations.filter(is_deleted=False).filter(
|
||||
Q(status__in=[Registration.StatusChoices.CONFIRMED, Registration.StatusChoices.ATTENDED])
|
||||
| Q(status=Registration.StatusChoices.PENDING, checkout_expires_at__gt=now)
|
||||
).count()
|
||||
|
||||
@staticmethod
|
||||
def resolve_description_html(obj):
|
||||
@@ -102,6 +108,7 @@ class EventListSchema(Schema):
|
||||
absolute_featured_image_thumbnail_url: Optional[str] = None
|
||||
absolute_featured_image_preview_url: Optional[str] = None
|
||||
event_type: str
|
||||
address: Optional[str] = None
|
||||
start_time: datetime
|
||||
end_time: datetime
|
||||
registration_start_date: Optional[datetime] = None
|
||||
@@ -133,7 +140,22 @@ class EventListSchema(Schema):
|
||||
|
||||
@staticmethod
|
||||
def resolve_registration_count(obj):
|
||||
return obj.registrations.filter(status__in=[Registration.StatusChoices.CONFIRMED, Registration.StatusChoices.ATTENDED]).count()
|
||||
if hasattr(obj, "browse_registration_count"):
|
||||
return obj.browse_registration_count
|
||||
now = timezone.now()
|
||||
return obj.registrations.filter(is_deleted=False).filter(
|
||||
Q(status__in=[Registration.StatusChoices.CONFIRMED, Registration.StatusChoices.ATTENDED])
|
||||
| Q(status=Registration.StatusChoices.PENDING, checkout_expires_at__gt=now)
|
||||
).count()
|
||||
|
||||
|
||||
class PaginatedEventBrowseSchema(Schema):
|
||||
count: int
|
||||
page: int
|
||||
page_size: int
|
||||
total_pages: int
|
||||
price_ceiling: int
|
||||
results: List[EventListSchema]
|
||||
|
||||
class EventCreateSchema(Schema):
|
||||
"""Payload for creating events via the API."""
|
||||
@@ -185,6 +207,9 @@ class RegistrationSchema(ModelSchema):
|
||||
'id',
|
||||
'status',
|
||||
'registered_at',
|
||||
'checkout_started_at',
|
||||
'checkout_expires_at',
|
||||
'cancellation_reason',
|
||||
'ticket_id',
|
||||
'discount_amount',
|
||||
'final_price',
|
||||
@@ -326,9 +351,16 @@ class EventBriefSchema(Schema):
|
||||
class MyEventRegistrationOut(Schema):
|
||||
"""Registration information as returned to authenticated users."""
|
||||
id: int
|
||||
ticket_id: UUID
|
||||
created_at: datetime
|
||||
status: Literal["pending", "confirmed", "cancelled", "attended"]
|
||||
event: EventBriefSchema
|
||||
checkout_state: Literal["none", "active", "legacy_pending", "expired", "unresolved", "cancelled"] = "none"
|
||||
checkout_expires_at: Optional[datetime] = None
|
||||
payment_id: Optional[int] = None
|
||||
payment_status: Optional[Literal["initiated", "pending", "paid", "failed", "canceled"]] = None
|
||||
refund_status: Optional[Literal["none", "pending", "succeeded", "manual_review"]] = None
|
||||
can_resume: bool = False
|
||||
|
||||
|
||||
class PaginatedMyEventRegistrationsSchema(Schema):
|
||||
@@ -340,6 +372,79 @@ class PaginatedMyEventRegistrationsSchema(Schema):
|
||||
|
||||
class RegistrationStatusOut(Schema):
|
||||
is_registered: bool
|
||||
checkout_state: Literal[
|
||||
"none", "confirmed", "active", "legacy_pending", "expired", "unresolved", "cancelled"
|
||||
] = "none"
|
||||
registration_id: Optional[int] = None
|
||||
registration_status: Optional[str] = None
|
||||
ticket_id: Optional[UUID] = None
|
||||
checkout_expires_at: Optional[datetime] = None
|
||||
payment_id: Optional[int] = None
|
||||
payment_status: Optional[Literal["initiated", "pending", "paid", "failed", "canceled"]] = None
|
||||
refund_status: Optional[Literal["none", "pending", "succeeded", "manual_review"]] = None
|
||||
can_resume: bool = False
|
||||
can_cancel: bool = False
|
||||
|
||||
|
||||
class RegistrationHubEventOut(Schema):
|
||||
id: int
|
||||
title: str
|
||||
slug: str
|
||||
description: str
|
||||
event_type: str
|
||||
address: Optional[str] = None
|
||||
location: Optional[str] = None
|
||||
start_time: datetime
|
||||
end_time: datetime
|
||||
registration_start_date: Optional[datetime] = None
|
||||
registration_end_date: Optional[datetime] = None
|
||||
capacity: Optional[int] = None
|
||||
price: int
|
||||
status: str
|
||||
registration_count: int
|
||||
image_url: Optional[str] = None
|
||||
registration_success_markdown: Optional[str] = None
|
||||
|
||||
|
||||
class RegistrationHubRegistrationOut(Schema):
|
||||
ticket_id: UUID
|
||||
status: str
|
||||
registered_at: datetime
|
||||
checkout_started_at: Optional[datetime] = None
|
||||
checkout_expires_at: Optional[datetime] = None
|
||||
cancellation_reason: str = ""
|
||||
final_price: Optional[int] = None
|
||||
discount_amount: int = 0
|
||||
|
||||
|
||||
class RegistrationHubPaymentOut(Schema):
|
||||
id: int
|
||||
status: Literal["initiated", "pending", "paid", "failed", "canceled"]
|
||||
verification_state: Literal[
|
||||
"awaiting_verification", "checking", "retry_scheduled", "manual_review",
|
||||
"verified", "failed", "canceled",
|
||||
]
|
||||
refund_status: Literal["none", "pending", "succeeded", "manual_review"]
|
||||
base_amount: int
|
||||
discount_amount: int
|
||||
amount: int
|
||||
ref_id: Optional[str] = None
|
||||
created_at: datetime
|
||||
gateway_paid_at: Optional[datetime] = None
|
||||
|
||||
|
||||
class RegistrationHubOut(Schema):
|
||||
state: Literal[
|
||||
"ready", "active_checkout", "legacy_pending", "verification_pending",
|
||||
"manual_review", "paid_success", "free_success", "failed", "cancelled",
|
||||
"expired", "refund_pending", "refund_manual_review", "refund_succeeded",
|
||||
]
|
||||
event: RegistrationHubEventOut
|
||||
registration: Optional[RegistrationHubRegistrationOut] = None
|
||||
payment: Optional[RegistrationHubPaymentOut] = None
|
||||
can_register: bool = False
|
||||
can_resume: bool = False
|
||||
can_cancel: bool = False
|
||||
|
||||
|
||||
class RegistrationCreateSchema(Schema):
|
||||
|
||||
Reference in New Issue
Block a user