From 2e537bc78d98b35cf8eb5c0d208ff9941cf7819a Mon Sep 17 00:00:00 2001
From: Marco
Date: Thu, 30 Jul 2026 10:39:08 +0000
Subject: [PATCH] =?UTF-8?q?Restrict=20R=C3=BCcksendung=20anfragen=20to=20d?=
=?UTF-8?q?elivered=20orders,=20fix=20Impressum=20email=20link?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- customerOrderAction() only offers "Rücksendung anfragen" once an order
is delivered, not already at shipped — a return before the package
arrived doesn't make sense yet. UI-only change (a stricter subset of
what the backend's CUSTOMER_ALLOWED_TRANSITIONS already permits).
- AnbieterAngaben.tsx's seller email is now a real mailto: link — it was
plain text, the only non-clickable email on the site.
---
README.md | 25 +++++++++++++++++++
.../[orderNumber]/switch-to-stripe/route.ts | 11 +++++---
app/impressum/components/AnbieterAngaben.tsx | 4 ++-
app/konto/bestellungen/[orderNumber]/page.tsx | 2 +-
app/lib/customerAuth.ts | 9 ++++++-
5 files changed, 44 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index b826181..59ddb4e 100644
--- a/README.md
+++ b/README.md
@@ -2072,6 +2072,31 @@ had `isManualPayment: false` — the Vorkasse/Überweisung branch (and its
new switch-option mention) was never previewable in the admin at all
before this.
+**Follow-up same day: switch-to-stripe eligibility narrowed.**
+`canSwitchPayment` (page.tsx) and the `switch-to-stripe` route's own
+re-check both changed from `paymentStatus !== "paid"` to an explicit
+allowlist (`"pending"` or `"not_applicable"`) — see the Payload backend's
+own README for why the blocklist form was too permissive. No visible
+behavior change for the normal case, just closes an edge case
+(`"failed"`/`"refunded"`/`"partially_refunded"` on a manual order — not
+realistic today, but not meaningful states to switch *from* either).
+
+**Two more same-day fixes:**
+- `customerOrderAction()` now only offers "Rücksendung anfragen" once an
+ order is actually `delivered`, not already at `shipped` — a (partial)
+ return before the package has even arrived doesn't make sense yet. The
+ backend's own `CUSTOMER_ALLOWED_TRANSITIONS` still technically permits
+ `shipped` → `return_requested` too (an extensive existing test suite is
+ built around that as its base fixture, see the Payload backend's
+ README) — this only narrows what the UI itself offers, a stricter
+ subset of what the backend already allows, not a security boundary
+ being loosened.
+- `/impressum`'s "Angaben zum Anbieter" email (`AnbieterAngaben.tsx`) was
+ plain text, not a `mailto:` link — the only email address on the whole
+ site that wasn't clickable, since this one section is rendered
+ straight from `company-settings` fields rather than through the CMS
+ richText renderer (which already links emails/URLs correctly).
+
## Deployment
- **Dockerfile**: 3-stage build (`deps` → `builder` → `runner`) with
diff --git a/app/api/account/orders/[orderNumber]/switch-to-stripe/route.ts b/app/api/account/orders/[orderNumber]/switch-to-stripe/route.ts
index 65af0ec..cafc006 100644
--- a/app/api/account/orders/[orderNumber]/switch-to-stripe/route.ts
+++ b/app/api/account/orders/[orderNumber]/switch-to-stripe/route.ts
@@ -21,10 +21,13 @@ export async function POST(request: Request, { params }: { params: Promise<{ ord
if (!order) return NextResponse.json({ ok: false, reason: "Bestellung nicht gefunden." }, { status: 404 });
// Same eligibility the backend endpoint re-checks authoritatively —
- // checked here too for a friendly error instead of a bare 409. An admin
- // can mark an Überweisung order paid by hand (Orders.ts's paymentStatus
- // field) — that order must never also be switched to Stripe.
- if (order.paymentProvider !== "manual" || order.status !== "received" || order.paymentStatus === "paid") {
+ // checked here too for a friendly error instead of a bare 409. An
+ // explicit allowlist ('pending'/'not_applicable'), not a
+ // paymentStatus !== "paid" blocklist — see switchPaymentToStripe.ts's
+ // own comment on why 'failed'/'refunded'/'partially_refunded' aren't
+ // meaningful states to switch from either.
+ const eligiblePaymentStatus = order.paymentStatus === "pending" || order.paymentStatus === "not_applicable";
+ if (order.paymentProvider !== "manual" || order.status !== "received" || !eligiblePaymentStatus) {
return NextResponse.json({ ok: false, reason: "Die Zahlungsart kann für diese Bestellung gerade nicht geändert werden." }, { status: 400 });
}
diff --git a/app/impressum/components/AnbieterAngaben.tsx b/app/impressum/components/AnbieterAngaben.tsx
index 8910f08..246eeae 100644
--- a/app/impressum/components/AnbieterAngaben.tsx
+++ b/app/impressum/components/AnbieterAngaben.tsx
@@ -76,7 +76,9 @@ export function AnbieterAngaben({ seller }: { seller: CompanySettings }) {
{seller.sellerZip} {seller.sellerCity}
{seller.sellerCountry}
- E-Mail: {seller.sellerEmail}
+
+ E-Mail: {seller.sellerEmail}
+
{seller.vatId && (
diff --git a/app/konto/bestellungen/[orderNumber]/page.tsx b/app/konto/bestellungen/[orderNumber]/page.tsx
index ca1eadf..961a915 100644
--- a/app/konto/bestellungen/[orderNumber]/page.tsx
+++ b/app/konto/bestellungen/[orderNumber]/page.tsx
@@ -55,7 +55,7 @@ export default async function KontoBestellungDetailPage({ params }: { params: Pr
const canSwitchPayment =
order.paymentProvider === "manual" &&
order.status === "received" &&
- order.paymentStatus !== "paid" &&
+ (order.paymentStatus === "pending" || order.paymentStatus === "not_applicable") &&
groupPaymentMethodsForCheckout(await getPaymentMethods()).some((m) => m.provider === "stripe");
return (
diff --git a/app/lib/customerAuth.ts b/app/lib/customerAuth.ts
index 846ec6e..fa8295d 100644
--- a/app/lib/customerAuth.ts
+++ b/app/lib/customerAuth.ts
@@ -455,7 +455,14 @@ export const ORDER_STATUS_LABEL: Record = {
// decide which button, if any, to show).
export function customerOrderAction(status: string): "cancel" | "request-return" | null {
if (status === "received") return "cancel";
- if (status === "shipped" || status === "delivered") return "request-return";
+ // Only once actually delivered — a (partial) return before the package
+ // even arrived doesn't make sense yet. The backend's own
+ // CUSTOMER_ALLOWED_TRANSITIONS still technically permits 'shipped' →
+ // 'return_requested' too (an extensive existing test suite is built
+ // around that as its base fixture) — this only narrows what the UI
+ // itself offers, a stricter subset of what the backend already allows,
+ // not a security boundary being loosened.
+ if (status === "delivered") return "request-return";
return null;
}