from rest_framework import generics, status from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from rest_framework.views import APIView from apps.course.models import Participant, Course from apps.transaction.models import TransactionParticipant, TransactionReceipt from apps.transaction.serializers import ( TransactionParticipantSerializer, TransactionListSerializer, UploadReceiptsSerializer, TransactionReceiptSerializer ) from utils.exceptions import AppAPIException from apps.account.models import User from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi from apps.transaction.models import TransactionParticipant from apps.transaction.doc import ( doc_upload_transaction_receipts, doc_list_transaction_receipts, doc_transaction_list, doc_create_transaction ) from utils.ip_helper import get_client_ip, get_country_code class TransactionParticipantCreateView(generics.CreateAPIView): queryset = TransactionParticipant.objects.all() serializer_class = TransactionParticipantSerializer permission_classes = [IsAuthenticated] @swagger_auto_schema( operation_description=doc_create_transaction(), responses={ status.HTTP_201_CREATED: openapi.Response( description="Transaction participant created successfully", examples={ "application/json": { "message": "Transaction Participant created successfully.", "transaction_id": 374, "payment_method": "Payment_Gateway", "payment_link": "https://russia-payment.com/pay/374", "participant_infos": [ { "fullname": "string", "email": "admin@gmail.com", "phone_number": "string", "gender": "male", "birthdate": "2025-12-28" } ] } }, schema=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'message': openapi.Schema(type=openapi.TYPE_STRING, description="Success message"), 'transaction_id': openapi.Schema(type=openapi.TYPE_INTEGER, description="Unique transaction identifier"), 'payment_method': openapi.Schema( type=openapi.TYPE_STRING, enum=['Payment_Gateway', 'receipt'], description="Payment method: 'Payment_Gateway' for online payment, 'receipt' for WhatsApp upload" ), 'payment_link': openapi.Schema( type=openapi.TYPE_STRING, nullable=True, description="Payment gateway URL (only present when payment_method is 'Payment_Gateway')" ), 'participant_infos': openapi.Schema( type=openapi.TYPE_ARRAY, items=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'fullname': openapi.Schema(type=openapi.TYPE_STRING), 'email': openapi.Schema(type=openapi.TYPE_STRING), 'phone_number': openapi.Schema(type=openapi.TYPE_STRING), 'gender': openapi.Schema(type=openapi.TYPE_STRING, enum=['male', 'female']), 'birthdate': openapi.Schema(type=openapi.TYPE_STRING, format='date'), } ), description="List of participant information" ), }, required=['message', 'transaction_id', 'participant_infos'] ) ), status.HTTP_400_BAD_REQUEST: openapi.Response( description="Invalid data provided", schema=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'detail': openapi.Schema(type=openapi.TYPE_STRING), } ) ), status.HTTP_404_NOT_FOUND: openapi.Response( description="Course not found", schema=openapi.Schema( type=openapi.TYPE_OBJECT, properties={ 'message': openapi.Schema(type=openapi.TYPE_STRING), } ) ), }, tags=['transaction'] ) def post(self, request, *args, **kwargs): # Simply call the create method return self.create(request, *args, **kwargs) def create(self, request, *args, **kwargs): user = request.user course_slug = self.kwargs.get('slug') # 1. Retrieve Course try: course = Course.objects.get(slug=course_slug) except Course.DoesNotExist: raise AppAPIException({'message': "Course not found"}) participant_infos = request.data.get('participant_infos', []) # 2. Validate and Initialize serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) statis = TransactionParticipant.TransactionStatus.PENDING # 3. Handle Free/Self-Enrollment Logic if len(participant_infos) == 1 and (course.final_price == 0 or course.is_free): participant = participant_infos[0] if participant.get('email') != user.email: raise AppAPIException({'message': "The email must be for the requesting user"}) if not user.has_role('student'): user.add_role('student') existing_participant = Participant.objects.filter(student=user, course=course).first() if existing_participant: participant = existing_participant else: participant = Participant.objects.create(student=user, course=course) statis = TransactionParticipant.TransactionStatus.SUCCESS # 4. Save Transaction transaction_participant = serializer.save( user=user, course=course, price=course.final_price, status=statis ) print(f'---> {type(transaction_participant)}/ {transaction_participant}') # ======================================================= # NEW LOGIC: HYBRID GEOLOCATION CHECK (Cloudflare + Local DB) # ======================================================= payment_link = None payment_method = TransactionParticipant.PaymentMethods.FREE if statis == TransactionParticipant.TransactionStatus.PENDING: # Step A: Fast Path - Check Cloudflare Header # Cloudflare sends the 2-letter code (e.g., 'RU', 'US') in this header country_code = request.META.get('HTTP_CF_IPCOUNTRY') # Step B: Slow Path - Fallback to Local DB # If header is missing (e.g., Localhost, direct connection, or CF failed) if not country_code: try: client_ip =get_client_ip(request) # "188.93.104.1" # get_client_ip(request) # Assuming your helper handles errors gracefully and returns None country_code = get_country_code(client_ip) except Exception as e: print(f"GeoIP Lookup Failed: {e}") country_code = None payment_method = TransactionParticipant.PaymentMethods.RECEIPT # Step C: Apply Logic if country_code != 'RU': payment_method = TransactionParticipant.PaymentMethods.PAYMENT_GATEWAY payment_link = f"https://russia-payment.com/pay/{transaction_participant.id}" # Uncomment if you want a global fallback link # else: # payment_link = f"https://global-payment.com/pay/{transaction_participant.id}" # ======================================================= return Response({ 'message': 'Transaction Participant created successfully.', 'transaction_id': transaction_participant.id, 'payment_method':payment_method, 'payment_link': payment_link, 'participant_infos': serializer.data['participant_infos'] }, status=status.HTTP_201_CREATED) class TransactiontListView(generics.ListAPIView): queryset = TransactionParticipant.objects.all() serializer_class = TransactionListSerializer permission_classes = [IsAuthenticated] @swagger_auto_schema( operation_description=doc_transaction_list() ) def get_queryset(self): queryset = super().get_queryset() queryset = queryset.filter(user=self.request.user, is_deleted=False) return queryset class SoftDeleteTransactionParticipantView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( operation_summary="Soft delete a transaction participant", operation_description="Marks a transaction participant as deleted without removing it from the database", manual_parameters=[ openapi.Parameter( 'id', openapi.IN_PATH, description="Transaction Participant ID", type=openapi.TYPE_INTEGER, required=True ) ], responses={ 200: openapi.Response( description="Transaction participant successfully marked as deleted", examples={ "application/json": { "success": True, "message": "Transaction participant successfully marked as deleted" } } ), 404: "Transaction participant not found", 403: "Permission denied" } ) def delete(self, request, pk): try: transaction = TransactionParticipant.objects.get(pk=pk) if transaction.user == request.user: transaction.is_deleted = True transaction.save() return Response({ "success": True, "message": "Transaction participant successfully marked as deleted" }, status=status.HTTP_200_OK) else: raise AppAPIException( detail={'message': "You don't have permission to delete this transaction"}, status_code=status.HTTP_403_FORBIDDEN ) except TransactionParticipant.DoesNotExist: raise AppAPIException({'message': "Transaction participant not found"}) class UploadTransactionReceiptsView(APIView): permission_classes = [IsAuthenticated] @swagger_auto_schema( operation_summary="Upload payment receipts for a transaction", operation_description=doc_upload_transaction_receipts(), request_body=UploadReceiptsSerializer, responses={ 201: openapi.Response( description="Receipts uploaded successfully", examples={ "application/json": { "success": True, "message": "Receipts uploaded successfully", "transaction_status": "waiting_approval", "receipts": [ { "id": 1, "file": "http://example.com/media/receipts/1/receipt.jpg", "description": "Payment receipt", "uploaded_at": "2025-12-03T10:30:00Z" } ] } } ), 400: "Invalid data or transaction cannot accept receipts", 403: "Permission denied", 404: "Transaction not found" } ) def post(self, request, transaction_id): try: transaction = TransactionParticipant.objects.get(pk=transaction_id, is_deleted=False) except TransactionParticipant.DoesNotExist: raise AppAPIException({'message': "Transaction not found"}) # Check if user owns this transaction if transaction.user != request.user: raise AppAPIException( detail={'message': "You don't have permission to upload receipts for this transaction"}, status_code=status.HTTP_403_FORBIDDEN ) # Check if transaction is in a state that can accept receipts if transaction.status not in [ TransactionParticipant.TransactionStatus.PENDING, TransactionParticipant.TransactionStatus.WAITING_APPROVAL ]: raise AppAPIException( detail={'message': f"Cannot upload receipts for transaction with status '{transaction.status}'"}, status_code=status.HTTP_400_BAD_REQUEST ) # Validate using serializer serializer = UploadReceiptsSerializer(data=request.data, context={'request': request}) serializer.is_valid(raise_exception=True) # Create receipt records receipts = [] description = serializer.validated_data.get('description', '') file_urls = serializer.validated_data.get('files', []) for file_url in file_urls: receipt = TransactionReceipt.objects.create( transaction=transaction, file=file_url, description=description ) receipts.append(receipt) # Update transaction status to waiting_approval transaction.status = TransactionParticipant.TransactionStatus.WAITING_APPROVAL transaction.save() # Serialize receipts for response receipts_data = TransactionReceiptSerializer(receipts, many=True, context={'request': request}).data return Response({ 'success': True, 'message': 'Receipts uploaded successfully', 'transaction_status': transaction.status, 'receipts': receipts_data }, status=status.HTTP_201_CREATED) class TransactionReceiptsListView(generics.ListAPIView): serializer_class = TransactionReceiptSerializer permission_classes = [IsAuthenticated] @swagger_auto_schema( operation_summary="List receipts for a transaction", operation_description=doc_list_transaction_receipts(), manual_parameters=[ openapi.Parameter( 'transaction_id', openapi.IN_PATH, description="Transaction ID", type=openapi.TYPE_INTEGER, required=True ) ], responses={ 200: TransactionReceiptSerializer(many=True), 403: "Permission denied", 404: "Transaction not found" } ) def get_queryset(self): transaction_id = self.kwargs.get('transaction_id') try: transaction = TransactionParticipant.objects.get(pk=transaction_id, is_deleted=False) except TransactionParticipant.DoesNotExist: raise AppAPIException({'message': "Transaction not found"}) # Check if user owns this transaction if transaction.user != self.request.user: raise AppAPIException( detail={'message': "You don't have permission to view receipts for this transaction"}, status_code=status.HTTP_403_FORBIDDEN ) return TransactionReceipt.objects.filter(transaction=transaction)