feat: per-page SumUp credentials in checkout flow (Phase 3)

- Add resolve_page_config() to determine PageConfig from cart/calendar context
- Set page_config_id on Order during checkout
- Pass page_config to SumUp create_checkout and build_sumup_reference
- check_sumup_status uses order.page_config for per-page credential resolution
- Fix: use session.flush() instead of g.s.flush() in check_sumup_status

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-10 20:49:45 +00:00
parent 6729b0f158
commit c8d927bf72
3 changed files with 70 additions and 9 deletions

View File

@@ -21,6 +21,7 @@ from .services import (
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,
@@ -102,6 +103,17 @@ def register(url_prefix: str) -> Blueprint:
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(
@@ -114,9 +126,13 @@ def register(url_prefix: str) -> Blueprint:
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)
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)
@@ -127,6 +143,7 @@ def register(url_prefix: str) -> Blueprint:
redirect_url=redirect_url,
webhook_url=webhook_url,
description=description,
page_config=page_config,
)
await clear_cart_for_order(g.s, order)

View File

@@ -1,10 +1,12 @@
from suma_browser.app.payments.sumup import get_checkout as sumup_get_checkout
from sqlalchemy import update
from models.calendars import CalendarEntry # NEW
from models.calendars import CalendarEntry
async def check_sumup_status(session, order):
checkout_data = await sumup_get_checkout(order.sumup_checkout_id)
# Use order's page_config for per-page SumUp credentials
page_config = getattr(order, "page_config", None)
checkout_data = await sumup_get_checkout(order.sumup_checkout_id, page_config=page_config)
order.sumup_status = checkout_data.get("status") or order.sumup_status
sumup_status = (order.sumup_status or "").upper()
@@ -26,10 +28,9 @@ async def check_sumup_status(session, order):
.where(*filters)
.values(state="provisional")
)
# also clear cart for this user/session if it wasn't already
elif sumup_status == "FAILED":
order.status = "failed"
else:
order.status = sumup_status.lower() or order.status
await g.s.flush()
await session.flush()

View File

@@ -9,7 +9,9 @@ from sqlalchemy.orm import selectinload
from models.market import Product, CartItem
from models.order import Order, OrderItem
from models.calendars import CalendarEntry
from models.calendars import CalendarEntry, Calendar
from models.page_config import PageConfig
from models.market_place import MarketPlace
from config import config
@@ -57,6 +59,44 @@ async def find_or_create_cart_item(
return cart_item
async def resolve_page_config(
session: AsyncSession,
cart: list[CartItem],
calendar_entries: list[CalendarEntry],
) -> Optional["PageConfig"]:
"""Determine the PageConfig for this order.
Returns PageConfig or None (use global credentials).
Raises ValueError if items span multiple pages.
"""
post_ids: set[int] = set()
# From cart items via market_place
for ci in cart:
if ci.market_place_id:
mp = await session.get(MarketPlace, ci.market_place_id)
if mp:
post_ids.add(mp.post_id)
# From calendar entries via calendar
for entry in calendar_entries:
cal = await session.get(Calendar, entry.calendar_id)
if cal and cal.post_id:
post_ids.add(cal.post_id)
if len(post_ids) > 1:
raise ValueError("Cannot checkout items from multiple pages")
if not post_ids:
return None # global credentials
post_id = post_ids.pop()
pc = (await session.execute(
select(PageConfig).where(PageConfig.post_id == post_id)
)).scalar_one_or_none()
return pc
async def create_order_from_cart(
session: AsyncSession,
cart: list[CartItem],
@@ -139,10 +179,13 @@ def build_sumup_description(cart: list[CartItem], order_id: int) -> str:
return f"Order {order_id} ({item_count} item{'s' if item_count != 1 else ''}): {summary}"
def build_sumup_reference(order_id: int) -> str:
def build_sumup_reference(order_id: int, page_config=None) -> str:
"""Build a SumUp reference with configured prefix."""
sumup_cfg = config().get("sumup", {}) or {}
prefix = sumup_cfg.get("checkout_reference_prefix", "")
if page_config and page_config.sumup_checkout_prefix:
prefix = page_config.sumup_checkout_prefix
else:
sumup_cfg = config().get("sumup", {}) or {}
prefix = sumup_cfg.get("checkout_reference_prefix", "")
return f"{prefix}{order_id}"