from django.db import transaction from django.utils import timezone from rest_framework import generics, status from rest_framework.exceptions import NotFound from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from apps.account.models import User from apps.billing.models import BazaarPurchase, FeaturePlan, GemPack, GemTransaction from apps.billing.providers.bazaar import verify_purchase from .serializers import ( ActivateFeatureSerializer, CreatePurchaseSerializer, FeaturePlanSerializer, GemPackSerializer, WalletSerializer, ) from .services import FeatureAccessService, GemWalletService def _wallet_data(user: User) -> dict: return { "gem_balance": user.gem_balance, "is_in_trial": user.is_in_trial, "trial_expires_at": user.trial_expires_at, "referral_code": user.referral_code, "referral_count": User.objects.filter(referred_by=user, referral_bonus_granted=True).count(), } class WalletView(APIView): permission_classes = [IsAuthenticated] def get(self, request): return Response(WalletSerializer(_wallet_data(request.user)).data) class FeatureListView(generics.ListAPIView): serializer_class = FeaturePlanSerializer permission_classes = [IsAuthenticated] queryset = FeaturePlan.objects.filter(is_purchasable=True).prefetch_related("tiers") class ActivateFeatureView(APIView): permission_classes = [IsAuthenticated] throttle_scope = "billing_write" def post(self, request, key): serializer = ActivateFeatureSerializer(data=request.data) serializer.is_valid(raise_exception=True) FeatureAccessService.activate(request.user, key, serializer.validated_data["duration_months"]) return Response( { "success": True, "feature": FeaturePlanSerializer( FeaturePlan.objects.get(key=key), context={"request": request} ).data, "wallet": WalletSerializer(_wallet_data(request.user)).data, } ) class GemPackListView(generics.ListAPIView): serializer_class = GemPackSerializer permission_classes = [IsAuthenticated] queryset = GemPack.objects.filter(is_active=True) class CreatePurchaseView(APIView): """کلاینت بعد از باز کردن لینک خرید کافه‌بازار و برگشتن، اینجا فقط می‌گوید «این خرید را شروع کردم»؛ جمی اضافه نمی‌شود تا verify تایید کند.""" permission_classes = [IsAuthenticated] throttle_scope = "billing_write" def post(self, request): serializer = CreatePurchaseSerializer(data=request.data) serializer.is_valid(raise_exception=True) try: gem_pack = GemPack.objects.get(id=serializer.validated_data["gem_pack_id"], is_active=True) except GemPack.DoesNotExist: raise NotFound("بسته‌ی جم یافت نشد.") purchase, _created = BazaarPurchase.objects.get_or_create( purchase_token=serializer.validated_data["purchase_token"], defaults={"user": request.user, "gem_pack": gem_pack}, ) return Response({"id": purchase.id, "status": purchase.status}, status=status.HTTP_201_CREATED) class VerifyPurchaseView(APIView): permission_classes = [IsAuthenticated] throttle_scope = "billing_write" def post(self, request, purchase_id): # قفل ردیفِ همین خرید تا پایان تراکنش — بدون این، دو درخواست verify # هم‌زمان (دابل‌تپ یا retry شبکه) هر دو می‌توانند از چک status=pending # رد شوند و جم را دوبار اضافه کنند. با این قفل، دومی تا کامیت شدن # اولی معطل می‌ماند و بعد status=verified را می‌بیند و کاری نمی‌کند. with transaction.atomic(): try: purchase = BazaarPurchase.objects.select_for_update().get(id=purchase_id, user=request.user) except BazaarPurchase.DoesNotExist: raise NotFound("خرید یافت نشد.") if purchase.status == BazaarPurchase.Status.VERIFIED: return Response( {"status": purchase.status, "wallet": WalletSerializer(_wallet_data(request.user)).data} ) is_valid = verify_purchase(purchase.gem_pack.bazaar_sku, purchase.purchase_token) if not is_valid: purchase.status = BazaarPurchase.Status.FAILED purchase.save(update_fields=["status"]) return Response( {"status": purchase.status, "detail": "تایید خرید ناموفق بود."}, status=status.HTTP_400_BAD_REQUEST, ) GemWalletService.add_gems( request.user, purchase.gem_pack.gem_amount, GemTransaction.Reason.PURCHASE, note=f"خرید {purchase.gem_pack.display_name}", ) purchase.status = BazaarPurchase.Status.VERIFIED purchase.gems_granted = purchase.gem_pack.gem_amount purchase.verified_at = timezone.now() purchase.save(update_fields=["status", "gems_granted", "verified_at"]) return Response({"status": purchase.status, "wallet": WalletSerializer(_wallet_data(request.user)).data})