Major refactor of commerce and Stripe integration
Refactored commerce models to support refunds, invoices, and improved carrier/payment logic. Added new serializers and viewsets for products, categories, images, discount codes, and refunds. Introduced Stripe client integration and removed legacy Stripe admin/model code. Updated Dockerfile for PDF generation dependencies. Removed obsolete migration files and updated configuration app initialization. Added invoice template and tasks for order cleanup.
This commit is contained in:
@@ -1,30 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-29 14:53
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DownloaderRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('url', models.URLField()),
|
||||
('download_time', models.DateTimeField(auto_now_add=True)),
|
||||
('format', models.CharField(max_length=50)),
|
||||
('length_of_media', models.IntegerField(help_text='Length of media in seconds')),
|
||||
('file_size', models.BigIntegerField(help_text='File size in bytes')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
21
backend/thirdparty/stripe/admin.py
vendored
21
backend/thirdparty/stripe/admin.py
vendored
@@ -1,23 +1,2 @@
|
||||
from django.contrib import admin
|
||||
from .models import Order
|
||||
|
||||
@admin.register(Order)
|
||||
class OrderAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "amount", "currency", "status", "created_at")
|
||||
list_filter = ("status", "currency", "created_at")
|
||||
search_fields = ("id", "stripe_session_id", "stripe_payment_intent")
|
||||
readonly_fields = ("created_at", "stripe_session_id", "stripe_payment_intent")
|
||||
|
||||
fieldsets = (
|
||||
(None, {
|
||||
"fields": ("amount", "currency", "status")
|
||||
}),
|
||||
("Stripe info", {
|
||||
"fields": ("stripe_session_id", "stripe_payment_intent"),
|
||||
"classes": ("collapse",),
|
||||
}),
|
||||
("Metadata", {
|
||||
"fields": ("created_at",),
|
||||
}),
|
||||
)
|
||||
ordering = ("-created_at",)
|
||||
|
||||
54
backend/thirdparty/stripe/client.py
vendored
Normal file
54
backend/thirdparty/stripe/client.py
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
import stripe
|
||||
from django.conf import settings
|
||||
import json
|
||||
import os
|
||||
|
||||
FRONTEND_URL = os.getenv("FRONTEND_URL") if not settings.DEBUG else os.getenv("DEBUG_DOMAIN")
|
||||
SSL = "https://" if os.getenv("USE_SSL") == "true" else "http://"
|
||||
|
||||
stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
|
||||
|
||||
class StripeClient:
|
||||
|
||||
def create_checkout_session(order):
|
||||
"""
|
||||
Vytvoří Stripe Checkout Session pro danou objednávku.
|
||||
Args:
|
||||
order (Order): Instance objednávky pro kterou se vytváří session.
|
||||
|
||||
Returns:
|
||||
stripe.checkout.Session: Vytvořená Stripe Checkout Session.
|
||||
"""
|
||||
|
||||
session = stripe.checkout.Session.create(
|
||||
mode="payment",
|
||||
payment_method_types=["card"],
|
||||
|
||||
success_url=f"{SSL}{FRONTEND_URL}/payment/success?order={order.id}", #jenom na grafickou část (webhook reálně ověří stav)
|
||||
cancel_url=f"{SSL}{FRONTEND_URL}/payment/cancel?order={order.id}",
|
||||
|
||||
client_reference_id=str(order.id),
|
||||
line_items=[{
|
||||
"price_data": {
|
||||
"currency": "czk",
|
||||
"product_data": {
|
||||
"name": f"Objednávka {order.id}",
|
||||
},
|
||||
"unit_amount": int(order.total_price * 100), # cena v haléřích
|
||||
},
|
||||
"quantity": 1,
|
||||
}],
|
||||
)
|
||||
|
||||
return session
|
||||
|
||||
|
||||
def refund_order(stripe_payment_intent):
|
||||
try:
|
||||
refund = stripe.Refund.create(
|
||||
payment_intent=stripe_payment_intent
|
||||
)
|
||||
return refund
|
||||
|
||||
except Exception as e:
|
||||
return json.dumps({"error": str(e)})
|
||||
@@ -1,26 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-28 22:28
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('currency', models.CharField(default='czk', max_length=10)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('paid', 'Paid'), ('failed', 'Failed'), ('cancelled', 'Cancelled')], default='pending', max_length=20)),
|
||||
('stripe_session_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('stripe_payment_intent', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
),
|
||||
]
|
||||
67
backend/thirdparty/stripe/models.py
vendored
67
backend/thirdparty/stripe/models.py
vendored
@@ -1,25 +1,68 @@
|
||||
from django.db import models
|
||||
from django.apps import apps
|
||||
|
||||
# Create your models here.
|
||||
|
||||
#TODO: logika a interakce bude na stripu (třeba aktualizovaní objednávky na zaplacenou apod.)
|
||||
|
||||
class StripePayment(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
("pending", "Pending"),
|
||||
("paid", "Paid"),
|
||||
("failed", "Failed"),
|
||||
("cancelled", "Cancelled"),
|
||||
]
|
||||
|
||||
amount = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
currency = models.CharField(max_length=10, default="czk")
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="pending")
|
||||
from .client import StripeClient
|
||||
|
||||
class StripeModel(models.Model):
|
||||
class STATUS_CHOICES(models.TextChoices):
|
||||
PENDING = "pending", "Čeká se na platbu"
|
||||
PAID = "paid", "Zaplaceno"
|
||||
FAILED = "failed", "Neúspěšné"
|
||||
CANCELLED = "cancelled", "Zrušeno"
|
||||
REFUNDING = "refunding", "Platba se vrací"
|
||||
REFUNDED = "refunded", "Platba úspěšně vrácena"
|
||||
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES.choices, default=STATUS_CHOICES.PENDING)
|
||||
|
||||
stripe_session_id = models.CharField(max_length=255, blank=True, null=True)
|
||||
stripe_payment_intent = models.CharField(max_length=255, blank=True, null=True)
|
||||
stripe_session_url = models.URLField(blank=True, null=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True, null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Order {self.id} - {self.status}"
|
||||
return f"Order {self.id} - {self.status}"
|
||||
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
#if new
|
||||
if self.pk:
|
||||
Order = apps.get_model('commerce', 'Order')
|
||||
Payment = apps.get_model('commerce', 'Payment')
|
||||
|
||||
order = Order.objects.get(payment=Payment.objects.get(stripe=self))
|
||||
|
||||
session = StripeClient.create_checkout_session(order)# <-- předáme self.StripePayment
|
||||
|
||||
self.stripe_session_id = session.id
|
||||
self.stripe_payment_intent = session.payment_intent
|
||||
self.stripe_session_url = session.url
|
||||
|
||||
else:
|
||||
self.updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def paid(self):
|
||||
self.status = self.STATUS_CHOICES.PAID
|
||||
self.save()
|
||||
|
||||
def refund(self):
|
||||
StripeClient.refund_order(self.stripe_payment_intent)
|
||||
self.status = self.STATUS_CHOICES.REFUNDING
|
||||
self.save()
|
||||
|
||||
def refund_confirmed(self):
|
||||
self.status = self.STATUS_CHOICES.REFUNDED
|
||||
self.save()
|
||||
|
||||
def cancel(self):
|
||||
StripeClient.cancel_checkout_session(self.stripe_session_id)
|
||||
self.status = self.STATUS_CHOICES.CANCELLED
|
||||
self.save()
|
||||
9
backend/thirdparty/stripe/stripe.md
vendored
Normal file
9
backend/thirdparty/stripe/stripe.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Stripe Tutorial
|
||||
|
||||
## Example of redirecting the webhook events to local Django endpoint
|
||||
```
|
||||
stripe listen --forward-to localhost:8000/api/stripe/webhook/
|
||||
```
|
||||
|
||||
|
||||
# POUŽÍVEJTE SANDBOX/TESING REŽIM PŘI DEVELOPMENTU!!!
|
||||
100
backend/thirdparty/stripe/views.py
vendored
100
backend/thirdparty/stripe/views.py
vendored
@@ -6,73 +6,73 @@ from rest_framework import generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from drf_spectacular.utils import extend_schema
|
||||
|
||||
from .models import Order
|
||||
from .serializers import OrderSerializer
|
||||
import os
|
||||
import logging
|
||||
|
||||
from .models import StripeTransaction
|
||||
from commerce.models import Order, Payment
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
import stripe
|
||||
stripe.api_key = os.getenv("STRIPE_SECRET_KEY")
|
||||
|
||||
class CreateCheckoutSessionView(APIView):
|
||||
class StripeWebhook(APIView):
|
||||
@extend_schema(
|
||||
tags=["stripe"],
|
||||
)
|
||||
def post(self, request):
|
||||
serializer = OrderSerializer(data=request.data) #obecný serializer
|
||||
serializer.is_valid(raise_exception=True)
|
||||
payload = request.body
|
||||
sig_header = request.META['HTTP_STRIPE_SIGNATURE']
|
||||
|
||||
order = Order.objects.create(
|
||||
amount=serializer.validated_data["amount"],
|
||||
currency=serializer.validated_data.get("currency", "czk"),
|
||||
)
|
||||
try:
|
||||
#build stripe event
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload, sig_header, os.getenv("STRIPE_WEBHOOK_SECRET")
|
||||
)
|
||||
|
||||
# Vytvoření Stripe Checkout Session
|
||||
session = stripe.checkout.Session.create(
|
||||
payment_method_types=["card"],
|
||||
line_items=[{
|
||||
"price_data": {
|
||||
"currency": order.currency,
|
||||
"product_data": {"name": f"Order {order.id}"},
|
||||
"unit_amount": int(order.amount * 100), # v centech
|
||||
},
|
||||
"quantity": 1,
|
||||
}],
|
||||
mode="payment",
|
||||
success_url=request.build_absolute_uri(f"/payment/success/{order.id}"),
|
||||
cancel_url=request.build_absolute_uri(f"/payment/cancel/{order.id}"),
|
||||
)
|
||||
except ValueError as e:
|
||||
logger.error(f"Invalid payload: {e}")
|
||||
return HttpResponse(status=400)
|
||||
|
||||
except stripe.error as e:
|
||||
# stripe error
|
||||
logger.error(f"Stripe error: {e}")
|
||||
return HttpResponse(status=400)
|
||||
|
||||
|
||||
order.stripe_session_id = session.id
|
||||
order.stripe_payment_intent = session.payment_intent
|
||||
order.save()
|
||||
|
||||
data = OrderSerializer(order).data
|
||||
data["checkout_url"] = session.url
|
||||
return Response(data)
|
||||
|
||||
session = event['data']['object']
|
||||
|
||||
|
||||
# ZAPLACENO
|
||||
if event['type'] == 'checkout.session.completed':
|
||||
|
||||
stripe_transaction = StripeTransaction.objects.get(stripe_session_id=session.id)
|
||||
|
||||
if stripe_transaction:
|
||||
stripe_transaction.paid()
|
||||
|
||||
@csrf_exempt
|
||||
def stripe_webhook(request):
|
||||
payload = request.body
|
||||
sig_header = request.META.get("HTTP_STRIPE_SIGNATURE")
|
||||
event = None
|
||||
logger.info(f"Transaction {stripe_transaction.id} marked as paid.")
|
||||
|
||||
try:
|
||||
event = stripe.Webhook.construct_event(
|
||||
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
|
||||
)
|
||||
except stripe.error.SignatureVerificationError:
|
||||
return HttpResponse(status=400)
|
||||
else:
|
||||
logger.warning(f"No transaction found for session ID: {session.id}")
|
||||
|
||||
if event["type"] == "checkout.session.completed":
|
||||
session = event["data"]["object"]
|
||||
order = Order.objects.filter(stripe_session_id=session.get("id")).first()
|
||||
if order:
|
||||
order.status = "paid"
|
||||
# EXPIRACE (zrušení objednávky) uživatel nezaplatil do 24 hodin!
|
||||
elif event['type'] == 'checkout.session.expired':
|
||||
order = Order.objects.get(payment=Payment.objects.get(stripe=StripeTransaction.objects.get(stripe_session_id=session.id)))
|
||||
order.status = Order.STATUS_CHOICES.CANCELLED
|
||||
order.save()
|
||||
|
||||
return HttpResponse(status=200)
|
||||
elif event['type'] == 'payment_intent.payment_failed':
|
||||
#nothing to do for now
|
||||
pass
|
||||
|
||||
# REFUND POTVRZEN
|
||||
elif event['type'] == 'payment_intent.refunded':
|
||||
session = event['data']['object']
|
||||
stripe_transaction = StripeTransaction.objects.get(stripe_payment_intent=session.id)
|
||||
|
||||
if stripe_transaction:
|
||||
stripe_transaction.refund_confirmed()
|
||||
|
||||
logger.info(f"Transaction {stripe_transaction.id} marked as refunded.")
|
||||
|
||||
BIN
backend/thirdparty/stripe/where to find webhooks settings.png
vendored
Normal file
BIN
backend/thirdparty/stripe/where to find webhooks settings.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 108 KiB |
38
backend/thirdparty/zasilkovna/models.py
vendored
38
backend/thirdparty/zasilkovna/models.py
vendored
@@ -21,10 +21,11 @@ from django.db import models
|
||||
from django.utils import timezone
|
||||
from django.core.validators import RegexValidator
|
||||
from django.core.files.base import ContentFile
|
||||
from django.apps import apps
|
||||
|
||||
from .client import PacketaAPI
|
||||
from commerce.models import Order, Carrier
|
||||
from configuration.models import Configuration
|
||||
|
||||
from configuration.models import ShopConfiguration
|
||||
|
||||
packeta_client = PacketaAPI() # single reusable instance
|
||||
|
||||
@@ -55,6 +56,13 @@ class ZasilkovnaPacket(models.Model):
|
||||
help_text="Hmotnost zásilky v gramech"
|
||||
)
|
||||
|
||||
# 🚚 návratové směrovací kódy (pro vrácení zásilky)
|
||||
return_routing = models.JSONField(
|
||||
default=list,
|
||||
blank=True,
|
||||
help_text="Seznam 2 routing stringů pro vrácení zásilky"
|
||||
)
|
||||
|
||||
class PDF_SIZE(models.TextChoices):
|
||||
A6_ON_A6 = ("A6 on A6", "105x148 mm (A6) label on a page of the same size")
|
||||
A7_ON_A7 = ("A7 on A7", "105x74 mm (A7) label on a page of the same size")
|
||||
@@ -63,19 +71,16 @@ class ZasilkovnaPacket(models.Model):
|
||||
A8_ON_A8 = ("A8 on A8", "50x74 mm (A8) label on a page of the same size")
|
||||
size_of_pdf = models.CharField(max_length=20, choices=PDF_SIZE.choices, default=PDF_SIZE.A6_ON_A6)
|
||||
|
||||
|
||||
# 🚚 návratové směrovací kódy (pro vrácení zásilky)
|
||||
return_routing = models.JSONField(
|
||||
default=list,
|
||||
blank=True,
|
||||
help_text="Seznam 2 routing stringů pro vrácení zásilky"
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# On first save, create the packet remotely if packet_id is not set
|
||||
# workaroud to avoid circular import
|
||||
Carrier = apps.get_model('commerce', 'Carrier')
|
||||
Order = apps.get_model('commerce', 'Order')
|
||||
|
||||
carrier = Carrier.objects.get(zasilkovna=self)
|
||||
order = Order.objects.get(carrier=carrier)
|
||||
|
||||
cash_on_delivery = order.payment.payment_method == order.payment.PAYMENT.CASH_ON_DELIVERY
|
||||
|
||||
if not self.packet_id:
|
||||
response = packeta_client.create_packet(
|
||||
address_id=self.addressId,
|
||||
@@ -85,14 +90,13 @@ class ZasilkovnaPacket(models.Model):
|
||||
surname=order.last_name,
|
||||
company=order.company,
|
||||
email=order.email,
|
||||
addressId=Configuration.get_solo().zasilkovna_address_id,
|
||||
addressId=ShopConfiguration.get_solo().zasilkovna_address_id,
|
||||
|
||||
#FIXME: udělat logiku pro počítaní dobírky a hodnoty zboží
|
||||
cod=100.00,
|
||||
value=100.00,
|
||||
cod=order.total_price if cash_on_delivery else 0, # dobírka
|
||||
value=order.total_price,
|
||||
|
||||
currency=Configuration.get_solo().currency,
|
||||
eshop= Configuration.get_solo().name,
|
||||
currency=ShopConfiguration.get_solo().currency,
|
||||
eshop= ShopConfiguration.get_solo().name,
|
||||
)
|
||||
self.packet_id = response['packet_id']
|
||||
self.barcode = response['barcode']
|
||||
|
||||
38
backend/thirdparty/zasilkovna/serializers.py
vendored
38
backend/thirdparty/zasilkovna/serializers.py
vendored
@@ -0,0 +1,38 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import ZasilkovnaPacket, ZasilkovnaShipment
|
||||
|
||||
|
||||
class ZasilkovnaPacketSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ZasilkovnaPacket
|
||||
fields = [
|
||||
"id",
|
||||
"created_at",
|
||||
"packet_id",
|
||||
"barcode",
|
||||
"state",
|
||||
"weight",
|
||||
"return_routing",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class TrackingURLSerializer(serializers.Serializer):
|
||||
barcode = serializers.CharField(read_only=True)
|
||||
tracking_url = serializers.URLField(read_only=True)
|
||||
|
||||
|
||||
class ZasilkovnaShipmentSerializer(serializers.ModelSerializer):
|
||||
packets = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = ZasilkovnaShipment
|
||||
fields = [
|
||||
"id",
|
||||
"created_at",
|
||||
"shipment_id",
|
||||
"barcode",
|
||||
"packets",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
15
backend/thirdparty/zasilkovna/urls.py
vendored
15
backend/thirdparty/zasilkovna/urls.py
vendored
@@ -0,0 +1,15 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
from .views import ZasilkovnaShipmentViewSet, ZasilkovnaPacketViewSet
|
||||
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r"shipments", ZasilkovnaShipmentViewSet, basename="zasilkovna-shipment")
|
||||
router.register(r"packets", ZasilkovnaPacketViewSet, basename="zasilkovna-packet")
|
||||
|
||||
app_name = "zasilkovna"
|
||||
|
||||
urlpatterns = [
|
||||
path("", include(router.urls)),
|
||||
]
|
||||
|
||||
86
backend/thirdparty/zasilkovna/views.py
vendored
86
backend/thirdparty/zasilkovna/views.py
vendored
@@ -1,8 +1,80 @@
|
||||
#views.py
|
||||
from rest_framework import viewsets, mixins, status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.response import Response
|
||||
|
||||
"""
|
||||
TODO: OBJEDNAVANÍ SE VYVOLÁVA V CARRIER V COMMERCE.MODELS.PY
|
||||
získaní labelu,
|
||||
info o kurýrovi, vracení balíku,
|
||||
vytvoření hromadné expedice
|
||||
"""
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
|
||||
|
||||
from .models import ZasilkovnaShipment, ZasilkovnaPacket
|
||||
from .serializers import (
|
||||
ZasilkovnaShipmentSerializer,
|
||||
ZasilkovnaPacketSerializer,
|
||||
TrackingURLSerializer,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(
|
||||
tags=["Zásilkovna"],
|
||||
summary="List shipments",
|
||||
description="Returns a paginated list of Packeta (Zásilkovna) shipments.",
|
||||
responses={200: ZasilkovnaShipmentSerializer},
|
||||
),
|
||||
retrieve=extend_schema(
|
||||
tags=["Zásilkovna"],
|
||||
summary="Retrieve a shipment",
|
||||
description="Returns detail for a single shipment.",
|
||||
responses={200: ZasilkovnaShipmentSerializer},
|
||||
),
|
||||
)
|
||||
class ZasilkovnaShipmentViewSet(viewsets.ReadOnlyModelViewSet):
|
||||
queryset = ZasilkovnaShipment.objects.all().order_by("-created_at")
|
||||
serializer_class = ZasilkovnaShipmentSerializer
|
||||
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
retrieve=extend_schema(
|
||||
tags=["Zásilkovna"],
|
||||
summary="Retrieve a packet",
|
||||
description="Returns detail for a single packet.",
|
||||
responses={200: ZasilkovnaPacketSerializer},
|
||||
)
|
||||
)
|
||||
class ZasilkovnaPacketViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
|
||||
queryset = ZasilkovnaPacket.objects.all()
|
||||
serializer_class = ZasilkovnaPacketSerializer
|
||||
|
||||
@extend_schema(
|
||||
tags=["Zásilkovna"],
|
||||
summary="Get public tracking URL",
|
||||
description=(
|
||||
"Returns the public Zásilkovna tracking URL derived from the packet's barcode."
|
||||
),
|
||||
responses={200: OpenApiResponse(response=TrackingURLSerializer)},
|
||||
)
|
||||
@action(detail=True, methods=["get"], url_path="tracking-url")
|
||||
def tracking_url(self, request, pk=None):
|
||||
packet: ZasilkovnaPacket = self.get_object()
|
||||
data = {
|
||||
"barcode": packet.barcode,
|
||||
"tracking_url": packet.get_tracking_url(),
|
||||
}
|
||||
return Response(data)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
tags=["Zásilkovna"],
|
||||
summary="Cancel packet",
|
||||
description=(
|
||||
"Cancels the packet through the Packeta API and updates its state to CANCELED. "
|
||||
"No request body is required."
|
||||
),
|
||||
request=None,
|
||||
responses={200: OpenApiResponse(response=ZasilkovnaPacketSerializer)},
|
||||
)
|
||||
@action(detail=True, methods=["patch"], url_path="cancel")
|
||||
def cancel(self, request, pk=None):
|
||||
packet: ZasilkovnaPacket = self.get_object()
|
||||
packet.cancel_packet()
|
||||
serializer = self.get_serializer(packet)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
Reference in New Issue
Block a user