adding
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
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})
|
||||
Reference in New Issue
Block a user