from django.utils import timezone from rest_framework import permissions, status from rest_framework.exceptions import PermissionDenied from rest_framework.response import Response from rest_framework.views import APIView from booking.models import Booking from booking.services import transition_booking_status from .models import PaymentRecord, WebhookEvent from .serializers import ( CreatePaymentIntentSerializer, MockWebhookSerializer, PaymentRecordSerializer, WebhookEventSerializer, ) from .services import ( create_mock_payment_intent, mark_payment_failed, mark_payment_processing, mark_payment_refunded, mark_payment_succeeded, ) class CreateMockPaymentIntentView(APIView): permission_classes = (permissions.IsAuthenticated,) def post(self, request): serializer = CreatePaymentIntentSerializer(data=request.data) serializer.is_valid(raise_exception=True) booking = Booking.objects.select_related("vendor").filter(id=serializer.validated_data["booking_id"]).first() if not booking: return Response({"detail": "Booking not found."}, status=status.HTTP_404_NOT_FOUND) # Only the booking customer can initiate payment. if booking.customer_id != request.user.id: raise PermissionDenied("Only the booking customer can create payment intents.") if booking.status != Booking.Status.APPROVED: return Response({"detail": "Payment intent can only be created for approved bookings."}, status=400) existing = PaymentRecord.objects.filter(booking=booking).order_by("-created_at").first() if existing and existing.status in [PaymentRecord.Status.REQUIRES_PAYMENT, PaymentRecord.Status.PROCESSING]: return Response( { "detail": "An active payment already exists for this booking.", "payment": PaymentRecordSerializer(existing).data, "client_secret": f"reuse_{existing.stripe_payment_intent_id}", "mocked": True, }, status=200, ) payment, client_secret = create_mock_payment_intent( booking=booking, amount=booking.total_price, currency=serializer.validated_data["currency"].lower(), ) return Response( { "payment": PaymentRecordSerializer(payment).data, "client_secret": client_secret, "mocked": True, }, status=201, ) class PaymentStatusView(APIView): permission_classes = (permissions.IsAuthenticated,) def get(self, request, payment_id): payment = PaymentRecord.objects.select_related("booking").filter(id=payment_id).first() if not payment: return Response({"detail": "Payment not found."}, status=404) if payment.booking.customer_id != request.user.id and payment.booking.vendor.user_id != request.user.id: raise PermissionDenied("You do not have access to this payment.") return Response(PaymentRecordSerializer(payment).data) class MockStripeWebhookView(APIView): permission_classes = (permissions.AllowAny,) def post(self, request): serializer = MockWebhookSerializer(data=request.data) serializer.is_valid(raise_exception=True) event_id = serializer.validated_data["stripe_event_id"] existing_event = WebhookEvent.objects.filter(stripe_event_id=event_id).first() if existing_event: return Response( { "detail": "Event already processed.", "event": WebhookEventSerializer(existing_event).data, "idempotent": True, } ) event = WebhookEvent.objects.create( stripe_event_id=event_id, event_type=serializer.validated_data["event_type"], payload=serializer.validated_data.get("payload", {}), ) payment = PaymentRecord.objects.filter( stripe_payment_intent_id=serializer.validated_data["stripe_payment_intent_id"] ).select_related("booking").first() if not payment: return Response({"detail": "Payment intent not found for webhook event."}, status=404) if event.event_type == "payment_intent.processing": mark_payment_processing(payment) elif event.event_type == "payment_intent.succeeded": mark_payment_succeeded(payment) if payment.booking.status == Booking.Status.APPROVED: transition_booking_status( booking=payment.booking, to_status=Booking.Status.CONFIRMED, actor=None, note="Auto-confirmed by mock payment success webhook.", ) elif event.event_type == "payment_intent.payment_failed": mark_payment_failed(payment) elif event.event_type == "charge.refunded": mark_payment_refunded(payment) event.processed = True event.processed_at = timezone.now() event.save(update_fields=["processed", "processed_at"]) return Response( { "detail": "Mock webhook processed.", "event": WebhookEventSerializer(event).data, "payment": PaymentRecordSerializer(payment).data, "mocked": True, } )