""" Orders service s-expression page components. Checkout error/return pages are still rendered from Python because they use ``full_page_sx()`` with custom layouts. All other order rendering is now handled by .sx defcomps. """ from __future__ import annotations import os from typing import Any from shared.sx.jinja_bridge import load_service_components from shared.sx.helpers import ( call_url, render_to_sx, root_header_sx, full_page_sx, header_child_sx, ) from shared.infrastructure.urls import market_product_url, cart_url # Load orders-specific .sx components + handlers at import time load_service_components(os.path.dirname(os.path.dirname(__file__)), service_name="orders") # --------------------------------------------------------------------------- # Public API: Checkout error # --------------------------------------------------------------------------- async def render_checkout_error_page(ctx: dict, error: str | None = None, order: Any | None = None) -> str: """Full page: checkout error (sx wire format).""" account_url = call_url(ctx, "account_url", "") auth_hdr = await render_to_sx("auth-header-row", account_url=account_url) hdr = await root_header_sx(ctx) hdr = "(<> " + hdr + " " + await header_child_sx(auth_hdr) + ")" filt = await render_to_sx("checkout-error-header") err_msg = error or "Unexpected error while creating the hosted checkout session." order_sx = "" if order: order_sx = await render_to_sx("checkout-error-order-id", oid=f"#{order.id}") from shared.sx.parser import SxExpr content = await render_to_sx( "checkout-error-content", msg=err_msg, order=SxExpr(order_sx) if order_sx else None, back_url=cart_url("/"), ) return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content) # --------------------------------------------------------------------------- # Public API: Checkout return # --------------------------------------------------------------------------- async def render_checkout_return_page(ctx: dict, order: Any | None, status: str, calendar_entries: list | None = None, order_tickets: list | None = None) -> str: """Full page: checkout return after SumUp payment (sx wire format).""" from shared.sx.parser import SxExpr filt = await render_to_sx("checkout-return-header", status=status) if not order: content = await render_to_sx("checkout-return-missing") else: # Serialize order data for defcomp order_dict = { "id": order.id, "status": order.status or "pending", "created_at_formatted": order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else None, "description": order.description, "currency": order.currency, "total_formatted": f"{order.total_amount:.2f}" if order.total_amount else None, "items": [ { "product_url": market_product_url(item.product_slug), "product_image": item.product_image, "product_title": item.product_title, "product_id": item.product_id, "quantity": item.quantity, "currency": item.currency, "unit_price_formatted": f"{item.unit_price or 0:.2f}", } for item in (order.items or []) ], } summary = await render_to_sx("order-summary-card", order_id=order.id, created_at=order_dict["created_at_formatted"], description=order.description, status=order.status, currency=order.currency, total_amount=order_dict["total_formatted"], ) # Items items = "" if order.items: item_parts = [] for item_d in order_dict["items"]: if item_d["product_image"]: img = await render_to_sx("order-item-image", src=item_d["product_image"], alt=item_d["product_title"] or "Product image") else: img = await render_to_sx("order-item-no-image") item_parts.append(await render_to_sx("order-item-row", href=item_d["product_url"], img=SxExpr(img), title=item_d["product_title"] or "Unknown product", pid=f"Product ID: {item_d['product_id']}", qty=f"Qty: {item_d['quantity']}", price=f"{item_d['currency'] or order.currency or 'GBP'} {item_d['unit_price_formatted']}", )) items = await render_to_sx("order-items-panel", items=SxExpr("(<> " + " ".join(item_parts) + ")")) # Calendar entries calendar = "" if calendar_entries: cal_parts = [] for e in calendar_entries: st = e.state or "" pill = ( "bg-emerald-100 text-emerald-800" if st == "confirmed" else "bg-amber-100 text-amber-800" if st == "provisional" else "bg-blue-100 text-blue-800" if st == "ordered" else "bg-stone-100 text-stone-700" ) ds = e.start_at.strftime("%-d %b %Y, %H:%M") if e.start_at else "" if e.end_at: ds += f" \u2013 {e.end_at.strftime('%-d %b %Y, %H:%M')}" cal_parts.append(await render_to_sx("order-calendar-entry", name=e.name, pill=f"inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium {pill}", status=st.capitalize(), date_str=ds, cost=f"\u00a3{e.cost or 0:.2f}", )) calendar = await render_to_sx("order-calendar-section", items=SxExpr("(<> " + " ".join(cal_parts) + ")")) # Tickets tickets = "" if order_tickets: tk_parts = [] for tk in order_tickets: st = tk.state or "" pill = ( "bg-emerald-100 text-emerald-800" if st == "confirmed" else "bg-amber-100 text-amber-800" if st == "reserved" else "bg-blue-100 text-blue-800" if st == "checked_in" else "bg-stone-100 text-stone-700" ) pill_cls = f"inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium {pill}" ds = tk.entry_start_at.strftime("%-d %b %Y, %H:%M") if tk.entry_start_at else "" if tk.entry_end_at: ds += f" \u2013 {tk.entry_end_at.strftime('%-d %b %Y, %H:%M')}" tk_parts.append(await render_to_sx("checkout-return-ticket", name=tk.entry_name, pill=pill_cls, state=st.replace("_", " ").capitalize(), type_name=tk.ticket_type_name or None, date_str=ds, code=tk.code, price=f"\u00a3{tk.price or 0:.2f}", )) tickets = await render_to_sx("checkout-return-tickets", items=SxExpr("(<> " + " ".join(tk_parts) + ")")) # Status message status_msg = "" if order.status == "failed": status_msg = await render_to_sx("checkout-return-failed", order_id=order.id) elif order.status == "paid": status_msg = await render_to_sx("checkout-return-paid") content = await render_to_sx("checkout-return-content", summary=SxExpr(summary), items=SxExpr(items) if items else None, calendar=SxExpr(calendar) if calendar else None, tickets=SxExpr(tickets) if tickets else None, status_message=SxExpr(status_msg) if status_msg else None, ) account_url = call_url(ctx, "account_url", "") auth_hdr = await render_to_sx("auth-header-row", account_url=account_url) hdr = await root_header_sx(ctx) hdr = "(<> " + hdr + " " + await header_child_sx(auth_hdr) + ")" return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)