uchill/backend/apps/subscriptions/promo_code_views.py

231 lines
8.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
API views для промокодов.
"""
from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, AllowAny
from .services import PromoCodeService
from .models import PromoCode
from rest_framework import serializers
class PromoCodeValidateSerializer(serializers.Serializer):
"""Сериализатор валидации промокода."""
code = serializers.CharField(max_length=50, required=True)
plan_id = serializers.IntegerField(required=False)
student_count = serializers.IntegerField(required=False, default=0, min_value=0)
duration_days = serializers.IntegerField(required=False, default=30, min_value=1)
@api_view(['POST'])
@permission_classes([AllowAny])
def validate_promo_code(request):
"""
Валидация промокода и расчет скидки.
POST /api/subscriptions/promo-codes/validate/
Body: {
"code": "PROMO2024",
"plan_id": 1,
"student_count": 5,
"duration_days": 30
}
"""
serializer = PromoCodeValidateSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
code = serializer.validated_data['code']
plan_id = serializer.validated_data.get('plan_id')
student_count = serializer.validated_data.get('student_count', 0)
duration_days = serializer.validated_data.get('duration_days', 30)
# Валидация промокода
user = request.user if request.user.is_authenticated else None
promo_result = PromoCodeService.validate_promo_code(code, user)
if not promo_result['valid']:
return Response(
{
'valid': False,
'error': promo_result['error']
},
status=status.HTTP_400_BAD_REQUEST
)
promo_code = promo_result['promo_code']
# Если указан план, рассчитываем скидку
if plan_id:
from .models import SubscriptionPlan
try:
plan = SubscriptionPlan.objects.select_related().get(id=plan_id, is_active=True)
price_data = plan.calculate_price(
student_count=student_count,
duration_days=duration_days,
promo_code=promo_code
)
return Response({
'valid': True,
'promo_code': {
'code': promo_code.code,
'discount_type': promo_code.discount_type,
'discount_value': float(promo_code.discount_value),
},
'price': {
'original_amount': float(price_data['original_amount']),
'discount_amount': float(price_data['discount_amount']),
'final_amount': float(price_data['final_amount']),
}
})
except SubscriptionPlan.DoesNotExist:
return Response(
{
'valid': True,
'promo_code': {
'code': promo_code.code,
'discount_type': promo_code.discount_type,
'discount_value': float(promo_code.discount_value),
},
'price': None
}
)
# Если план не указан, просто возвращаем информацию о промокоде
return Response({
'valid': True,
'promo_code': {
'code': promo_code.code,
'discount_type': promo_code.discount_type,
'discount_value': float(promo_code.discount_value),
}
})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def calculate_price(request):
"""
Расчет цены подписки с учетом промокода.
GET /api/subscriptions/calculate-price/?plan_id=1&student_count=5&duration_days=30&promo_code=PROMO2024
"""
from .models import SubscriptionPlan
from .services import SubscriptionService
plan_id = request.query_params.get('plan_id')
student_count = int(request.query_params.get('student_count', 0))
duration_days = int(request.query_params.get('duration_days', 30))
promo_code_str = request.query_params.get('promo_code')
remaining_days = request.query_params.get('remaining_days')
is_renewal = request.query_params.get('is_renewal') == 'true'
is_add_students = request.query_params.get('is_add_students') == 'true'
current_student_count = request.query_params.get('current_student_count')
if not plan_id:
return Response(
{'error': 'Необходимо указать plan_id'},
status=status.HTTP_400_BAD_REQUEST
)
try:
plan = SubscriptionPlan.objects.select_related().get(id=plan_id, is_active=True)
except SubscriptionPlan.DoesNotExist:
return Response(
{'error': 'Тарифный план не найден'},
status=status.HTTP_404_NOT_FOUND
)
# Проверяем доступность периода
if not plan.is_duration_available(duration_days):
available = plan.get_available_durations()
return Response(
{'error': f'Период {duration_days} дней недоступен для этого тарифа. Доступные периоды: {", ".join(map(str, available))}'},
status=status.HTTP_400_BAD_REQUEST
)
# Валидация промокода если указан
promo_code = None
if promo_code_str:
promo_result = PromoCodeService.validate_promo_code(promo_code_str, request.user)
if promo_result['valid']:
promo_code = promo_result['promo_code']
else:
return Response(
{'error': promo_result['error']},
status=status.HTTP_400_BAD_REQUEST
)
# Если это продление и есть оставшиеся дни, учитываем их
remaining_days_int = None
if (is_renewal or is_add_students) and remaining_days:
try:
remaining_days_int = int(remaining_days)
except (ValueError, TypeError):
pass
# Получаем текущее количество учеников для расчета доплаты
current_student_count_int = 0
if current_student_count:
try:
current_student_count_int = int(current_student_count)
except (ValueError, TypeError):
pass
elif is_renewal and request.user.is_authenticated:
# Если не передано, пытаемся получить из активной подписки
from .models import Subscription
try:
subscription = Subscription.objects.filter(
user=request.user,
plan=plan,
status__in=['active', 'trial']
).select_related('plan').order_by('-end_date').first()
if subscription:
current_student_count_int = subscription.student_count or 0
except:
pass
# Рассчитываем цену
price_data = SubscriptionService.calculate_subscription_price(
plan=plan,
student_count=student_count,
duration_days=duration_days,
promo_code=promo_code,
remaining_days=remaining_days_int,
is_add_students=is_add_students,
current_student_count=current_student_count_int
)
response_data = {
'original_amount': float(price_data['original_amount']),
'discount_amount': float(price_data['discount_amount']),
'final_amount': float(price_data['final_amount']),
}
# Добавляем информацию о доплате за новых учеников
if 'extra_students_payment' in price_data:
response_data['extra_students_payment'] = price_data['extra_students_payment']
response_data['extra_students_count'] = price_data.get('extra_students_count', 0)
response_data['remaining_days_payment'] = price_data.get('remaining_days_payment', 0)
# Добавляем информацию о скидке за длительность
if 'duration_discount' in price_data:
response_data['duration_discount'] = price_data['duration_discount']
# Добавляем дату окончания подписки
if 'end_date' in price_data:
response_data['end_date'] = price_data['end_date']
if 'end_date_display' in price_data:
response_data['end_date_display'] = price_data['end_date_display']
if promo_code:
response_data['promo_code'] = {
'code': promo_code.code,
'discount_type': promo_code.discount_type,
'discount_value': float(promo_code.discount_value),
}
return Response(response_data)