from django.db import models, transaction from django.utils import timezone from statuslog.signals import status_changed from .models import Subscription def has_unlimited_hearts(user_id: int) -> bool: """آیا کاربر اشتراکِ فعال داره (progress.UserHearts از این استفاده می‌کنه تا هارت کم نکنه). فعلاً هر اشتراکِ فعال، فارغ از پلن/مدت، این پرک رو می‌ده — وقتی محصول پلن‌های متفاوت با پرکِ متفاوت خواست، این تابع دقیق‌تر می‌شه. expires_at=None یعنی اشتراکِ نامحدود (grant_free_subscription با duration_days=None).""" now = timezone.now() return Subscription.objects.filter( user_id=user_id, status='active', ).filter( models.Q(expires_at__isnull=True) | models.Q(expires_at__gt=now) ).exists() def activate_subscription(subscription_id: int): """ بعد از تایید پرداخت (payment webhook) صدا زده می‌شود؛ اشتراک را active می‌کند و expires_at را بر اساس Plan.duration_days محاسبه می‌کند. مستقل از caller اتمیک است چون از چند جا (payment webhook، bazaar verify) صدا زده می‌شود. """ with transaction.atomic(): try: subscription = Subscription.objects.select_for_update().get(pk=subscription_id) except Subscription.DoesNotExist: return None old_status = subscription.status duration_days = subscription.plan.duration_days if subscription.plan else 30 subscription.status = 'active' subscription.expires_at = timezone.now() + timezone.timedelta(days=duration_days) subscription.save(update_fields=['status', 'expires_at']) status_changed.send( sender='subscription.Subscription', object_id=subscription.id, old_status=old_status, new_status='active', event='payment_confirmed', user_id=subscription.user_id, ) return subscription def grant_free_subscription(user_id: int, *, duration_days: int | None = None, event: str = 'offer_applied'): """ فعال‌سازی اشتراک بدون هیچ Order/پرداختی — برای Offer (آفر رایگان محدود/کامل). duration_days=None یعنی نامحدود (free_full)؛ عدد یعنی trial با همان تعداد روز. مستقل از caller اتمیک است. ردیف Subscription قبل از خواندن/تغییر با select_for_update قفل می‌شود تا فراخوانی‌های هم‌زمان lost update نسازند. """ with transaction.atomic(): Subscription.objects.get_or_create(user_id=user_id) subscription = Subscription.objects.select_for_update().get(user_id=user_id) old_status = subscription.status subscription.status = 'active' subscription.expires_at = ( timezone.now() + timezone.timedelta(days=duration_days) if duration_days else None ) subscription.save(update_fields=['status', 'expires_at']) status_changed.send( sender='subscription.Subscription', object_id=subscription.id, old_status=old_status, new_status='active', event=event, user_id=user_id, ) return subscription