diff --git a/src/correctionInvoicePdf.tsx b/src/correctionInvoicePdf.tsx
index 1f389d5..9599b51 100644
--- a/src/correctionInvoicePdf.tsx
+++ b/src/correctionInvoicePdf.tsx
@@ -78,15 +78,22 @@ const styles = StyleSheet.create({
bundleLine: { fontSize: 8, color: TEXT_MUTED, marginTop: 2 },
summary: { alignItems: "flex-end", marginBottom: 24 },
summaryBox: { width: 240, backgroundColor: BG_MUTED, borderRadius: 6, padding: 14 },
- // See invoicePdf.tsx's own comment on this same style — groups rows that
- // belong together with a bit of breathing room between groups.
- summaryCluster: { marginBottom: 6 },
summaryRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 2 },
summaryLabel: { fontSize: 10, color: TEXT_MUTED },
summaryValue: { fontSize: 10 },
grandTotalRow: { flexDirection: "row", justifyContent: "space-between", paddingTop: 8, marginTop: 6, borderTopWidth: 1, borderTopColor: BORDER },
grandTotalLabel: { fontSize: 12, fontFamily: "Helvetica-Bold" },
grandTotalValue: { fontSize: 12, fontFamily: "Helvetica-Bold" },
+ // Same "enthält X% MwSt." annotation-under-Gesamt framing as
+ // invoicePdf.tsx now uses, fixing the identical double-counting bug this
+ // file had: the old layout showed Versand as its own row AND ALSO
+ // folded into the per-rate Netto/MwSt rows below it (via
+ // computeTaxBreakdown's scale factor) — the visible rows never summed
+ // to the printed Gesamt. Fixed 2026-07-23.
+ vatNotes: { marginTop: 6 },
+ vatNoteRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 1 },
+ vatNoteLabel: { fontSize: 8, color: TEXT_MUTED },
+ vatNoteValue: { fontSize: 8, color: TEXT_MUTED },
// `fixed` (on the element, see CorrectionInvoiceDocument below) — always
// pinned to the bottom of the page regardless of content height above,
// same as invoicePdf.tsx's own footer. (One of the two pre-package
@@ -288,34 +295,49 @@ function CorrectionInvoiceDocument({ kind, order, seller }: { kind: CorrectionIn
- {/* Storno reverses the full original invoice, shipping
- included (see this file's top-of-file comment on why the
- two kinds differ) — shown as its own line instead of
- silently folded into the tax-rate groups below, same as
- the original invoice's own Versand row. Gutschrift never
- reverses shipping, so this never renders for it. */}
- {kind === "storno" && order.shippingCost > 0 && (
-
- Versand
- -{formatPrice(order.shippingCost)}
-
+ {/* Genuinely additive chain for Storno — Zwischensumme, the
+ discount reversal ("Rabatt (entfällt)", a positive
+ add-back since the original discount no longer applies
+ once everything is undone), and Versand always sum
+ exactly to Gesamt below. Gutschrift never reverses
+ shipping/discount (see this file's top comment), so its
+ Gesamt is just the returned lines' own total — nothing to
+ itemize above it. Previously showed Versand as its own
+ row AND folded into the per-rate Netto/MwSt rows below
+ (double-counted, visible rows never summed to Gesamt) —
+ fixed 2026-07-23, same as invoicePdf.tsx. */}
+ {kind === "storno" && (
+ <>
+
+ Zwischensumme
+ -{formatPrice(order.subtotal)}
+
+ {order.discountAmount > 0 && (
+
+ Rabatt (entfällt)
+ +{formatPrice(order.discountAmount)}
+
+ )}
+ {order.shippingCost > 0 && (
+
+ Versand
+ -{formatPrice(order.shippingCost)}
+
+ )}
+ >
)}
- {rateGroups.map((g) => (
-
-
- Netto
- -{formatPrice(g.net)}
-
-
- zzgl. {g.rate}% MwSt.
- -{formatPrice(g.tax)}
-
-
- ))}
Gesamt
-{formatPrice(grandTotal)}
+
+ {rateGroups.map((g) => (
+
+ enthält {g.rate}% MwSt.
+ -{formatPrice(g.tax)}
+
+ ))}
+
diff --git a/src/invoicePdf.tsx b/src/invoicePdf.tsx
index 092e99e..a8b09f7 100644
--- a/src/invoicePdf.tsx
+++ b/src/invoicePdf.tsx
@@ -70,18 +70,28 @@ const styles = StyleSheet.create({
bundleLine: { fontSize: 8, color: TEXT_MUTED, marginTop: 2 },
summary: { alignItems: "flex-end", marginBottom: 24 },
summaryBox: { width: 240, backgroundColor: BG_MUTED, borderRadius: 6, padding: 14 },
- // Groups rows that belong together (Rabatt+Versand; each rate's own
- // Netto/MwSt pair) with a bit of breathing room between groups, while
- // rows *within* a group stay at the original tight spacing — a visual
- // cue that e.g. "Netto"/"zzgl. 19% MwSt." are one line item's two halves,
- // not just an undifferentiated stack of numbers.
- summaryCluster: { marginBottom: 6 },
summaryRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 2 },
summaryLabel: { fontSize: 10, color: TEXT_MUTED },
summaryValue: { fontSize: 10 },
grandTotalRow: { flexDirection: "row", justifyContent: "space-between", paddingTop: 8, marginTop: 6, borderTopWidth: 1, borderTopColor: BORDER },
grandTotalLabel: { fontSize: 12, fontFamily: "Helvetica-Bold" },
grandTotalValue: { fontSize: 12, fontFamily: "Helvetica-Bold" },
+ // "Enthält X% MwSt.: Y €" annotations under Gesamt — same "contained
+ // within the total, not an additional deduction" framing the website's
+ // own VatBreakdown component already uses on /cart and /checkout.
+ // Deliberately NOT part of the summaryRow additive chain above (Zwischen-
+ // summe → Rabatt → Versand → Gesamt): every row in that chain sums
+ // exactly to Gesamt on its own; the tax-rate breakdown is informational
+ // context about what portion of Gesamt is tax, not a further adjustment.
+ // A previous version showed the same per-rate net/tax figures as their
+ // own additive-looking rows alongside Rabatt/Versand, which double-
+ // counted shipping/discount (already proportionally folded into each
+ // rate's own net/tax by computeTaxBreakdown) — the visible rows never
+ // actually summed to the printed Gesamt. Fixed 2026-07-23.
+ vatNotes: { marginTop: 6 },
+ vatNoteRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 1 },
+ vatNoteLabel: { fontSize: 8, color: TEXT_MUTED },
+ vatNoteValue: { fontSize: 8, color: TEXT_MUTED },
// Moved here (below the summary card) from the top meta row — reads more
// naturally right next to the amount it's confirming was paid, and keeps
// the meta row itself to just the three reference numbers. Deliberately
@@ -340,34 +350,36 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
-
- {order.discountAmount > 0 && (
-
- Rabatt{order.discountCode ? ` (${order.discountCode})` : ""}
- -{formatPrice(order.discountAmount)}
-
- )}
-
- Versand
- {order.shippingCost === 0 ? "Kostenlos" : formatPrice(order.shippingCost)}
-
+ {/* Genuinely additive chain — Zwischensumme (raw item gross,
+ unscaled) − Rabatt + Versand always equals Gesamt exactly,
+ unlike the old layout this replaced (see vatNotes' own
+ comment above). */}
+
+ Zwischensumme
+ {formatPrice(order.subtotal)}
- {rateGroups.map((g) => (
-
-
- Netto
- {formatPrice(g.net)}
-
-
- zzgl. {g.rate}% MwSt.
- {formatPrice(g.tax)}
-
+ {order.discountAmount > 0 && (
+
+ Rabatt{order.discountCode ? ` (${order.discountCode})` : ""}
+ -{formatPrice(order.discountAmount)}
- ))}
+ )}
+
+ Versand
+ {order.shippingCost === 0 ? "Kostenlos" : formatPrice(order.shippingCost)}
+
Gesamt
{formatPrice(order.total)}
+
+ {rateGroups.map((g) => (
+
+ enthält {g.rate}% MwSt.
+ {formatPrice(g.tax)}
+
+ ))}
+
{paid && (