From 10796dcb31261fe4c0904fc122eb39e8800fd010 Mon Sep 17 00:00:00 2001 From: Brunobrno Date: Sun, 5 Oct 2025 23:41:14 +0200 Subject: [PATCH] =?UTF-8?q?integrace=20api,=20stripe,=20vytvo=C5=99en?= =?UTF-8?q?=C3=AD=20commecre=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/launch.json | 15 ++ backend/account/models.py | 45 +----- backend/account/permissions.py | 28 +--- .../templates/emails/advertisment.html | 137 ++++++++++++++++++ backend/advertisement/__init__.py | 0 backend/advertisement/admin.py | 3 + backend/advertisement/apps.py | 6 + backend/advertisement/migrations/__init__.py | 0 backend/advertisement/models.py | 3 + backend/advertisement/tasks.py | 2 + backend/advertisement/tests.py | 3 + backend/advertisement/views.py | 3 + backend/commerce/__init__.py | 0 backend/commerce/admin.py | 17 +++ backend/commerce/apps.py | 6 + backend/commerce/migrations/__init__.py | 0 backend/commerce/models.py | 40 +++++ backend/commerce/serializers.py | 26 ++++ backend/commerce/tests.py | 3 + backend/commerce/views.py | 3 + backend/thirdparty/stripe/admin.py | 22 ++- backend/thirdparty/stripe/models.py | 18 +++ backend/thirdparty/stripe/serializers.py | 56 ++++++- backend/thirdparty/stripe/urls.py | 4 +- backend/thirdparty/stripe/views.py | 130 +++++++++-------- frontend/src/index.css | 6 + 26 files changed, 436 insertions(+), 140 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 backend/account/templates/emails/advertisment.html create mode 100644 backend/advertisement/__init__.py create mode 100644 backend/advertisement/admin.py create mode 100644 backend/advertisement/apps.py create mode 100644 backend/advertisement/migrations/__init__.py create mode 100644 backend/advertisement/models.py create mode 100644 backend/advertisement/tasks.py create mode 100644 backend/advertisement/tests.py create mode 100644 backend/advertisement/views.py create mode 100644 backend/commerce/__init__.py create mode 100644 backend/commerce/admin.py create mode 100644 backend/commerce/apps.py create mode 100644 backend/commerce/migrations/__init__.py create mode 100644 backend/commerce/models.py create mode 100644 backend/commerce/serializers.py create mode 100644 backend/commerce/tests.py create mode 100644 backend/commerce/views.py diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2ba986f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/backend/account/models.py b/backend/account/models.py index ef7d280..226f3a2 100644 --- a/backend/account/models.py +++ b/backend/account/models.py @@ -16,15 +16,6 @@ import logging logger = logging.getLogger(__name__) -# Custom User Manager to handle soft deletion -class CustomUserActiveManager(UserManager): - def get_queryset(self): - return super().get_queryset().filter(is_deleted=False) - -# Custom User Manager to handle all users, including soft deleted -class CustomUserAllManager(UserManager): - def get_queryset(self): - return super().get_queryset() class CustomUser(SoftDeleteModel, AbstractUser): @@ -67,33 +58,6 @@ class CustomUser(SoftDeleteModel, AbstractUser): email = models.EmailField(unique=True, db_index=True) create_time = models.DateTimeField(auto_now_add=True) - - """company_id = models.CharField( - max_length=8, - blank=True, - null=True, - validators=[ - RegexValidator( - regex=r'^\d{8}$', - message="Company ID must contain exactly 8 digits.", - code='invalid_company_id' - ) - ] - )""" - - """personal_id = models.CharField( - max_length=11, - blank=True, - null=True, - validators=[ - RegexValidator( - regex=r'^\d{6}/\d{3,4}$', - message="Personal ID must be in the format 123456/7890.", - code='invalid_personal_id' - ) - ] - )""" - city = models.CharField(null=True, blank=True, max_length=100) street = models.CharField(null=True, blank=True, max_length=200) @@ -113,9 +77,6 @@ class CustomUser(SoftDeleteModel, AbstractUser): is_active = models.BooleanField(default=False) - objects = CustomUserActiveManager() - all_objects = CustomUserAllManager() - REQUIRED_FIELDS = ['email', "username", "password"] @@ -125,14 +86,10 @@ class CustomUser(SoftDeleteModel, AbstractUser): def delete(self, *args, **kwargs): self.is_active = False - #self.orders.all().update(is_deleted=True, deleted_at=timezone.now()) - return super().delete(*args, **kwargs) def save(self, *args, **kwargs): - is_new = self.pk is None # check BEFORE saving - - if is_new: + if self.pk is None: # if newely created user if self.is_superuser or self.role == "admin": self.is_active = True diff --git a/backend/account/permissions.py b/backend/account/permissions.py index 6916300..1ee5b7c 100644 --- a/backend/account/permissions.py +++ b/backend/account/permissions.py @@ -1,41 +1,25 @@ +from urllib import request from rest_framework.permissions import BasePermission, SAFE_METHODS from rest_framework.permissions import IsAuthenticated from rest_framework_api_key.permissions import HasAPIKey -#Podle svého uvážení (NEPOUŽÍVAT!!!) -class RolePermission(BasePermission): - allowed_roles = [] - - def has_permission(self, request, view): - # Je uživatel přihlášený a má roli z povolených? - user_has_role = ( - request.user and - request.user.is_authenticated and - getattr(request.user, "role", None) in self.allowed_roles - ) - - # Má API klíč? - has_api_key = HasAPIKey().has_permission(request, view) - - - return user_has_role or has_api_key - - #TOHLE POUŽÍT!!! #Prostě stačí vložit: RoleAllowed('seller','cityClerk') def RoleAllowed(*roles): """ Allows safe methods for any authenticated user. Allows unsafe methods only for users with specific roles. + Allows access if a valid API key is provided. Args: - RolerAllowed('admin', 'user') + RoleAllowed('admin', 'user') """ class SafeOrRolePermission(BasePermission): - - def has_permission(self, request, view): + # Má API klíč? + has_api_key = HasAPIKey().has_permission(request, view) + # Allow safe methods for any authenticated user if request.method in SAFE_METHODS: return IsAuthenticated().has_permission(request, view) diff --git a/backend/account/templates/emails/advertisment.html b/backend/account/templates/emails/advertisment.html new file mode 100644 index 0000000..0ee4e75 --- /dev/null +++ b/backend/account/templates/emails/advertisment.html @@ -0,0 +1,137 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Nabídka tvorby webových stránek +
+

+ Jsme malý tým, který se snaží prorazit a přinášet moderní řešení za férové + ceny. + Nabízíme také levný hosting a SSL zabezpečení zdarma. +

+

+ Dbáme na bezpečnost, používáme moderní frameworky + a rozhodně nejsme součástí „gerontosaurů“ – PHP nepoužíváme. +

+
+ Balíčky +
+

BASIC

+
    +
  • Jednoduchá prezentační webová stránka
  • +
  • Moderní a responzivní design (PC, tablety, mobily)
  • +
  • Maximalní počet stránek: 5
  • +
  • Použítí vlastní domény a SSL certifikát zdarma
  • +
+

+ Cena: 5 000 Kč + (jednorázově) + 100 Kč / měsíc

+
+

STANDARD

+
    +
  • Vše z balíčku BASIC
  • +
  • Kontaktní formulář, který posílá pobídky na váš email
  • +
  • Větší priorita při řešení problémů a rychlejší vývoj (cca 2 týdny)
  • +
  • Základní SEO
  • +
  • Maximální počet stránek: 10
  • +
+

+ Cena: 7 500 Kč (jednorázově) + 250 Kč / měsíc

+
+

PREMIUM

+
    +
  • Vše z balíčku STANDARD
  • +
  • Registrace firmy do Google Business Profile
  • +
  • Pokročilé SEO (klíčová slova, podpora pro slepce, čtečky)
  • +
  • Měsíční report návštěvnosti webu
  • +
  • Možnost drobných úprav (texty, fotky)
  • +
  • Neomezený počet stránek
  • +
+

+ Cena: od 9 500 Kč (jednorázově) + 400 Kč / měsíc

+
+

CUSTOM

+
    +
  • Kompletně na míru podle potřeb
  • +
  • Možnost e-shopu, rezervačního systému, managment
  • +
  • Integrace jakéhokoliv API
  • +
  • Integrace platební brány (např. Stripe, Platba QR kódem, atd.)
  • +
  • Pokročilé SEO
  • +
  • Marketing skrz Google Ads
  • +
+

+ Cena: dohodou

+
+
+ + + + + + + + + +
+

Máte zájem o některý z balíčků?

+
+

Stačí odpovědět na tento e-mail nebo mě kontaktovat telefonicky:

+

+ + brunovontor@gmail.com + +

+

+ +420 605 512 624 +

+

+ vontor.cz +

+
\ No newline at end of file diff --git a/backend/advertisement/__init__.py b/backend/advertisement/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/advertisement/admin.py b/backend/advertisement/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/advertisement/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/advertisement/apps.py b/backend/advertisement/apps.py new file mode 100644 index 0000000..700e777 --- /dev/null +++ b/backend/advertisement/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AdvertisementConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'advertisement' diff --git a/backend/advertisement/migrations/__init__.py b/backend/advertisement/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/advertisement/models.py b/backend/advertisement/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/backend/advertisement/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/backend/advertisement/tasks.py b/backend/advertisement/tasks.py new file mode 100644 index 0000000..a5bec03 --- /dev/null +++ b/backend/advertisement/tasks.py @@ -0,0 +1,2 @@ +#udělat zasílaní reklamních emailů uživatelům. +#newletter --> když se vytvoří nový record s reklamou email se uloží pomocí zaškrtnutí tlačítka v záznamu \ No newline at end of file diff --git a/backend/advertisement/tests.py b/backend/advertisement/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/advertisement/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/advertisement/views.py b/backend/advertisement/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/backend/advertisement/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/backend/commerce/__init__.py b/backend/commerce/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/commerce/admin.py b/backend/commerce/admin.py new file mode 100644 index 0000000..d76afc4 --- /dev/null +++ b/backend/commerce/admin.py @@ -0,0 +1,17 @@ +from django.contrib import admin +from .models import Carrier, Order +# Register your models here. + + +@admin.register(Carrier) +class CarrierAdmin(admin.ModelAdmin): + list_display = ("name", "price", "api_id") + search_fields = ("name", "api_id") + + +@admin.register(Order) +class OrderAdmin(admin.ModelAdmin): + list_display = ("id", "product", "carrier", "quantity", "total_price", "status", "created_at") + list_filter = ("status", "created_at") + search_fields = ("stripe_session_id",) + readonly_fields = ("total_price", "status", "stripe_session_id", "created_at", "updated_at") diff --git a/backend/commerce/apps.py b/backend/commerce/apps.py new file mode 100644 index 0000000..b996052 --- /dev/null +++ b/backend/commerce/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class CommerceConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'commerce' diff --git a/backend/commerce/migrations/__init__.py b/backend/commerce/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/commerce/models.py b/backend/commerce/models.py new file mode 100644 index 0000000..6a5eb9d --- /dev/null +++ b/backend/commerce/models.py @@ -0,0 +1,40 @@ +from django.db import models + +class Product(models.Model): + name = models.CharField(max_length=200) + description = models.TextField(blank=True) + price = models.DecimalField(max_digits=10, decimal_places=2) + currency = models.CharField(max_length=10, default="czk") + stock = models.PositiveIntegerField(default=0) + is_active = models.BooleanField(default=True) + default_carrier = models.ForeignKey( + "Carrier", on_delete=models.SET_NULL, null=True, blank=True, related_name="default_for_products" + ) + created_at = models.DateTimeField(auto_now_add=True) + + @property + def available(self): + return self.is_active and self.stock > 0 + + def __str__(self): + return f"{self.name} ({self.price} {self.currency.upper()})" + + +# Dopravci a způsoby dopravy +from django.db import models + +class Carrier(models.Model): + name = models.CharField(max_length=100) # název dopravce (Zásilkovna, Česká pošta…) + base_price = models.DecimalField(max_digits=10, decimal_places=2, default=0) # základní cena dopravy + delivery_time = models.CharField(max_length=100, blank=True) # např. "2–3 pracovní dny" + is_active = models.BooleanField(default=True) + + # pole pro logo + logo = models.ImageField(upload_to="carriers/", blank=True, null=True) + + # pole pro propojení s externím API (např. ID služby u Zásilkovny) + external_id = models.CharField(max_length=50, blank=True, null=True) + + def __str__(self): + return f"{self.name} ({self.base_price} Kč)" + diff --git a/backend/commerce/serializers.py b/backend/commerce/serializers.py new file mode 100644 index 0000000..9ff1b14 --- /dev/null +++ b/backend/commerce/serializers.py @@ -0,0 +1,26 @@ +from rest_framework import serializers +from .models import Carrier + +class CarrierSerializer(serializers.ModelSerializer): + class Meta: + model = Carrier + fields = [ + "id", "name", "base_price", "delivery_time", + "is_active", "logo", "external_id" + ] + + +from rest_framework import serializers +from .models import Product, Carrier, Order + + +class ProductSerializer(serializers.ModelSerializer): + class Meta: + model = Product + fields = "__all__" + + +class CarrierSerializer(serializers.ModelSerializer): + class Meta: + model = Carrier + fields = "__all__" \ No newline at end of file diff --git a/backend/commerce/tests.py b/backend/commerce/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/commerce/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/commerce/views.py b/backend/commerce/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/backend/commerce/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/backend/thirdparty/stripe/admin.py b/backend/thirdparty/stripe/admin.py index 8c38f3f..d23cc8e 100644 --- a/backend/thirdparty/stripe/admin.py +++ b/backend/thirdparty/stripe/admin.py @@ -1,3 +1,23 @@ from django.contrib import admin +from .models import Order -# Register your models here. +@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",) diff --git a/backend/thirdparty/stripe/models.py b/backend/thirdparty/stripe/models.py index 71a8362..61a83c1 100644 --- a/backend/thirdparty/stripe/models.py +++ b/backend/thirdparty/stripe/models.py @@ -1,3 +1,21 @@ from django.db import models # Create your models here. + +class Order(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") + stripe_session_id = models.CharField(max_length=255, blank=True, null=True) + stripe_payment_intent = models.CharField(max_length=255, blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Order {self.id} - {self.status}" \ No newline at end of file diff --git a/backend/thirdparty/stripe/serializers.py b/backend/thirdparty/stripe/serializers.py index 7ad1635..f88ce06 100644 --- a/backend/thirdparty/stripe/serializers.py +++ b/backend/thirdparty/stripe/serializers.py @@ -1,12 +1,54 @@ from rest_framework import serializers +from rest_framework import serializers +from .models import Product, Carrier, Order -class StripeCheckoutRequestSerializer(serializers.Serializer): - amount = serializers.DecimalField(max_digits=12, decimal_places=2, min_value=0.01) - product_name = serializers.CharField(required=False, default="Example Product") - success_url = serializers.URLField(required=False) - cancel_url = serializers.URLField(required=False) +from ...commerce.serializers import ProductSerializer, CarrierSerializer -class StripeCheckoutResponseSerializer(serializers.Serializer): - url = serializers.URLField() +class OrderSerializer(serializers.ModelSerializer): + product = ProductSerializer(read_only=True) + product_id = serializers.PrimaryKeyRelatedField( + queryset=Product.objects.all(), source="product", write_only=True + ) + carrier = CarrierSerializer(read_only=True) + carrier_id = serializers.PrimaryKeyRelatedField( + queryset=Carrier.objects.all(), source="carrier", write_only=True + ) + + class Meta: + model = Order + fields = [ + "id", + "product", "product_id", + "carrier", "carrier_id", + "quantity", + "total_price", + "status", + "stripe_session_id", + "created_at", + "updated_at", + ] + read_only_fields = ("total_price", "status", "stripe_session_id", "created_at", "updated_at") + + queryset=Product.objects.all(), source="product", write_only=True + + carrier = CarrierSerializer(read_only=True) + carrier_id = serializers.PrimaryKeyRelatedField( + queryset=Carrier.objects.all(), source="carrier", write_only=True + ) + + class Meta: + model = Order + fields = [ + "id", + "product", "product_id", + "carrier", "carrier_id", + "quantity", + "total_price", + "status", + "stripe_session_id", + "created_at", + "updated_at", + ] + read_only_fields = ("total_price", "status", "stripe_session_id", "created_at", "updated_at") diff --git a/backend/thirdparty/stripe/urls.py b/backend/thirdparty/stripe/urls.py index 02287d5..58182eb 100644 --- a/backend/thirdparty/stripe/urls.py +++ b/backend/thirdparty/stripe/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from .views import StripeCheckoutCZKView +from .views import CreateCheckoutSessionView urlpatterns = [ - path('checkout/', StripeCheckoutCZKView.as_view(), name='stripe-checkout-czk'), + path("orders/create-checkout/", CreateCheckoutSessionView.as_view(), name="create-checkout"), ] \ No newline at end of file diff --git a/backend/thirdparty/stripe/views.py b/backend/thirdparty/stripe/views.py index 8028b08..278bf0c 100644 --- a/backend/thirdparty/stripe/views.py +++ b/backend/thirdparty/stripe/views.py @@ -1,71 +1,73 @@ -import stripe -import os -from rest_framework.views import APIView -from rest_framework.permissions import IsAuthenticated +from django.views.decorators.csrf import csrf_exempt +from django.conf import settings +from django.http import HttpResponse + +from rest_framework import generics from rest_framework.response import Response +from rest_framework.views import APIView -from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiExample, OpenApiParameter -from .serializers import ( - StripeCheckoutRequestSerializer, - StripeCheckoutResponseSerializer, -) +from .models import Order +from .serializers import OrderSerializer -stripe.api_key = os.getenv("STRIPE_SECRET_KEY") +import stripe +stripe.api_key = settings.STRIPE_SECRET_KEY # uložený v .env - -class StripeCheckoutCZKView(APIView): - permission_classes = [IsAuthenticated] - - @extend_schema( - tags=["Stripe"], - summary="Create Stripe Checkout session in CZK", - description="Creates a Stripe Checkout session for payment in Czech Koruna (CZK). Requires authentication.", - request=StripeCheckoutRequestSerializer, - responses={ - 200: OpenApiResponse(response=StripeCheckoutResponseSerializer, description="Stripe Checkout session URL returned successfully."), - 400: OpenApiResponse(description="Amount is required or invalid."), - }, - examples=[ - OpenApiExample( - "Success", - value={"url": "https://checkout.stripe.com/pay/cs_test_123456"}, - response_only=True, - status_codes=["200"], - ), - OpenApiExample( - "Missing amount", - value={"error": "Amount is required"}, - response_only=True, - status_codes=["400"], - ), - ] - ) +class CreateCheckoutSessionView(APIView): def post(self, request): - serializer = StripeCheckoutRequestSerializer(data=request.data) - if not serializer.is_valid(): - return Response(serializer.errors, status=400) + serializer = OrderSerializer(data=request.data) #obecný serializer + serializer.is_valid(raise_exception=True) - amount = serializer.validated_data.get("amount") - product_name = serializer.validated_data.get("product_name", "Example Product") - success_url = serializer.validated_data.get("success_url", "https://yourfrontend.com/success") - cancel_url = serializer.validated_data.get("cancel_url", "https://yourfrontend.com/cancel") - # Stripe expects amount in the smallest currency unit (haléř = 1/100 CZK) - amount_in_haler = int(amount * 100) - session = stripe.checkout.Session.create( - payment_method_types=['card'], - line_items=[{ - 'price_data': { - 'currency': 'czk', - 'product_data': { - 'name': product_name, - }, - 'unit_amount': amount_in_haler, - }, - 'quantity': 1, - }], - mode='payment', - success_url=success_url, - cancel_url=cancel_url, - customer_email=getattr(request.user, 'email', None) + order = Order.objects.create( + amount=serializer.validated_data["amount"], + currency=serializer.validated_data.get("currency", "czk"), ) - return Response({"url": session.url}) + + # 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}"), + ) + + 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) + + + + + +@csrf_exempt +def stripe_webhook(request): + payload = request.body + sig_header = request.META.get("HTTP_STRIPE_SIGNATURE") + event = None + + try: + event = stripe.Webhook.construct_event( + payload, sig_header, settings.STRIPE_WEBHOOK_SECRET + ) + except stripe.error.SignatureVerificationError: + return HttpResponse(status=400) + + 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" + order.save() + + return HttpResponse(status=200) diff --git a/frontend/src/index.css b/frontend/src/index.css index 08a3ac9..7e2f1cc 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -11,6 +11,12 @@ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + --c-background: #031D44; /*background*/ + --c-background-light: #04395E; /*background-highlight*/ + --c-boxes: #24719f;; /*boxes*/ + --c-lines: #87a9da; /*lines*/ + --c-text: #CAF0F8; /*text*/ + --c-other: #70A288; /*other*/ } a {