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:
@@ -21,6 +21,7 @@ from .services import (
|
|||||||
from .services.checkout import (
|
from .services.checkout import (
|
||||||
find_or_create_cart_item,
|
find_or_create_cart_item,
|
||||||
create_order_from_cart,
|
create_order_from_cart,
|
||||||
|
resolve_page_config,
|
||||||
build_sumup_description,
|
build_sumup_description,
|
||||||
build_sumup_reference,
|
build_sumup_reference,
|
||||||
build_webhook_url,
|
build_webhook_url,
|
||||||
@@ -102,6 +103,17 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
if cart_total <= 0:
|
if cart_total <= 0:
|
||||||
return redirect(url_for("cart.view_cart"))
|
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
|
# Create order from cart
|
||||||
ident = current_cart_identity()
|
ident = current_cart_identity()
|
||||||
order = await create_order_from_cart(
|
order = await create_order_from_cart(
|
||||||
@@ -114,9 +126,13 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
calendar_amount,
|
calendar_amount,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Set page_config on order if resolved
|
||||||
|
if page_config:
|
||||||
|
order.page_config_id = page_config.id
|
||||||
|
|
||||||
# Build SumUp checkout details
|
# Build SumUp checkout details
|
||||||
redirect_url = url_for("cart.checkout_return", order_id=order.id, _external=True)
|
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)
|
description = build_sumup_description(cart, order.id)
|
||||||
|
|
||||||
webhook_base_url = url_for("cart.checkout_webhook", order_id=order.id, _external=True)
|
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,
|
redirect_url=redirect_url,
|
||||||
webhook_url=webhook_url,
|
webhook_url=webhook_url,
|
||||||
description=description,
|
description=description,
|
||||||
|
page_config=page_config,
|
||||||
)
|
)
|
||||||
await clear_cart_for_order(g.s, order)
|
await clear_cart_for_order(g.s, order)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from suma_browser.app.payments.sumup import get_checkout as sumup_get_checkout
|
from suma_browser.app.payments.sumup import get_checkout as sumup_get_checkout
|
||||||
from sqlalchemy import update
|
from sqlalchemy import update
|
||||||
from models.calendars import CalendarEntry # NEW
|
from models.calendars import CalendarEntry
|
||||||
|
|
||||||
|
|
||||||
async def check_sumup_status(session, order):
|
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
|
order.sumup_status = checkout_data.get("status") or order.sumup_status
|
||||||
sumup_status = (order.sumup_status or "").upper()
|
sumup_status = (order.sumup_status or "").upper()
|
||||||
|
|
||||||
@@ -26,10 +28,9 @@ async def check_sumup_status(session, order):
|
|||||||
.where(*filters)
|
.where(*filters)
|
||||||
.values(state="provisional")
|
.values(state="provisional")
|
||||||
)
|
)
|
||||||
# also clear cart for this user/session if it wasn't already
|
|
||||||
elif sumup_status == "FAILED":
|
elif sumup_status == "FAILED":
|
||||||
order.status = "failed"
|
order.status = "failed"
|
||||||
else:
|
else:
|
||||||
order.status = sumup_status.lower() or order.status
|
order.status = sumup_status.lower() or order.status
|
||||||
|
|
||||||
await g.s.flush()
|
await session.flush()
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ from sqlalchemy.orm import selectinload
|
|||||||
|
|
||||||
from models.market import Product, CartItem
|
from models.market import Product, CartItem
|
||||||
from models.order import Order, OrderItem
|
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
|
from config import config
|
||||||
|
|
||||||
|
|
||||||
@@ -57,6 +59,44 @@ async def find_or_create_cart_item(
|
|||||||
return 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(
|
async def create_order_from_cart(
|
||||||
session: AsyncSession,
|
session: AsyncSession,
|
||||||
cart: list[CartItem],
|
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}"
|
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."""
|
"""Build a SumUp reference with configured prefix."""
|
||||||
sumup_cfg = config().get("sumup", {}) or {}
|
if page_config and page_config.sumup_checkout_prefix:
|
||||||
prefix = sumup_cfg.get("checkout_reference_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}"
|
return f"{prefix}{order_id}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user