Wartungs-Abo v2: Rechtsrunde umgesetzt

- Bestellseite: Pflichtangaben direkt ueber dem Button, Zahlungsarten,
  Wahl des Leistungsbeginns (sofort / nach Widerrufsfrist via 15 Tage
  Stripe-Probezeit), keine Vorauswahl
- Zustimmungsnachweis: Zeitpunkt, Version, Wahl, IP, Browser in Stripe-
  Metadaten und Appwrite (neue Attribute, Setup-Skript ergaenzt)
- Checkout: Karte + SEPA (Fallback nur Karte), korrigierter Hinweistext
- Vertragsbestaetigung mit PDF der Vertragsunterlagen statt HTML
- Kuendigen/Widerrufen: Formular direkt (keine Zwischenseite), Wunsch-
  termin, Druck/PDF der Bestaetigung, Vorbefuellung bei Login
- Widerrufsbutton im Abo waehrend der Frist, 'beginnt am'-Status
- Pilotbetrieb per ABO_PILOT_EMAILS
- Vertragsbedingungen v2, Datenschutzerklaerung, Kalkulator ohne MwSt
  und mit 100/80 EUR

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-09-25 14:10:21 +00:00
1 parent c0214725c0
commit 4ab154bfa2
18 files changed
+354 -163

No files matched your search

+8 -6
View File
@@ -8,7 +8,8 @@
localStorage des Browsers. */
/* --------------------------------------------------------------- Katalog --- */
const MWST = 19
// Kleinunternehmer (§ 19 UStG): alle Preise sind Endpreise, keine Umsatzsteuer
const USt_HINWEIS = 'Gemäß § 19 UStG wird keine Umsatzsteuer berechnet (Kleinunternehmerregelung).'
const CATS = [
{
@@ -118,7 +119,8 @@ const CATS = [
},
]
const STAFFEL = [{ max: 5, r: 150 }, { max: 15, r: 130 }, { max: 40, r: 115 }, { max: 1e9, r: 100 }]
// Stundensatz: 100 € für kleine Aufträge, ab 10 Stunden (größeres Projekt) 80 €
const STAFFEL = [{ max: 9.99, r: 100 }, { max: 1e9, r: 80 }]
const PEOPLE = {
justin: {
@@ -285,7 +287,7 @@ function totals() {
}
return {
sel, hrsNum, rateNum, netNum, grossNum: netNum * (1 + MWST / 100),
sel, hrsNum, rateNum, netNum,
mSum, askServer, monthlyItems, hasMonthly: monthlyItems.length > 0,
payTitle, payText, flagTitle, flagText, tone,
monthly: eur(mSum) + (askServer ? ' +' : ''),
@@ -389,7 +391,7 @@ function summaryText() {
lines.push('')
lines.push(`Summe: ${fmt(t.hrsNum)} Stunden`)
lines.push(`Stundensatz: ${eur(t.rateNum)}`)
lines.push(`Projektpreis: ${eur(t.netNum)} netto (${eur(t.grossNum)} brutto inkl. ${MWST} % MwSt.)`)
lines.push(`Projektpreis: ${eur(t.netNum)} (Endpreis, ohne Umsatzsteuer)`)
lines.push('', 'ZAHLUNG', `${t.payTitle} — ${t.payText}`)
if (t.hasMonthly) {
@@ -414,7 +416,7 @@ function summaryText() {
if (state.portalEmail) lines.push(`E-Mail: ${state.portalEmail}`)
if (state.portalPass) lines.push(`Passwort: ${state.portalPass}`)
}
lines.push('', 'Alle Preise netto zzgl. gesetzlicher Umsatzsteuer.')
lines.push('', `Alle Preise sind Endpreise. ${USt_HINWEIS}`)
return lines.join('\n')
}
@@ -425,7 +427,7 @@ function renderNumbers(t) {
el('hdr-monthly').textContent = t.hasMonthly ? t.monthly : '–'
el('hero-net').textContent = eur(t.netNum)
el('hero-sub').textContent = `netto · ${eur(t.grossNum)} brutto inkl. ${MWST} % MwSt.`
el('hero-sub').textContent = 'Endpreis · gemäß § 19 UStG ohne Umsatzsteuer'
el('hero-hrs').textContent = `${fmt(t.hrsNum)} Std`
el('hero-rate').textContent = `Stundensatz ${eur(t.rateNum)}/Std`
el('hero-monthly').textContent = t.hasMonthly ? t.monthly : '–'