Implement Moyasar payments flow with webhooks

This commit is contained in:
2026-02-28 13:01:12 +03:00
parent d9767ff0a7
commit f3c93f500e
12 changed files with 600 additions and 43 deletions
+48 -13
View File
@@ -1,10 +1,17 @@
from rest_framework import permissions, status, viewsets
from rest_framework.response import Response
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
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:
@@ -41,14 +48,42 @@ class PaymentViewSet(viewsets.ModelViewSet):
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 = serializer.save()
return Response(
{
"detail": _("Payment record created. Provider integration pending."),
"payment_id": payment.id,
"amount": str(payment.amount),
"currency": payment.currency,
"status": payment.status,
},
status=status.HTTP_201_CREATED,
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)