from django.conf import settings from django.db import transaction from django.db.models import Q from django.utils import timezone from rest_framework import decorators, mixins, response, status, viewsets from apps.core.errors import error_response from apps.payments.gateways import get_gateway from apps.payments.models import Payment from apps.payments.serializers import PaymentSerializer from apps.rbac.permissions import ActionPermission class PaymentViewSet( mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, viewsets.GenericViewSet ): serializer_class = PaymentSerializer permission_classes = [ActionPermission] code_map = { "list": "invoice.view", "retrieve": "invoice.view", "create": "reservation.create", "verify": "invoice.create", } def get_queryset(self): user = self.request.user qs = Payment.objects.select_related("order", "reservation").all() if user.is_superuser: return qs return qs.filter(Q(order__customer__user=user) | Q(reservation__customer__user=user)) def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) order = serializer.validated_data.get("order") reservation = serializer.validated_data.get("reservation") if not order and not reservation: return response.Response( error_response("PAYMENT_TARGET_REQUIRED", "An order or reservation is required."), status=status.HTTP_400_BAD_REQUEST, ) amount = serializer.validated_data["amount"] currency = order.currency if order else getattr(settings, "BASE_CURRENCY", "IRR") reference = order.order_number if order else f"RES-{str(reservation.id)[:8]}" gateway = get_gateway() callback_url = request.build_absolute_uri("/api/v1/payments/callback/") result = gateway.request_payment(str(amount), currency, reference, callback_url) payment = Payment.objects.create( order=order, reservation=reservation, amount=amount, currency=currency, gateway=settings.PAYMENT_GATEWAY_PROVIDER or "mock", authority=result["authority"], status=Payment.Status.PENDING, request_time=timezone.now(), ) return response.Response( {"id": str(payment.id), "authority": result["authority"], "redirect_url": result["redirect_url"]}, status=status.HTTP_201_CREATED, ) @decorators.action(detail=True, methods=["post"]) @transaction.atomic def verify(self, request, pk=None): payment = Payment.objects.select_for_update().get(pk=pk) # Idempotent: a duplicate callback must not create a duplicate success. if payment.status == Payment.Status.SUCCESS: return response.Response(PaymentSerializer(payment).data) gateway = get_gateway() result = gateway.verify(payment.authority, str(payment.amount)) payment.verification_time = timezone.now() if result.get("status") == "success": payment.status = Payment.Status.SUCCESS payment.transaction_id = result["transaction_id"] else: payment.status = Payment.Status.FAILED payment.error = result.get("error", "") payment.save() return response.Response(PaymentSerializer(payment).data)