from __future__ import annotations # Tato aplikace poskytuje samostatné HTTP API pro GoPay, připravené k připojení do e‑shopu. # Pohledy volají GoPay SDK a ukládají minimální data o životním cyklu (platby/refundy). # Koncové body jsou dokumentovány pro Swagger/Redoc pomocí drf-spectacular. from rest_framework import permissions, status from rest_framework.response import Response from rest_framework.views import APIView from drf_spectacular.utils import ( extend_schema, OpenApiParameter, OpenApiResponse, OpenApiExample, OpenApiTypes, ) from django.conf import settings from django.core.exceptions import ImproperlyConfigured import gopay from gopay.enums import TokenScope, Language def payments_client(): """Create a fresh GoPay payments client from settings. Keeps it simple and explicit; no shared global state. """ required = [ ("GOPAY_GOID", settings.GOPAY_GOID), ("GOPAY_CLIENT_ID", settings.GOPAY_CLIENT_ID), ("GOPAY_CLIENT_SECRET", settings.GOPAY_CLIENT_SECRET), ("GOPAY_GATEWAY_URL", settings.GOPAY_GATEWAY_URL), ] missing = [name for name, val in required if not val] if missing: raise ImproperlyConfigured(f"Missing GoPay settings: {', '.join(missing)}") cfg = { "goid": settings.GOPAY_GOID, "client_id": settings.GOPAY_CLIENT_ID, "client_secret": settings.GOPAY_CLIENT_SECRET, "gateway_url": settings.GOPAY_GATEWAY_URL, # reasonable defaults; can be changed later via settings if desired "scope": TokenScope.ALL, "language": Language.CZECH, } return gopay.payments(cfg) from .models import GoPayPayment, GoPayRefund from .serializers import ( PaymentCreateSerializer, RefundSerializer, ) class CreatePaymentView(APIView): """Vytvoří novou platbu v GoPay a uloží odpověď lokálně. Typický e‑shop flow: - Frontend zavolá tento endpoint s GoPay payloadem. - Backend předá payload GoPay SDK a vrátí upstream odpověď. - Vznikne lokální záznam GoPayPayment pro pozdější sledování stavu. """ permission_classes = [permissions.IsAuthenticated] @extend_schema( tags=["gopay"], operation_id="gopay_create_payment", summary="Vytvořit platbu (minimální vstup)", description=( "Vytvoří platbu v GoPay s minimálními povinnými poli. Citlivé údaje o kartě se neposílají," " platbu obslouží GoPay stránka (gw_url). Pokud není zadán notification_url, použije se" " hodnota ze settings.GOPAY_NOTIFICATION_URL." ), request=PaymentCreateSerializer, responses={ 201: OpenApiResponse( response=OpenApiTypes.OBJECT, description="Platba vytvořena. Vrací gw_url pro přesměrování na platební bránu." ), 502: OpenApiResponse(description="Chyba upstream GoPay"), }, examples=[ OpenApiExample( "Minimální platba", value={ "payment": { "amount": 10000, "currency": "CZK", "order_number": "123456", "payer": {"contact": {"email": "john.doe@example.com"}}, "callback": { "return_url": "https://example.com/your-return-url", "notification_url": "https://example.com/your-notify-url" } } }, ) ], ) def post(self, request): ser = PaymentCreateSerializer(data=request.data) ser.is_valid(raise_exception=True) payload = ser.validated_data["payment"] try: res = payments_client().create_payment(payload) # Expecting dict-like/dict response except Exception as e: return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY) # Map fields defensively as_dict = res if isinstance(res, dict) else getattr(res, "__dict__", {}) or {} gopay_id = str(as_dict.get("id", "")) status_text = str(as_dict.get("state", "")) amount = int(as_dict.get("amount", payload.get("amount", 0)) or 0) currency = as_dict.get("currency", payload.get("currency", "")) gw_url = as_dict.get("gw_url") or as_dict.get("gwUrl") or as_dict.get("gw-url") payment = GoPayPayment.objects.create( user=request.user if request.user and request.user.is_authenticated else None, gopay_id=gopay_id or payload.get("id", ""), order_number=str(payload.get("order_number", "")), amount=amount, currency=currency or "", status=status_text, preauthorized=bool(payload.get("preauthorize", False)), request_payload=payload, response_payload=as_dict or {"raw": str(res)}, ) return Response( { "payment_id": payment.gopay_id, "state": payment.status, "gw_url": gw_url, "raw": payment.response_payload, }, status=status.HTTP_201_CREATED, ) class PaymentStatusView(APIView): """Načte aktuální stav platby GoPay a případně synchronizuje lokální záznam.""" permission_classes = [permissions.IsAuthenticated] @extend_schema( tags=["gopay"], operation_id="gopay_get_status", summary="Získat stav platby", parameters=[ OpenApiParameter( name="payment_id", type=OpenApiTypes.STR, location=OpenApiParameter.PATH, description="ID platby GoPay", ) ], responses={ 200: OpenApiResponse(OpenApiTypes.OBJECT, description="Aktuální stav platby"), 502: OpenApiResponse(description="Chyba upstream GoPay"), }, ) def get(self, request, payment_id: str | int): try: res = payments_client().get_status(payment_id) except Exception as e: return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY) # Normalize GoPay SDK response into a dict body body = None if hasattr(res, "success") and hasattr(res, "json"): # Official SDK-style: check success and extract JSON body ok = bool(getattr(res, "success", False)) if not ok: return Response({"detail": "GoPay upstream error", "success": False}, status=status.HTTP_502_BAD_GATEWAY) json_attr = getattr(res, "json") body = json_attr() if callable(json_attr) else json_attr elif isinstance(res, dict): body = res else: body = getattr(res, "__dict__", {}) or {"raw": str(res)} state_val = body.get("state") if isinstance(body, dict) else None # Update local status if we have it try: local = GoPayPayment.objects.get(gopay_id=str(payment_id)) if state_val: local.status = state_val local.response_payload = body if isinstance(body, dict) else {"raw": str(body)} local.save(update_fields=["status", "response_payload", "updated_at"]) except GoPayPayment.DoesNotExist: pass return Response(body, status=status.HTTP_200_OK) class RefundPaymentView(APIView): """Provede refundaci platby v GoPay a uloží záznam refundace.""" permission_classes = [permissions.IsAuthenticated] @extend_schema( tags=["gopay"], operation_id="gopay_refund_payment", summary="Refundovat platbu", parameters=[ OpenApiParameter( name="payment_id", type=OpenApiTypes.STR, location=OpenApiParameter.PATH, description="ID platby GoPay k refundaci", ) ], request=RefundSerializer, responses={ 200: OpenApiResponse(OpenApiTypes.OBJECT, description="Výsledek refundace"), 502: OpenApiResponse(description="Chyba upstream GoPay"), }, ) def post(self, request, payment_id: str | int): ser = RefundSerializer(data=request.data) ser.is_valid(raise_exception=True) amount = ser.validated_data["amount"] try: res = payments_client().refund_payment(payment_id, amount) except Exception as e: return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY) payment = GoPayPayment.objects.filter(gopay_id=str(payment_id)).first() ref = GoPayRefund.objects.create( payment=payment, gopay_refund_id=str((res.get("id") if isinstance(res, dict) else getattr(res, "id", "")) or ""), amount=amount, status=(res.get("state") if isinstance(res, dict) else getattr(res, "state", "")) or "", payload=res if isinstance(res, dict) else {"raw": str(res)}, ) return Response({"refund": ref.payload}, status=status.HTTP_200_OK)