Refactor Stripe payment handling and order status
Refactored Stripe payment integration to use a dedicated Stripe model for session data, updating the PaymentSerializer accordingly. Renamed Order.Status to Order.OrderStatus for clarity. Updated configuration app to ensure SiteConfiguration singleton is created post-migration. Removed obsolete seed_app_config command from docker-compose. Adjusted frontend API generated files and moved orval config.
This commit is contained in:
@@ -104,7 +104,7 @@ class ProductImage(models.Model):
|
|||||||
# ------------------ OBJEDNÁVKY ------------------
|
# ------------------ OBJEDNÁVKY ------------------
|
||||||
|
|
||||||
class Order(models.Model):
|
class Order(models.Model):
|
||||||
class Status(models.TextChoices):
|
class OrderStatus(models.TextChoices):
|
||||||
CREATED = "created", "cz#Vytvořeno"
|
CREATED = "created", "cz#Vytvořeno"
|
||||||
CANCELLED = "cancelled", "cz#Zrušeno"
|
CANCELLED = "cancelled", "cz#Zrušeno"
|
||||||
COMPLETED = "completed", "cz#Dokončeno"
|
COMPLETED = "completed", "cz#Dokončeno"
|
||||||
@@ -113,7 +113,7 @@ class Order(models.Model):
|
|||||||
REFUNDED = "refunded", "cz#Vráceno"
|
REFUNDED = "refunded", "cz#Vráceno"
|
||||||
|
|
||||||
status = models.CharField(
|
status = models.CharField(
|
||||||
max_length=20, choices=Status.choices, null=True, blank=True, default=Status.CREATED
|
max_length=20, choices=OrderStatus.choices, null=True, blank=True, default=OrderStatus.CREATED
|
||||||
)
|
)
|
||||||
|
|
||||||
# Stored order grand total; recalculated on save
|
# Stored order grand total; recalculated on save
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ class OrderItemCreateSerializer(serializers.Serializer):
|
|||||||
|
|
||||||
# -- PAYMENT --
|
# -- PAYMENT --
|
||||||
class PaymentSerializer(serializers.ModelSerializer):
|
class PaymentSerializer(serializers.ModelSerializer):
|
||||||
|
stripe_session_id = serializers.CharField(source='stripe.stripe_session_id', read_only=True)
|
||||||
|
stripe_payment_intent = serializers.CharField(source='stripe.stripe_payment_intent', read_only=True)
|
||||||
|
stripe_session_url = serializers.URLField(source='stripe.stripe_session_url', read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Payment
|
model = Payment
|
||||||
@@ -184,15 +187,15 @@ class PaymentSerializer(serializers.ModelSerializer):
|
|||||||
if payment.payment_method == Payment.PAYMENT.STRIPE:
|
if payment.payment_method == Payment.PAYMENT.STRIPE:
|
||||||
session = StripeClient.create_checkout_session(order)
|
session = StripeClient.create_checkout_session(order)
|
||||||
|
|
||||||
payment.stripe_session_id = session.id
|
stripe_instance = StripeModel.objects.create(
|
||||||
payment.stripe_payment_intent = session.payment_intent
|
stripe_session_id=session.id,
|
||||||
payment.stripe_session_url = session.url
|
stripe_payment_intent=session.payment_intent,
|
||||||
|
stripe_session_url=session.url,
|
||||||
|
status=StripeModel.STATUS_CHOICES.PENDING
|
||||||
|
)
|
||||||
|
|
||||||
payment.save(update_fields=[
|
payment.stripe = stripe_instance
|
||||||
"stripe_session_id",
|
payment.save(update_fields=["stripe"])
|
||||||
"stripe_payment_intent",
|
|
||||||
"stripe_session_url",
|
|
||||||
])
|
|
||||||
|
|
||||||
return payment
|
return payment
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.utils import OperationalError, ProgrammingError
|
from django.db.models.signals import post_migrate
|
||||||
|
|
||||||
|
def create_site_config(sender, **kwargs):
|
||||||
|
"""
|
||||||
|
Ensure the SiteConfiguration singleton exists after migrations.
|
||||||
|
"""
|
||||||
|
from .models import SiteConfiguration
|
||||||
|
try:
|
||||||
|
SiteConfiguration.get_solo()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
class ConfigurationConfig(AppConfig):
|
class ConfigurationConfig(AppConfig):
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
name = 'configuration'
|
name = 'configuration'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
"""Ensure the SiteConfiguration singleton exists at startup.
|
# Spustí create_site_config po dokončení migrací
|
||||||
Wrapped in broad DB error handling so that commands like
|
post_migrate.connect(create_site_config, sender=self)
|
||||||
makemigrations/migrate don't fail when the table does not yet exist.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
from .models import SiteConfiguration # local import to avoid premature app registry access
|
|
||||||
SiteConfiguration.get_solo() # creates if missing
|
|
||||||
|
|
||||||
except (OperationalError, ProgrammingError):
|
|
||||||
# DB not ready (e.g., before initial migrate); ignore silently
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
backend/globalstaticfiles/filler
Normal file
1
backend/globalstaticfiles/filler
Normal file
@@ -0,0 +1 @@
|
|||||||
|
filler
|
||||||
@@ -19,7 +19,6 @@ services:
|
|||||||
python manage.py migrate --verbosity 3 --noinput &&
|
python manage.py migrate --verbosity 3 --noinput &&
|
||||||
python manage.py check &&
|
python manage.py check &&
|
||||||
python manage.py collectstatic --clear --noinput --verbosity 3 &&
|
python manage.py collectstatic --clear --noinput --verbosity 3 &&
|
||||||
python manage.py seed_app_config &&
|
|
||||||
gunicorn -k uvicorn.workers.UvicornWorker vontor_cz.asgi:application --bind 0.0.0.0:8000"
|
gunicorn -k uvicorn.workers.UvicornWorker vontor_cz.asgi:application --bind 0.0.0.0:8000"
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
|
|||||||
330
frontend/package-lock.json
generated
330
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
|||||||
"build": "tsc -b && tsc -b && vite build",
|
"build": "tsc -b && tsc -b && vite build",
|
||||||
"lint": "eslint .",
|
"lint": "eslint .",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"api:gen": "orval --config orval.config.ts"
|
"api:gen": "orval --config src/orval.config.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
"@types/react-router": "^5.1.20",
|
"@types/react-router": "^5.1.20",
|
||||||
"axios": "^1.13.0",
|
"axios": "^1.13.0",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"orval": "^7.13.2",
|
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
@@ -26,8 +25,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.33.0",
|
"@eslint/js": "^9.33.0",
|
||||||
"@tailwindcss/postcss": "^4.1.17",
|
"@tailwindcss/postcss": "^4.1.17",
|
||||||
"@types/axios": "^0.9.36",
|
"@types/node": "^24.10.4",
|
||||||
"@types/node": "^24.10.1",
|
|
||||||
"@types/react": "^19.1.10",
|
"@types/react": "^19.1.10",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"@vitejs/plugin-react": "^5.0.0",
|
"@vitejs/plugin-react": "^5.0.0",
|
||||||
@@ -36,6 +34,7 @@
|
|||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
|
"orval": "^7.13.2",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.39.1",
|
"typescript-eslint": "^8.39.1",
|
||||||
"vite": "^7.1.2"
|
"vite": "^7.1.2"
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
v tehle složce se vygeneruje schema
|
|
||||||
1
frontend/src/api/generated/private/models/filler
Normal file
1
frontend/src/api/generated/private/models/filler
Normal file
@@ -0,0 +1 @@
|
|||||||
|
filler
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import axios from "axios";
|
import axios, { type AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
// použij tohle pro API vyžadující autentizaci
|
// použij tohle pro API vyžadující autentizaci
|
||||||
export const privateApi = axios.create({
|
export const privateApi = axios.create({
|
||||||
@@ -25,3 +25,11 @@ privateApi.interceptors.response.use(
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
export const privateMutator = async <T>(
|
||||||
|
config: AxiosRequestConfig
|
||||||
|
): Promise<T> => {
|
||||||
|
const response = await privateApi.request<T>(config);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@@ -1,7 +1,16 @@
|
|||||||
import axios from "axios";
|
import axios, { type AxiosRequestConfig } from "axios";
|
||||||
|
|
||||||
// použij tohle pro veřejné API nevyžadující autentizaci
|
// použij tohle pro veřejné API nevyžadující autentizaci
|
||||||
export const publicApi = axios.create({
|
export const publicApi = axios.create({
|
||||||
baseURL: "/api/",
|
baseURL: "/api/",
|
||||||
withCredentials: false, // veřejné API NEPOSÍLÁ cookies
|
withCredentials: false, // veřejné API NEPOSÍLÁ cookies
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ⬇⬇⬇ TOHLE JE TEN MUTATOR ⬇⬇⬇
|
||||||
|
export const publicMutator = async <T>(
|
||||||
|
config: AxiosRequestConfig
|
||||||
|
): Promise<T> => {
|
||||||
|
const response = await publicApi.request<T>(config);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import { defineConfig } from "orval";
|
import { defineConfig } from "orval";
|
||||||
import "dotenv/config";
|
import "dotenv/config";
|
||||||
import {process} from "node:process";
|
|
||||||
|
|
||||||
const backendUrl = process.env.VITE_API_BASE_URL || "http://localhost:8000";
|
const backendUrl = process.env.VITE_BACKEND_URL || "http://localhost:8000";
|
||||||
|
|
||||||
// může se hodit pokud nechceme při buildu generovat klienta (nechat false pro produkci nebo vynechat)
|
// může se hodit pokud nechceme při buildu generovat klienta (nechat false pro produkci nebo vynechat)
|
||||||
const SKIP_ORVAL = process.env.SKIP_ORVAL === "true";
|
const SKIP_ORVAL = process.env.SKIP_ORVAL === "true";
|
||||||
@@ -23,8 +22,8 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
target: "src/api/generated/public.ts",
|
target: "api/generated/public.ts",
|
||||||
schemas: "src/api/generated/public/models",
|
schemas: "api/generated/public/models",
|
||||||
|
|
||||||
mode: "tags",
|
mode: "tags",
|
||||||
clean: true,
|
clean: true,
|
||||||
@@ -34,8 +33,8 @@ export default defineConfig({
|
|||||||
|
|
||||||
override: {
|
override: {
|
||||||
mutator: {
|
mutator: {
|
||||||
path: "src/api/publicClient.ts", //IMPORTANTE
|
path: "api/publicClient.ts", //IMPORTANTE
|
||||||
name: "publicApi",
|
name: "publicMutator",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -45,13 +44,11 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
private: {
|
private: {
|
||||||
input: {
|
input: {
|
||||||
target: `${backendUrl}/api/schema/`
|
target: `${backendUrl}/api/schema/`,
|
||||||
|
|
||||||
// No filters, include all endpoints
|
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
target: "src/api/generated/private.ts", //IMPORTANTE
|
target: "api/generated/private.ts", //IMPORTANTE
|
||||||
schemas: "src/api/generated/private/models",
|
schemas: "api/generated/private/models",
|
||||||
|
|
||||||
mode: "tags",
|
mode: "tags",
|
||||||
clean: true,
|
clean: true,
|
||||||
@@ -61,8 +58,8 @@ export default defineConfig({
|
|||||||
|
|
||||||
override: {
|
override: {
|
||||||
mutator: {
|
mutator: {
|
||||||
path: "src/api/privateClient.ts",
|
path: "api/privateClient.ts",
|
||||||
name: "privateApi",
|
name: "privateMutator",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Reference in New Issue
Block a user