diff --git a/src/app/api/checkout/route.ts b/src/app/api/checkout/route.ts index 70475be..46e7e4a 100644 --- a/src/app/api/checkout/route.ts +++ b/src/app/api/checkout/route.ts @@ -84,6 +84,24 @@ export async function POST(request: Request) { ); } + // Ein im Admin deaktiviertes Produkt (isActive = false) darf nicht mehr + // verkauft werden. getProductsByIds filtert bewusst nicht danach, weil es + // auch fuer bereits bestellte, inzwischen deaktivierte Produkte genutzt + // wird (siehe lib/data.ts) — die Pruefung gehoert deshalb hierher. + // + // Die Warenkorb-Oberflaeche zeigt ein solches Produkt ueber + // /api/shop/cart-stock zwar bereits mit Bestand 0 an, aber das ist nur der + // Client. Ein Warenkorb, der im Browser lag, bevor das Produkt deaktiviert + // wurde, kam bis hierher ungeprueft durch: die Bestellung wurde angelegt + // und tatsaechlich bezahlt, obwohl die Imkerei den Artikel gerade aus dem + // Verkauf genommen hatte. + if (products.some((product) => !product.isActive)) { + return NextResponse.json( + { error: "Ein Produkt im Warenkorb ist nicht mehr verfügbar." }, + { status: 400 } + ); + } + const orderItemsData = input.items.map((cartItem) => { const product = products.find((p) => p.id === cartItem.productId)!; return { diff --git a/src/app/honig/[slug]/page.tsx b/src/app/honig/[slug]/page.tsx index e57b9d3..9ba2a4e 100644 --- a/src/app/honig/[slug]/page.tsx +++ b/src/app/honig/[slug]/page.tsx @@ -41,6 +41,11 @@ export async function generateMetadata( title: product.name, description: `${product.name} — ${product.tasteTags.join(", ")}. ${product.weightGrams} g aus eigener Imkerei in Altenmünster.`, alternates: { canonical: `/honig/${slug}` }, + // Deaktivierte Produkte bleiben erreichbar (die Bestellhistorie und die + // Sendungsverfolgung verlinken hierher zum Bewerten), gehoeren aber nicht + // in den Suchindex: wer ueber Google kaeme, faende ein Produkt, das nicht + // mehr bestellbar ist. + ...(product.isActive ? {} : { robots: { index: false, follow: true } }), }; } @@ -66,8 +71,12 @@ export default async function ProductDetailPage( ? "was-bedeutet-sommertracht" : null; const stock = product.inventory?.quantity ?? 0; - const inStock = stock > 0; - const lowStock = inStock && stock <= 5; + // Ein im Admin deaktiviertes Produkt ist nicht bestellbar, auch wenn noch + // Bestand gebucht ist. Es verschwindet bereits aus Shop-Uebersicht und + // Sitemap, seine Detailseite blieb aber voll bestellbar — erreichbar ueber + // Direktlink, Lesezeichen, alte Newsletter und "Zuletzt angesehen". + const isPurchasable = product.isActive && stock > 0; + const lowStock = isPurchasable && stock <= 5; const customer = await getCurrentCustomer(); const [reviews, reviewSummary, relatedProducts, ownReview, canReview, isWishlisted, company, shippingTiers] = @@ -108,7 +117,7 @@ export default async function ProductDetailPage( "@type": "Offer", priceCurrency: product.currency, price: (product.priceCents / 100).toFixed(2), - availability: inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock", + availability: isPurchasable ? "https://schema.org/InStock" : "https://schema.org/OutOfStock", url: `${base}/honig/${product.slug}`, }, ...(reviewSummary.count > 0 @@ -144,7 +153,7 @@ export default async function ProductDetailPage( return (