190 lines
6.3 KiB
Python
190 lines
6.3 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 . import client as goclient
|
||
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",
|
||
description="Vytvoří platbu v GoPay. Objekt 'payment' je předán SDK beze změn.",
|
||
request=PaymentCreateSerializer,
|
||
responses={
|
||
201: OpenApiResponse(
|
||
response=OpenApiTypes.OBJECT,
|
||
description="Platba vytvořena (odpověď z GoPay)."
|
||
),
|
||
502: OpenApiResponse(description="Chyba upstream GoPay"),
|
||
},
|
||
examples=[
|
||
OpenApiExample(
|
||
"Základní platba kartou",
|
||
value={
|
||
"payment": {
|
||
"amount": 19900,
|
||
"currency": "CZK",
|
||
"order_number": "ORDER-1001",
|
||
"items": [{"name": "T-Shirt", "amount": 19900, "count": 1}],
|
||
"callback": {"return_url": "https://shop.example.com/return"},
|
||
}
|
||
},
|
||
)
|
||
],
|
||
extensions={
|
||
"x-codeSamples": [
|
||
{
|
||
"lang": "typescript",
|
||
"label": "Client.auth (frontend)",
|
||
"source": 'await Client.auth.post("/api/payments/gopay/create/", { payment });',
|
||
},
|
||
{
|
||
"lang": "curl",
|
||
"source": 'curl -X POST http://localhost:8000/api/payments/gopay/create/ -H "Content-Type: application/json" -d \'{"payment": {"amount":19900,"currency":"CZK"}}\'',
|
||
},
|
||
]
|
||
},
|
||
)
|
||
def post(self, request):
|
||
ser = PaymentCreateSerializer(data=request.data)
|
||
ser.is_valid(raise_exception=True)
|
||
payload = ser.validated_data["payment"]
|
||
|
||
try:
|
||
res = goclient.create_payment(payload) # Expecting dict-like response
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
# Map fields defensively
|
||
gopay_id = str((res.get("id") if isinstance(res, dict) else getattr(res, "id", "")) or "")
|
||
status_text = (res.get("state") if isinstance(res, dict) else getattr(res, "state", "")) or ""
|
||
amount = int((res.get("amount") if isinstance(res, dict) else getattr(res, "amount", 0)) or payload.get("amount") or 0)
|
||
currency = (res.get("currency") if isinstance(res, dict) else getattr(res, "currency", "")) or payload.get("currency", "")
|
||
|
||
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,
|
||
status=status_text,
|
||
preauthorized=bool(payload.get("preauthorize", False)),
|
||
request_payload=payload,
|
||
response_payload=res if isinstance(res, dict) else {"raw": str(res)},
|
||
)
|
||
|
||
return Response({"payment": 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 = goclient.get_status(payment_id)
|
||
except Exception as e:
|
||
return Response({"detail": str(e)}, status=status.HTTP_502_BAD_GATEWAY)
|
||
|
||
# Update local status if we have it
|
||
try:
|
||
local = GoPayPayment.objects.get(gopay_id=str(payment_id))
|
||
status_text = (res.get("state") if isinstance(res, dict) else getattr(res, "state", "")) or ""
|
||
if status_text:
|
||
local.status = status_text
|
||
local.response_payload = res if isinstance(res, dict) else {"raw": str(res)}
|
||
local.save(update_fields=["status", "response_payload", "updated_at"])
|
||
except GoPayPayment.DoesNotExist:
|
||
pass
|
||
|
||
return Response(res, 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 = goclient.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)
|