inital commit
This commit is contained in:
137
payment/views.py
Normal file
137
payment/views.py
Normal file
@@ -0,0 +1,137 @@
|
||||
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,
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user