From 21729e0f7ab7fc75e0af0e680857aba3dc07fc45 Mon Sep 17 00:00:00 2001 From: ClaydeCode Date: Mon, 8 Jun 2026 12:23:02 +0000 Subject: [PATCH 1/2] feat(pricing): drop flat fee, add 19% German VAT Pricing formula changes from (vm_base + disk_gb*0.04 + 3.00) * 1.5 to (vm_base + disk_gb*0.04) * 1.5 * 1.19 The flat 3.00 EUR was a revenue share, not a management cost, and is removed. The displayed price is now gross, including 19% German VAT. Only affects the Subscribe / Reactivate button label for not-yet- subscribed shards; active and grace subscribers still render the controller-supplied price_cents and stay grandfathered. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/pricing.js | 7 ++++--- tests/unit/pricing.spec.js | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/lib/pricing.js b/src/lib/pricing.js index e5afc83..9c067c9 100644 --- a/src/lib/pricing.js +++ b/src/lib/pricing.js @@ -12,8 +12,8 @@ export const VM_PRICING_EUR = { }; export const DISK_PRICE_PER_GB_EUR = 0.04; -export const MANAGEMENT_COST_EUR = 3.00; export const MARGIN_MULTIPLIER = 1.5; +export const VAT_MULTIPLIER = 1.19; export function computeMonthlyPrice(vmSize, volumeSizeGb) { if (!vmSize) return null; @@ -21,8 +21,9 @@ export function computeMonthlyPrice(vmSize, volumeSizeGb) { if (vmCost === undefined) return null; if (!Number.isFinite(volumeSizeGb)) return null; const total = - (vmCost + volumeSizeGb * DISK_PRICE_PER_GB_EUR + MANAGEMENT_COST_EUR) * - MARGIN_MULTIPLIER; + (vmCost + volumeSizeGb * DISK_PRICE_PER_GB_EUR) * + MARGIN_MULTIPLIER * + VAT_MULTIPLIER; return Math.round(total * 100) / 100; } diff --git a/tests/unit/pricing.spec.js b/tests/unit/pricing.spec.js index 9644ad3..95da0f1 100644 --- a/tests/unit/pricing.spec.js +++ b/tests/unit/pricing.spec.js @@ -5,24 +5,24 @@ import { } from '@/lib/pricing'; describe('computeMonthlyPrice', () => { - // (11.00 + 30*0.04 + 3.00) * 1.5 = 22.80 - test('s + 30GB => 22.80', () => { - expect(computeMonthlyPrice('s', 30)).toBe(22.80); + // (11.00 + 30*0.04) * 1.5 * 1.19 = 21.78 + test('s + 30GB => 21.78', () => { + expect(computeMonthlyPrice('s', 30)).toBe(21.78); }); - // (5.50 + 20*0.04 + 3.00) * 1.5 = 13.95 - test('xs + 20GB => 13.95', () => { - expect(computeMonthlyPrice('xs', 20)).toBe(13.95); + // (5.50 + 20*0.04) * 1.5 * 1.19 = 11.25 + test('xs + 20GB => 11.25', () => { + expect(computeMonthlyPrice('xs', 20)).toBe(11.25); }); - // (102.00 + 400*0.04 + 3.00) * 1.5 = 181.50 - test('xl + 400GB => 181.50', () => { - expect(computeMonthlyPrice('xl', 400)).toBe(181.50); + // (102.00 + 400*0.04) * 1.5 * 1.19 = 210.63 + test('xl + 400GB => 210.63', () => { + expect(computeMonthlyPrice('xl', 400)).toBe(210.63); }); - // (19.80 + 250*0.04 + 3.00) * 1.5 = 49.20 - test('m + 250GB => 49.20', () => { - expect(computeMonthlyPrice('m', 250)).toBe(49.20); + // (19.80 + 250*0.04) * 1.5 * 1.19 = 53.19 + test('m + 250GB => 53.19', () => { + expect(computeMonthlyPrice('m', 250)).toBe(53.19); }); test('missing vmSize => null', () => { From bfb0f8f8f3f95c228757f0dcfb04eaddf51abbf1 Mon Sep 17 00:00:00 2001 From: ClaydeCode Date: Mon, 8 Jun 2026 13:19:47 +0000 Subject: [PATCH 2/2] feat(pricing): show VAT-included note under the price MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a muted, smaller "incl. 19% VAT (€X.XX)" line under the active-plan price and the pending-upgrade price in the subscription card. Shows the VAT share of the gross price; the gross figure stays prominent. New vatAmountEur() helper in pricing.js (gross - round(gross/1.19)), covered by unit tests. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/lib/pricing.js | 7 +++++++ src/views/Settings.vue | 13 ++++++++++--- tests/unit/pricing.spec.js | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/lib/pricing.js b/src/lib/pricing.js index 9c067c9..3a5e21a 100644 --- a/src/lib/pricing.js +++ b/src/lib/pricing.js @@ -36,3 +36,10 @@ export function centsToEur(cents) { if (cents == null) return null; return Math.round(cents) / 100; } + +// VAT share of a gross price (gross already includes 19% VAT), in euros. +export function vatAmountEur(grossCents) { + if (grossCents == null) return null; + const net = Math.round(grossCents / VAT_MULTIPLIER); + return (grossCents - net) / 100; +} diff --git a/src/views/Settings.vue b/src/views/Settings.vue index 4080a33..94136f9 100644 --- a/src/views/Settings.vue +++ b/src/views/Settings.vue @@ -59,10 +59,13 @@