Honor Products.noShippingCost across cart, checkout, and product pages

- api/checkout/route.ts: authoritative shipping charge is 0 whenever
  every cart line opts out via noShippingCost, regardless of the
  free-shipping threshold.
- Cart/checkout order summaries: the whole "Versand" line (cost, free-
  shipping note, delivery time) is hidden entirely rather than showing
  "Kostenlos" — that's a different state from hitting the threshold.
- ProductSpotlight/Pricing/TodoKartenHero: "zzgl. Versand" and delivery-
  time hints drop for an exempted product's own page.
- /versand + its shared modal: one clarifying sentence that digital
  products are exempt.
- Widerrufsformular link: opens inline in a new tab (no forced download),
  arrow icon changed from a download glyph to a plain right arrow to
  match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-29 22:48:33 +00:00
parent bffc7d39a2
commit a3f843ad15
13 changed files with 145 additions and 70 deletions
+9
View File
@@ -207,6 +207,13 @@ export type Product = {
// storefront; the actual rate used for order totals is resolved and
// snapshotted server-side at checkout (api/checkout/route.ts).
taxRatePercent: number | null;
// No shipping cost for this product at all (e.g. a digital download) —
// never shows "zzgl. Versand" on its own product page, and doesn't count
// toward "does this cart need a shipping line" (lib/cartTotals.ts's
// cartHasShippableItem()). A cart with even one item that does NOT have
// this set still gets charged/shown the normal shipping cost — this only
// exempts the individual product, not the whole cart.
noShippingCost: boolean;
variants: { name: string; priceOverride: number | null; outOfStock: boolean; lowStock: boolean; maxQty: number | null }[];
};
@@ -231,6 +238,7 @@ type PayloadProduct = {
allowBackorder: boolean;
lowStockThreshold: number | null;
taxRatePercent: number | null;
noShippingCost: boolean;
variants:
| {
name: string;
@@ -291,6 +299,7 @@ export function mapPayloadProduct(product: PayloadProduct): Product {
lowStock: isLowStock(product.trackInventory, product.stock, product.lowStockThreshold),
maxQty: maxPurchasableQty(product.trackInventory, product.stock, product.allowBackorder),
taxRatePercent: product.taxRatePercent ?? null,
noShippingCost: product.noShippingCost,
variants: (product.variants ?? []).map((v) => ({
name: v.name,
priceOverride: v.priceOverride,