Files
cetana-backend/apps/payments/views.py
T
2026-09-24 12:32:59 +03:30

83 lines
3.5 KiB
Python

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)