""" Payment Management API Views Handles payment processing, transactions, and financial management endpoints """ from rest_framework import viewsets, status, permissions from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from django.utils import timezone from drf_spectacular.utils import extend_schema, OpenApiParameter from drf_spectacular.types import OpenApiTypes from core.models.payment import ( PaymentTransaction, PaymentStatus, PaymentMethod, PaymentProvider, RefundTransaction, DisputeTransaction, PaymentWebhook ) from core.models.subscription import Subscription from core.services.payment_service import PaymentService from core.services.subscription_service import SubscriptionService from core.auth.permissions import TenantPermission, IsTenantAdmin from core.serializers.payment import ( PaymentTransactionSerializer, PaymentCreateSerializer, PaymentUpdateSerializer, RefundTransactionSerializer, DisputeTransactionSerializer, PaymentMethodSerializer, PaymentStatsSerializer, PaymentWebhookSerializer ) class PaymentTransactionViewSet(viewsets.ModelViewSet): """ Payment Transaction ViewSet Provides CRUD operations for payment transactions with comprehensive financial logic """ permission_classes = [IsAuthenticated, TenantPermission] serializer_class = PaymentTransactionSerializer lookup_field = 'id' def get_queryset(self): """ Filter payment transactions based on tenant and permissions """ user = self.request.user tenant = user.tenant if not tenant: return PaymentTransaction.objects.none() # Superusers see all transactions if user.is_superuser: return PaymentTransaction.objects.all() # Tenant admins/managers see their tenant's transactions if user.role in ['admin', 'manager']: return PaymentTransaction.objects.filter(tenant=tenant) # Regular users see only their own transactions return PaymentTransaction.objects.filter(tenant=tenant, user=user) def get_serializer_class(self): """ Return appropriate serializer based on action """ if self.action == 'create': return PaymentCreateSerializer elif self.action in ['update', 'partial_update']: return PaymentUpdateSerializer elif self.action == 'refund': return RefundTransactionSerializer elif self.action == 'dispute': return DisputeTransactionSerializer elif self.action == 'stats': return PaymentStatsSerializer elif self.action == 'methods': return PaymentMethodSerializer elif self.action == 'webhook': return PaymentWebhookSerializer return PaymentTransactionSerializer def perform_create(self, serializer): """ Create payment transaction with proper initialization """ payment_service = PaymentService() transaction = payment_service.create_payment(serializer.validated_data) serializer.instance = transaction def perform_update(self, serializer): """ Update payment transaction with validation """ payment_service = PaymentService() transaction = payment_service.update_payment( self.get_object(), serializer.validated_data ) serializer.instance = transaction @extend_schema( summary="Process Payment", description="Process payment with various payment methods", request=OpenApiTypes.OBJECT, responses={200: PaymentTransactionSerializer} ) @action(detail=True, methods=['post']) def process(self, request, *args, **kwargs): """ Process payment transaction """ transaction = self.get_object() payment_service = PaymentService() try: result = payment_service.process_payment(transaction, request.data) serializer = PaymentTransactionSerializer(result) return Response(serializer.data) except Exception as e: return Response( {'detail': f'Payment processing failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) @extend_schema( summary="Refund Payment", description="Refund payment transaction", request=RefundTransactionSerializer, responses={200: RefundTransactionSerializer} ) @action(detail=True, methods=['post']) def refund(self, request, *args, **kwargs): """ Refund payment transaction """ transaction = self.get_object() payment_service = PaymentService() try: refund = payment_service.refund_payment(transaction, request.data) serializer = RefundTransactionSerializer(refund) return Response(serializer.data) except Exception as e: return Response( {'detail': f'Refund failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) @extend_schema( summary="Dispute Payment", description="Dispute payment transaction", request=DisputeTransactionSerializer, responses={200: DisputeTransactionSerializer} ) @action(detail=True, methods=['post']) def dispute(self, request, *args, **kwargs): """ Dispute payment transaction """ transaction = self.get_object() payment_service = PaymentService() try: dispute = payment_service.dispute_payment(transaction, request.data) serializer = DisputeTransactionSerializer(dispute) return Response(serializer.data) except Exception as e: return Response( {'detail': f'Dispute failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) @extend_schema( summary="Get Payment Receipt", description="Generate payment receipt", responses={200: OpenApiTypes.OBJECT} ) @action(detail=True, methods=['get']) def receipt(self, request, *args, **kwargs): """ Get payment receipt """ transaction = self.get_object() payment_service = PaymentService() try: receipt = payment_service.generate_receipt(transaction) return Response(receipt) except Exception as e: return Response( {'detail': f'Receipt generation failed: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) @extend_schema( summary="Get Payment Stats", description="Retrieve payment statistics", responses={200: PaymentStatsSerializer} ) @action(detail=False, methods=['get']) def stats(self, request): """ Get payment statistics """ tenant = request.user.tenant if not tenant: return Response( {'detail': 'Tenant not found'}, status=status.HTTP_404_NOT_FOUND ) payment_service = PaymentService() stats = payment_service.get_payment_stats(tenant) serializer = PaymentStatsSerializer(stats) return Response(serializer.data) @extend_schema( summary="Get Payment Methods", description="Retrieve available payment methods", responses={200: PaymentMethodSerializer(many=True)} ) @action(detail=False, methods=['get']) def methods(self, request): """ Get available payment methods """ payment_service = PaymentService() methods = payment_service.get_available_payment_methods() serializer = PaymentMethodSerializer(methods, many=True) return Response(serializer.data) @extend_schema( summary="Validate Payment Method", description="Validate payment method details", request=OpenApiTypes.OBJECT, responses={200: OpenApiTypes.OBJECT} ) @action(detail=False, methods=['post']) def validate_method(self, request): """ Validate payment method """ payment_service = PaymentService() try: validation_result = payment_service.validate_payment_method(request.data) return Response(validation_result) except Exception as e: return Response( {'detail': f'Validation failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) @extend_schema( summary="Handle Payment Webhook", description="Process payment provider webhook", request=PaymentWebhookSerializer, responses={200: OpenApiTypes.OBJECT} ) @action(detail=False, methods=['post']) def webhook(self, request): """ Handle payment webhook """ payment_service = PaymentService() try: result = payment_service.handle_webhook(request.data) return Response(result) except Exception as e: return Response( {'detail': f'Webhook processing failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) @extend_schema( summary="Get Transaction History", description="Retrieve transaction history with filters", parameters=[ OpenApiParameter( name='start_date', description='Start date filter', type=OpenApiTypes.DATE ), OpenApiParameter( name='end_date', description='End date filter', type=OpenApiTypes.DATE ), OpenApiParameter( name='status', description='Status filter', type=OpenApiTypes.STR ), OpenApiParameter( name='provider', description='Provider filter', type=OpenApiTypes.STR ) ], responses={200: PaymentTransactionSerializer(many=True)} ) @action(detail=False, methods=['get']) def history(self, request): """ Get transaction history """ tenant = request.user.tenant if not tenant: return Response( {'detail': 'Tenant not found'}, status=status.HTTP_404_NOT_FOUND ) # Parse filters start_date = request.query_params.get('start_date') end_date = request.query_params.get('end_date') status_filter = request.query_params.get('status') provider_filter = request.query_params.get('provider') payment_service = PaymentService() transactions = payment_service.get_transaction_history( tenant=tenant, start_date=start_date, end_date=end_date, status=status_filter, provider=provider_filter ) serializer = PaymentTransactionSerializer(transactions, many=True) return Response(serializer.data) @extend_schema( summary="Get Payment Analytics", description="Retrieve payment analytics and insights", parameters=[ OpenApiParameter( name='period', description='Analysis period (day, week, month, year)', type=OpenApiTypes.STR, default='month' ) ], responses={200: OpenApiTypes.OBJECT} ) @action(detail=False, methods=['get']) def analytics(self, request): """ Get payment analytics """ tenant = request.user.tenant if not tenant: return Response( {'detail': 'Tenant not found'}, status=status.HTTP_404_NOT_FOUND ) period = request.query_params.get('period', 'month') payment_service = PaymentService() try: analytics = payment_service.get_payment_analytics(tenant, period) return Response(analytics) except Exception as e: return Response( {'detail': f'Analytics retrieval failed: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) def get_permissions(self): """ Override permissions based on action """ if self.action in ['refund', 'dispute', 'stats', 'analytics', 'webhook']: permission_classes = [IsAuthenticated, IsTenantAdmin] elif self.action in ['create', 'update', 'partial_update', 'destroy']: permission_classes = [IsAuthenticated, IsTenantAdmin] else: permission_classes = [IsAuthenticated, TenantPermission] return [permission() for permission in permission_classes] class RefundTransactionViewSet(viewsets.ModelViewSet): """ Refund Transaction ViewSet Manages refund transactions """ permission_classes = [IsAuthenticated, IsTenantAdmin] serializer_class = RefundTransactionSerializer lookup_field = 'id' def get_queryset(self): """ Filter refund transactions based on tenant """ user = self.request.user tenant = user.tenant if not tenant: return RefundTransaction.objects.none() # Superusers see all refunds if user.is_superuser: return RefundTransaction.objects.all() return RefundTransaction.objects.filter(payment_transaction__tenant=tenant) @extend_schema( summary="Process Refund", description="Process refund transaction", responses={200: RefundTransactionSerializer} ) @action(detail=True, methods=['post']) def process(self, request, *args, **kwargs): """ Process refund transaction """ refund = self.get_object() payment_service = PaymentService() try: result = payment_service.process_refund(refund) serializer = RefundTransactionSerializer(result) return Response(serializer.data) except Exception as e: return Response( {'detail': f'Refund processing failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) @extend_schema( summary="Cancel Refund", description="Cancel refund transaction", responses={200: OpenApiTypes.OBJECT} ) @action(detail=True, methods=['post']) def cancel(self, request, *args, **kwargs): """ Cancel refund transaction """ refund = self.get_object() payment_service = PaymentService() try: payment_service.cancel_refund(refund) return Response({'detail': 'Refund cancelled successfully'}) except Exception as e: return Response( {'detail': f'Refund cancellation failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) class DisputeTransactionViewSet(viewsets.ModelViewSet): """ Dispute Transaction ViewSet Manages dispute transactions """ permission_classes = [IsAuthenticated, IsTenantAdmin] serializer_class = DisputeTransactionSerializer lookup_field = 'id' def get_queryset(self): """ Filter dispute transactions based on tenant """ user = self.request.user tenant = user.tenant if not tenant: return DisputeTransaction.objects.none() # Superusers see all disputes if user.is_superuser: return DisputeTransaction.objects.all() return DisputeTransaction.objects.filter(payment_transaction__tenant=tenant) @extend_schema( summary="Submit Evidence", description="Submit evidence for dispute", request=OpenApiTypes.OBJECT, responses={200: OpenApiTypes.OBJECT} ) @action(detail=True, methods=['post']) def submit_evidence(self, request, *args, **kwargs): """ Submit dispute evidence """ dispute = self.get_object() payment_service = PaymentService() try: payment_service.submit_dispute_evidence(dispute, request.data) return Response({'detail': 'Evidence submitted successfully'}) except Exception as e: return Response( {'detail': f'Evidence submission failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) @extend_schema( summary="Escalate Dispute", description="Escalate dispute to payment provider", responses={200: OpenApiTypes.OBJECT} ) @action(detail=True, methods=['post']) def escalate(self, request, *args, **kwargs): """ Escalate dispute """ dispute = self.get_object() payment_service = PaymentService() try: payment_service.escalate_dispute(dispute) return Response({'detail': 'Dispute escalated successfully'}) except Exception as e: return Response( {'detail': f'Dispute escalation failed: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST ) class PaymentMethodViewSet(viewsets.ReadOnlyModelViewSet): """ Payment Method ViewSet Provides read-only access to payment methods """ permission_classes = [IsAuthenticated, TenantPermission] serializer_class = PaymentMethodSerializer lookup_field = 'id' def get_queryset(self): """ Return available payment methods """ return PaymentMethod.objects.filter(is_active=True) @extend_schema( summary="Get Payment Method Fees", description="Retrieve payment method fees", responses={200: OpenApiTypes.OBJECT} ) @action(detail=True, methods=['get']) def fees(self, request, *args, **kwargs): """ Get payment method fees """ payment_method = self.get_object() payment_service = PaymentService() try: fees = payment_service.get_payment_method_fees(payment_method) return Response(fees) except Exception as e: return Response( {'detail': f'Fee retrieval failed: {str(e)}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) class PublicPaymentViewSet(viewsets.ViewSet): """ Public Payment ViewSet Provides public access to payment information """ permission_classes = [permissions.AllowAny] @extend_schema( summary="Get Available Payment Methods", description="Retrieve publicly available payment methods", responses={200: PaymentMethodSerializer(many=True)} ) @action(detail=False, methods=['get']) def available_methods(self, request): """ Get available payment methods """ payment_service = PaymentService() methods = payment_service.get_public_payment_methods() serializer = PaymentMethodSerializer(methods, many=True) return Response(serializer.data) @extend_schema( summary="Get Payment Providers", description="Retrieve supported payment providers", responses={200: OpenApiTypes.OBJECT} ) @action(detail=False, methods=['get']) def providers(self, request): """ Get payment providers """ payment_service = PaymentService() providers = payment_service.get_payment_providers() return Response(providers) @extend_schema( summary="Get Currency Information", description="Retrieve supported currencies and exchange rates", responses={200: OpenApiTypes.OBJECT} ) @action(detail=False, methods=['get']) def currencies(self, request): """ Get currency information """ payment_service = PaymentService() currencies = payment_service.get_currency_info() return Response(currencies)