Initial commit
This commit is contained in:
115
subscription/views.py
Normal file
115
subscription/views.py
Normal file
@@ -0,0 +1,115 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user