diff --git a/README.md b/README.md
index 74a502e..267879f 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,8 @@ One canonical implementation, consumed by both repos, makes this class of drift
**2026-07-24, Kleinunternehmerregelung (§19 UStG).** `InvoiceOrder`/`CorrectionInvoiceOrder` gained optional `kleinunternehmer`, a third order-level VAT treatment alongside `vatExempt` — see "Kleinunternehmerregelung" below. Takes precedence over `vatExempt` wherever both would otherwise apply (a Kleinunternehmer never charges VAT to begin with, so there's nothing left to "exempt" via the separate intra-community rule).
+**2026-07-25, Vorkasse fixes (v0.2.3).** `isPaidImmediately()` matched `paymentMethodTitle` with an exact `!==` check against the literal string `"Überweisung"` — the consuming shop renamed its Vorkasse row to `"Überweisung (Vorkasse)"` the same day (to make room for a possible future *automated* bank-transfer method, e.g. "Sofortüberweisung", routed through a real payment gateway), which would have silently made every unpaid Vorkasse invoice show "✓ Bereits beglichen". Changed to `startsWith("Überweisung")` — same "only Überweisung is the named exception" design, tolerant of a suffix qualifier on that one title. Also added an explicit instruction in the unpaid case (previously the *absence* of the paid confirmation was the only signal, no actual text): "Bitte überweisen Sie den Rechnungsbetrag unter Angabe der Bestellnummer … auf die unten stehende Bankverbindung. Die Bestellung wird nach Zahlungseingang bearbeitet (in der Regel innerhalb von 1–2 Werktagen)." — same plain-text treatment as the paid confirmation (`unpaidNoticeText`, muted rather than green — an unpaid Vorkasse invoice isn't a problem, just an expected pending state).
+
## How this is consumed
Not published to npm — installed as a git dependency:
diff --git a/package-lock.json b/package-lock.json
index 2c858a2..efc0e94 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@einfach-produktiv/invoicing",
- "version": "0.1.0",
+ "version": "0.2.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@einfach-produktiv/invoicing",
- "version": "0.1.0",
+ "version": "0.2.2",
"dependencies": {
"@e-invoice-eu/core": "^3.1.1"
},
diff --git a/package.json b/package.json
index 5730421..d3b84eb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@einfach-produktiv/invoicing",
- "version": "0.2.2",
+ "version": "0.2.3",
"private": true,
"description": "Shared invoice / correction-invoice (Stornorechnung, Gutschrift) PDF generation and VAT-breakdown math, consumed as a git dependency by both the einfach-produktiv frontend and the payload backend — not published to npm.",
"type": "module",
diff --git a/src/__tests__/invoicePdf.test.ts b/src/__tests__/invoicePdf.test.ts
index 5c58a66..2f0a299 100644
--- a/src/__tests__/invoicePdf.test.ts
+++ b/src/__tests__/invoicePdf.test.ts
@@ -42,10 +42,17 @@ describe("isPaidImmediately", () => {
expect(isPaidImmediately("PayPal")).toBe(true);
});
- it("is false only for Überweisung", () => {
+ it("is false for Überweisung", () => {
expect(isPaidImmediately("Überweisung")).toBe(false);
});
+ it("is false for a suffixed Überweisung title too (startsWith, not exact match)", () => {
+ // Regression: the shop renamed its Vorkasse row to this exact title
+ // (2026-07-25) — an exact `!==` match would have silently started
+ // showing "✓ Bereits beglichen" on unpaid invoices.
+ expect(isPaidImmediately("Überweisung (Vorkasse)")).toBe(false);
+ });
+
it("defaults to true for any future/unknown payment method (only Überweisung is the named exception)", () => {
expect(isPaidImmediately("Sofortüberweisung")).toBe(true);
expect(isPaidImmediately("Klarna")).toBe(true);
diff --git a/src/invoicePdf.tsx b/src/invoicePdf.tsx
index 11a34fd..69893c6 100644
--- a/src/invoicePdf.tsx
+++ b/src/invoicePdf.tsx
@@ -50,6 +50,12 @@ const styles = StyleSheet.create({
metaLabel: { fontSize: 7, color: TEXT_MUTED, textTransform: "uppercase", marginBottom: 2 },
metaValue: { fontSize: 10, fontFamily: "Helvetica-Bold" },
paidBadgeText: { fontSize: 10, fontFamily: "Helvetica-Bold", color: SUCCESS },
+ // Same plain-text treatment as paidBadgeText (no pill/box, see that
+ // style's own comment) — muted, not an error/warning color, since an
+ // unpaid Vorkasse invoice isn't a problem, just a pending, expected
+ // state. Right-aligned + a max width so the two-sentence instruction
+ // wraps under the summary card instead of running the full page width.
+ unpaidNoticeText: { fontSize: 9, color: TEXT_MUTED, textAlign: "right", maxWidth: 220 },
table: { borderRadius: 6, overflow: "hidden", borderWidth: 1, borderColor: BORDER, marginTop: 8, marginBottom: 16 },
tableHeader: { flexDirection: "row", backgroundColor: BG_MUTED, paddingVertical: 8, paddingHorizontal: 10 },
// Every item row shares this same tinted background now — no more
@@ -222,14 +228,23 @@ export const SAMPLE_INVOICE_ORDER: InvoiceOrder = {
total: 37.7,
};
-// "Überweisung" (bank transfer) is the only payment method on this shop
-// that ISN'T settled immediately — Kreditkarte/PayPal both capture at
-// checkout. Rather than hardcode a list of "immediate" method titles
-// (fragile the moment a new one is added in Payload's payment-methods
-// collection), the only method that's ever NOT immediate is named
-// explicitly — everything else defaults to "paid already".
+// "Überweisung" (bank transfer, Vorkasse) is the only payment method on
+// this shop that ISN'T settled immediately — Kreditkarte/PayPal both
+// capture at checkout. Rather than hardcode a list of "immediate" method
+// titles (fragile the moment a new one is added in Payload's
+// payment-methods collection), the only method that's ever NOT immediate
+// is named explicitly — everything else defaults to "paid already".
+//
+// `startsWith`, not an exact `!==` match — the consuming shop renamed its
+// Vorkasse row to "Überweisung (Vorkasse)" (2026-07-25, to distinguish it
+// from a possible future *automated* bank-transfer method routed through
+// Stripe, e.g. "Sofortüberweisung" — see the test below asserting that one
+// stays `true`). An exact-match check would have silently started showing
+// "✓ Bereits beglichen" on unpaid Vorkasse invoices the moment that title
+// changed. `startsWith` keeps the same "only Überweisung is the named
+// exception" design while tolerating a suffix qualifier on that one title.
function isPaidImmediately(paymentMethodTitle: string): boolean {
- return paymentMethodTitle !== "Überweisung";
+ return !paymentMethodTitle.startsWith("Überweisung");
}
// Distributes the order-level discount/shipping proportionally across each
@@ -448,10 +463,16 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
)}
- {paid && (
+ {paid ? (
✓ Bereits beglichen ({order.paymentMethodTitle})
+ ) : (
+
+
+ Bitte überweisen Sie den Rechnungsbetrag unter Angabe der Bestellnummer {order.orderNumber} auf die unten stehende Bankverbindung. Die Bestellung wird nach Zahlungseingang bearbeitet (in der Regel innerhalb von 1–2 Werktagen).
+
+
)}