import logging import os from django.utils.translation import gettext as _ from rest_framework import permissions, status, viewsets from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from apps.bookings.models import Booking from apps.payments.models import Payment, PaymentProvider from apps.payments.serializers import PaymentCreateSerializer, PaymentSerializer from apps.payments.services.payments import apply_webhook_event, create_payment_for_booking logger = logging.getLogger(__name__) def user_can_access_booking(user, booking: Booking) -> bool: if getattr(user, "is_superuser", False) or user.role == "admin": return True if user.role == "manager": return booking.salon.owner_id == user.id if user.role == "staff": return booking.staff_id and booking.staff.user_id == user.id return booking.customer_id == user.id class PaymentViewSet(viewsets.ModelViewSet): permission_classes = [permissions.IsAuthenticated] def get_queryset(self): user = self.request.user if getattr(user, "is_superuser", False) or user.role == "admin": return Payment.objects.all().order_by("-created_at") if user.role == "manager": return Payment.objects.filter(booking__salon__owner=user).order_by("-created_at") if user.role == "staff": return Payment.objects.filter(booking__staff__user=user).order_by("-created_at") return Payment.objects.filter(booking__customer=user).order_by("-created_at") def get_serializer_class(self): if self.action == "create": return PaymentCreateSerializer return PaymentSerializer def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) booking = Booking.objects.get(id=serializer.validated_data["booking_id"]) if not user_can_access_booking(request.user, booking): return Response({"detail": _("Not allowed")}, status=status.HTTP_403_FORBIDDEN) payment, created, redirect_url = create_payment_for_booking( booking=booking, provider=serializer.validated_data["provider"], idempotency_key=serializer.validated_data["idempotency_key"], source=serializer.validated_data["source"], callback_url=serializer.validated_data.get("callback_url"), ) response_data = PaymentSerializer(payment).data response_data["redirect_url"] = redirect_url response_data["created"] = created return Response(response_data, status=status.HTTP_201_CREATED if created else status.HTTP_200_OK) @api_view(["POST"]) @permission_classes([permissions.AllowAny]) def payment_webhook(request): secret = os.getenv("MOYASAR_WEBHOOK_SECRET") payload = request.data or {} if not secret: return Response({"detail": _("Webhook secret not configured")}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) if payload.get("secret_token") != secret: return Response({"detail": _("Invalid webhook signature")}, status=status.HTTP_401_UNAUTHORIZED) event_type = payload.get("type") data = payload.get("data") or {} external_id = data.get("id") if not external_id: return Response({"detail": _("Missing payment reference")}, status=status.HTTP_400_BAD_REQUEST) payment = Payment.objects.filter(external_id=external_id, provider=PaymentProvider.MOYASAR).first() if not payment: logger.warning("Moyasar webhook for unknown payment %s", external_id) return Response({"detail": _("Payment not found")}, status=status.HTTP_200_OK) applied = apply_webhook_event(payment, event_type, payload) if not applied: return Response({"detail": _("Event ignored")}, status=status.HTTP_200_OK) return Response({"detail": _("Webhook processed")}, status=status.HTTP_200_OK)