from django.shortcuts import get_object_or_404 from drf_spectacular.utils import extend_schema from rest_framework.exceptions import ValidationError from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response 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) data = {'referralCode': account.referral_code, 'commissionRate': account.commission_rate} return Response(AffiliateMeResponseSerializer(data).data) class TrackConversionView(APIView): """ POST /api/affiliate/track — ثبت کلیک/ارجاع طبق سند، این اپ فقط با referralCode به AffiliateAccount وصل می‌شه (نه توکن کاربر)، چون referredUserId خودش کاربر تازه‌ارجاع‌شده‌ست که لزوماً صاحب حساب افیلیت نیست؛ پس اینجا permission پیش‌فرض (AllowAny) نگه داشته شده، دقیقاً مثل ValidateDiscountView در اپ discount. """ @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 = int(v['referredUserId']) # ⚠️ اصلاح امنیتی: قبلاً referredUserId بدون هیچ اعتبارسنجی‌ای # مستقیم مصرف می‌شد؛ یعنی می‌شد برای یک کاربر ناموجود Conversion # ساخت یا با فرستادن userId خودِ صاحب حساب affiliate، به خودش # ارجاع داد (self-referral). حالا هر دو حالت رد می‌شن. from accounts.models import User as AccountUser if not AccountUser.objects.filter(pk=referred_user_id).exists(): raise ValidationError({'referredUserId': 'چنین کاربری وجود ندارد.'}) 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)