refactor: update route for downloader to apps/downloader chore: remove unused filler model files refactor: delete Default layout component and its imports
243 lines
7.7 KiB
Python
243 lines
7.7 KiB
Python
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)
|