from django.shortcuts import get_object_or_404 from drf_spectacular.utils import extend_schema from rest_framework.exceptions import PermissionDenied, ValidationError from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.throttling import ScopedRateThrottle from rest_framework.views import APIView from .models import AffiliateAccount, Conversion from .serializers import ( AffiliateMeResponseSerializer, ConversionListResponseSerializer, TrackRequestSerializer, TrackResponseSerializer, ) class AffiliateMeView(APIView): """GET /api/affiliate/me — حساب و کد ارجاع کاربر""" permission_classes = [IsAuthenticated] @extend_schema(responses=AffiliateMeResponseSerializer, tags=['Affiliate (affiliate)']) def get(self, request): account, _ = AffiliateAccount.objects.get_or_create(user_id=request.user.id) paid = account.conversions.filter(status='paid') data = { 'referralCode': account.referral_code, 'commissionRate': account.commission_rate, 'referredCount': account.conversions.count(), 'paidCount': paid.count(), 'totalCommission': sum(paid.values_list('commission_amount', flat=True)), } return Response(AffiliateMeResponseSerializer(data).data) class TrackConversionView(APIView): """ POST /api/affiliate/track — ثبت کلیک/ارجاع طبق سند، این اپ فقط با referralCode به AffiliateAccount وصل می‌شه. referredUserId دیگه ورودیِ کلاینت نیست: چون این درخواست باید توسطِ خودِ کاربرِ تازه‌ارجاع‌شده (بعد از لاگین/ثبت‌نام) زده بشه، هویتش رو از توکن احرازشده (request.user) می‌گیریم، نه از بدنه‌ی درخواست. # هویت از request.user گرفته می‌شه (نه بدنه‌ی درخواست) چون یک # referredUserIdِ کلاینت‌دار یعنی هر کلاینتِ ناشناس می‌تونست برای هر # کاربرِ دیگه‌ای Conversion بسازه یا با اشغالِ همه‌ی userId ها (قیدِ # unique رو دورزدن) مسیرِ ارجاعِ واقعی رو مسدود کنه. """ permission_classes = [IsAuthenticated] # referralCode نامعتبر ۴۰۴ می‌گیره (یک oracle قابل‌probe)، پس throttle # مجزا probe رو کند می‌کنه. throttle_scope = 'affiliate_track' throttle_classes = [ScopedRateThrottle] @extend_schema( request=TrackRequestSerializer, responses=TrackResponseSerializer, tags=['Affiliate (affiliate)'] ) def post(self, request): serializer = TrackRequestSerializer(data=request.data) serializer.is_valid(raise_exception=True) v = serializer.validated_data account = get_object_or_404(AffiliateAccount, referral_code=v['referralCode']) referred_user_id = request.user.id if referred_user_id == account.user_id: raise ValidationError({'referredUserId': 'امکان ثبت ارجاع برای خودِ صاحب کد وجود ندارد.'}) conversion, _ = Conversion.objects.get_or_create( referred_user_id=referred_user_id, defaults={'affiliate_account': account}, ) data = {'success': True, 'conversionId': str(conversion.pk), 'status': conversion.status} return Response(TrackResponseSerializer(data).data) class ConversionsListView(APIView): """GET /api/affiliate/conversions — لیست تبدیل‌ها و کمیسیون""" permission_classes = [IsAuthenticated] @extend_schema(responses=ConversionListResponseSerializer, tags=['Affiliate (affiliate)']) def get(self, request): account = AffiliateAccount.objects.filter(user_id=request.user.id).first() conversions = account.conversions.all() if account else Conversion.objects.none() items = [ { 'id': str(c.pk), 'referredUserId': str(c.referred_user_id), 'status': c.status, 'commissionAmount': c.commission_amount, } for c in conversions ] data = {'conversions': items} return Response(ConversionListResponseSerializer(data).data)