diff --git a/bp/cart/global_routes.py b/bp/cart/global_routes.py index 2010e78..3c3c5ed 100644 --- a/bp/cart/global_routes.py +++ b/bp/cart/global_routes.py @@ -6,7 +6,6 @@ from quart import Blueprint, g, request, render_template, redirect, url_for, mak from sqlalchemy import select from models.order import Order -from shared.browser.app.utils.htmx import is_htmx_request from glue.services.order_lifecycle import get_entries_for_order from .services import ( current_cart_identity, @@ -28,7 +27,6 @@ from .services.checkout import ( get_order_with_details, ) from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout -from shared.config import config def register(url_prefix: str) -> Blueprint: diff --git a/bp/cart/page_routes.py b/bp/cart/page_routes.py index 2c8a576..3a19057 100644 --- a/bp/cart/page_routes.py +++ b/bp/cart/page_routes.py @@ -11,7 +11,6 @@ from .services import ( total, clear_cart_for_order, calendar_total, - check_sumup_status, ) from .services.page_cart import get_cart_for_page, get_calendar_entries_for_page from .services.checkout import ( @@ -19,7 +18,6 @@ from .services.checkout import ( build_sumup_description, build_sumup_reference, build_webhook_url, - get_order_with_details, ) from .services import current_cart_identity diff --git a/bp/cart/routes_old.py b/bp/cart/routes_old.py deleted file mode 100644 index 6c77d74..0000000 --- a/bp/cart/routes_old.py +++ /dev/null @@ -1,253 +0,0 @@ -# app/bp/cart/routes.py - -from __future__ import annotations - -from quart import Blueprint, g, request, render_template, redirect, url_for, make_response -from sqlalchemy import select, update -from sqlalchemy.orm import selectinload - -from market.models.market import Product, CartItem -from models.order import Order, OrderItem -from shared.browser.app.payments.sumup import create_checkout as sumup_create_checkout -from .services import ( - current_cart_identity, - get_cart, - total, - clear_cart_for_order, - get_calendar_cart_entries, # NEW - calendar_total, # NEW - check_sumup_status -) -from .services.checkout import ( - find_or_create_cart_item, - create_order_from_cart, - resolve_page_config, - build_sumup_description, - build_sumup_reference, - build_webhook_url, - validate_webhook_secret, - get_order_with_details, -) -from shared.config import config -from events.models.calendars import CalendarEntry # NEW -from shared.browser.app.utils.htmx import is_htmx_request - -def register(url_prefix: str) -> Blueprint: - bp = Blueprint("cart", __name__, url_prefix=url_prefix) - - # NOTE: load_cart moved to shared/cart_loader.py - # and registered in shared/factory.py as an app-level before_request - - - - #@bp.context_processor - #async def inject_root(): - - # return { - # "total": total, - # "calendar_total": calendar_total, # NEW helper - # - # } - - @bp.get("/") - async def view_cart(): - if not is_htmx_request(): - # Normal browser request: full page with layout - html = await render_template( - "_types/cart/index.html", - ) - else: - - html = await render_template( - "_types/cart/_oob_elements.html", - ) - return await make_response(html) - - - @bp.post("/add//") - async def add_to_cart(product_id: int): - ident = current_cart_identity() - - cart_item = await find_or_create_cart_item( - g.s, - product_id, - ident["user_id"], - ident["session_id"], - ) - - if not cart_item: - return await make_response("Product not found", 404) - - # htmx support (optional) - if request.headers.get("HX-Request") == "true": - return await view_cart() - - # normal POST: go to cart page - return redirect(url_for("cart.view_cart")) - - - @bp.post("/checkout/") - async def checkout(): - """Create an Order from the current cart and redirect to SumUp Hosted Checkout.""" - # Build cart - cart = await get_cart(g.s) - calendar_entries = await get_calendar_cart_entries(g.s) - - if not cart and not calendar_entries: - return redirect(url_for("cart.view_cart")) - - product_total = total(cart) or 0 - calendar_amount = calendar_total(calendar_entries) or 0 - cart_total = product_total + calendar_amount - - if cart_total <= 0: - return redirect(url_for("cart.view_cart")) - - # Resolve per-page credentials - try: - page_config = await resolve_page_config(g.s, cart, calendar_entries) - except ValueError as e: - html = await render_template( - "_types/cart/checkout_error.html", - order=None, - error=str(e), - ) - return await make_response(html, 400) - - # Create order from cart - ident = current_cart_identity() - order = await create_order_from_cart( - g.s, - cart, - calendar_entries, - ident.get("user_id"), - ident.get("session_id"), - product_total, - calendar_amount, - ) - - # Set page_config on order if resolved - if page_config: - order.page_config_id = page_config.id - - # Build SumUp checkout details - redirect_url = url_for("cart.checkout_return", order_id=order.id, _external=True) - order.sumup_reference = build_sumup_reference(order.id, page_config=page_config) - description = build_sumup_description(cart, order.id) - - webhook_base_url = url_for("cart.checkout_webhook", order_id=order.id, _external=True) - webhook_url = build_webhook_url(webhook_base_url) - - checkout_data = await sumup_create_checkout( - order, - redirect_url=redirect_url, - webhook_url=webhook_url, - description=description, - page_config=page_config, - ) - await clear_cart_for_order(g.s, order) - - order.sumup_checkout_id = checkout_data.get("id") - order.sumup_status = checkout_data.get("status") - order.description = checkout_data.get("description") - - hosted_cfg = checkout_data.get("hosted_checkout") or {} - hosted_url = hosted_cfg.get("hosted_checkout_url") or checkout_data.get("hosted_checkout_url") - order.sumup_hosted_url = hosted_url - - await g.s.flush() - - if not hosted_url: - html = await render_template( - "_types/cart/checkout_error.html", - order=order, - error="No hosted checkout URL returned from SumUp.", - ) - return await make_response(html, 500) - - return redirect(hosted_url) - - - @bp.post("/checkout/webhook//") - async def checkout_webhook(order_id: int): - """ - Webhook endpoint for SumUp CHECKOUT_STATUS_CHANGED events. - - Security: - - Optional shared secret in ?token=... (checked against config sumup.webhook_secret) - - We *always* verify the event by calling SumUp's API. - """ - # Optional shared secret check - if not validate_webhook_secret(request.args.get("token")): - return "", 204 - - try: - payload = await request.get_json() - except Exception: - payload = None - - if not isinstance(payload, dict): - return "", 204 - - if payload.get("event_type") != "CHECKOUT_STATUS_CHANGED": - return "", 204 - - checkout_id = payload.get("id") - if not checkout_id: - return "", 204 - - # Look up our order - result = await g.s.execute(select(Order).where(Order.id == order_id)) - order = result.scalar_one_or_none() - if not order: - return "", 204 - - # Make sure the checkout id matches the one we stored - if order.sumup_checkout_id and order.sumup_checkout_id != checkout_id: - return "", 204 - - # Verify with SumUp - try: - await check_sumup_status(g.s, order) - except Exception: - pass - - return "", 204 - - - - @bp.get("/checkout/return//") - async def checkout_return(order_id: int): - """Handle the browser returning from SumUp after payment.""" - order = await get_order_with_details(g.s, order_id) - - if not order: - html = await render_template( - "_types/cart/checkout_return.html", - order=None, - status="missing", - calendar_entries=[], - ) - return await make_response(html) - - status = (order.status or "pending").lower() - - # Optionally refresh status from SumUp - if order.sumup_checkout_id: - try: - await check_sumup_status(g.s, order) - except Exception: - status = status or "pending" - - calendar_entries = order.calendar_entries or [] - await g.s.flush() - - html = await render_template( - "_types/cart/checkout_return.html", - order=order, - status=status, - calendar_entries=calendar_entries, - ) - return await make_response(html) - - return bp