90 lines
3.9 KiB
Python
90 lines
3.9 KiB
Python
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)
|