Files
MathApp/subscription/views.py
2026-08-11 16:43:12 +03:30

116 lines
4.8 KiB
Python

from django.db import transaction
from django.shortcuts import get_object_or_404
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from payment.services import create_order
from .models import Plan, Subscription
from .serializers import (
PlansListResponseSerializer,
PurchaseRequestSerializer,
PurchaseResponseSerializer,
SubscriptionStatusResponseSerializer,
)
class PlansListView(APIView):
"""GET /api/subscription/plans — لیست پلن‌ها"""
@extend_schema(responses=PlansListResponseSerializer, tags=['Subscription (subscription)'])
def get(self, request):
plans = Plan.objects.all()
data = {
'plans': [
{
'id': str(p.id),
'title': p.title,
'price': p.price,
'currency': p.currency,
'popular': p.popular,
'discountPercent': p.discount_percent,
'durationDays': p.duration_days,
}
for p in plans
]
}
return Response(PlansListResponseSerializer(data).data)
class PurchaseView(APIView):
"""
POST /api/subscription/purchase — شروع خرید (با هدر Idempotency-Key)
یک Subscription در وضعیت pending می‌سازه/به‌روز می‌کنه، بعد مستقیماً
تابع سرویس اپ Payment رو صدا می‌زنه تا Order بسازه (همون «فراخوانی
سرور-به-سرور» سند API که در این مونولیت به شکل import مستقیم پیاده‌سازی
شده، نه یک HTTP call جدا به خودمون).
"""
permission_classes = [IsAuthenticated]
# ⚠️ افزوده شده: مستندسازی هدر Idempotency-Key که قبلاً در Swagger UI
# قابل تنظیم/تست نبود.
@extend_schema(
request=PurchaseRequestSerializer,
responses=PurchaseResponseSerializer,
parameters=[
OpenApiParameter(
'Idempotency-Key', OpenApiTypes.STR, OpenApiParameter.HEADER, required=False,
description=(
'کلید یکتای idempotency برای جلوگیری از ساخت سفارش تکراری هنگام retry. '
'در صورت عدم ارسال، یک کلید تصادفی ساخته می‌شود.'
),
),
],
tags=['Subscription (subscription)'],
)
def post(self, request):
serializer = PurchaseRequestSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
v = serializer.validated_data
user = request.user
plan = get_object_or_404(Plan, pk=v['planId'])
# ⚠️ اصلاح: قبلاً اگه create_order (مثلاً به‌خاطر IdempotencyKeyConflict)
# خطا می‌داد، Subscription که یک خط بالاتر update_or_create شده بود
# نیمه‌کاره (status='pending' ولی بدون order_id معتبر) در دیتابیس
# باقی می‌موند. حالا کل عملیات در یک تراکنش اتمیک انجام می‌شه.
with transaction.atomic():
subscription, _created = Subscription.objects.update_or_create(
user_id=user.id,
defaults={'plan': plan, 'status': 'pending'},
)
order, payment_url = create_order(
user_id=user.id,
amount=plan.price,
currency=plan.currency,
subscription_id=subscription.id,
idempotency_key=request.headers.get('Idempotency-Key'),
)
subscription.order_id = order.id
subscription.save(update_fields=['order_id'])
data = {'orderId': str(order.id), 'paymentUrl': payment_url}
return Response(PurchaseResponseSerializer(data).data)
class SubscriptionStatusView(APIView):
"""GET /api/subscription/status — وضعیت فعلی اشتراک"""
permission_classes = [IsAuthenticated]
@extend_schema(responses=SubscriptionStatusResponseSerializer, tags=['Subscription (subscription)'])
def get(self, request):
subscription = Subscription.objects.filter(user_id=request.user.id).first()
data = {
'active': bool(subscription and subscription.status == 'active'),
'planId': str(subscription.plan_id) if subscription and subscription.plan_id else None,
'expiresAt': subscription.expires_at if subscription else None,
}
return Response(SubscriptionStatusResponseSerializer(data).data)