Compare commits

...

3 commits

Author SHA1 Message Date
db4d5cb1b6 Legal: Widerrufs-Checkbox, AGB-Abschnitt, Rechnungsnotiz (SW by-v970) 2026-05-15 12:07:13 +02:00
9a7f100855 Legal: Widerrufs-Checkbox im Upgrade-Modal + AGB-Abschnitt in Datenschutz
- Upgrade-Modal: Checkbox §356 Abs.4 BGB muss aktiv bestätigt werden,
  "Anfrage senden" bleibt bis dahin deaktiviert
- Akzeptanz-Zeitstempel wird mit der upgradeRequest-Message mitgeschickt
- datenschutz.js: neuer Abschnitt "Abonnement & Kündigung" mit Laufzeit,
  Verlängerung, Zahlung, Kündigung, Erstattung und Widerrufsrecht
2026-05-15 12:06:14 +02:00
699926cd76 Fix: Rechnung-Hinweistext auf AGB-konforme Jahresbeitrags-Notiz umgestellt
Alle drei Rechnungs-Einstiegspunkte (Admin-Upgrade-Button, automatische
Verlängerung via Scheduler, manuelles Upgrade via admin.py) erhalten jetzt
den einheitlichen Hinweis zum Jahresbeitrag gem. AGB ohne Rückerstattung.
2026-05-15 12:06:05 +02:00
8 changed files with 57 additions and 6 deletions

View file

@ -408,7 +408,7 @@ async def serve_media(path: str, request: _Request):
raise _HE(404, "Nicht gefunden.")
return _media_response(filepath)
APP_VER = "969" # muss mit APP_VER in app.js übereinstimmen
APP_VER = "970" # muss mit APP_VER in app.js übereinstimmen
@app.get("/.well-known/assetlinks.json")
async def assetlinks():

View file

@ -1312,7 +1312,7 @@ async def _handle_upgrade_invoices(req: dict, new_tier_label: str):
""", (
inv_number, req["user_id"], req["name"], req["email"], billing_address,
description, period, price, price, price,
f"Automatisch bei Upgrade von {req.get('old_tier','Standard')} auf {new_tier_label}.",
f"Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen. (Upgrade von {req.get('old_tier','Standard')} auf {new_tier_label})",
))
invoice_id = conn.execute("SELECT last_insert_rowid()").fetchone()[0]
conn.execute(

View file

@ -258,7 +258,7 @@ async def _create_renewal_invoice_draft(user: dict, expires: date, tier_label: s
invoice_number, user["id"], user["name"], user["email"], billing_address,
description, period,
price, price, price,
f"Automatisch erstellt — Abo läuft am {expires.strftime('%d.%m.%Y')} ab.",
f"Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen. (Automatisch erstellt, Ablauf: {expires.strftime('%d.%m.%Y')})",
))
conn.execute(
"INSERT INTO invoice_items (invoice_id, description, quantity, unit_price, total) VALUES (?,?,1,?,?)",

View file

@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
const APP_VER = '969'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VER = '970'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.6.0'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';
// Cache-Bust-Parameter nach Update-Reload sofort entfernen

View file

@ -3645,6 +3645,7 @@ window.Page_admin = (() => {
recipient_address: address || '',
service_period: _period,
items: [{ description: tierItem.description, quantity: 1, unit_price: tierItem.unit_price }],
notes: 'Jahresbeitrag gem. AGB. Bei vorzeitiger Kündigung keine anteilige Rückerstattung; Zugang bleibt bis Laufzeitende bestehen.',
});
});
});

View file

@ -238,6 +238,36 @@ window.Page_datenschutz = (() => {
style="${S.a}">www.lda.bayern.de</a>
</p>`)}
${sec('Abonnement &amp; Kündigung', `
<p style="${S.p}">
Ban Yaro Pro und das Züchter-Paket sind Jahresabonnements mit einer Laufzeit von
12 Monaten ab Freischaltung.
</p>
<p style="${S.p};margin-top:var(--space-3)">
<strong>Laufzeit &amp; Verlängerung:</strong> Das Abonnement läuft 12 Monate ab dem
Tag der Freischaltung. Nach Ablauf verlängert es sich auf unbestimmte Zeit mit einer
Kündigungsfrist von einem Monat zum Monatsende, sofern nicht vorher gekündigt wird
(§&nbsp;309 Nr.&nbsp;9 BGB).
</p>
<p style="${S.p};margin-top:var(--space-3)">
<strong>Zahlung:</strong> Der Jahresbeitrag (29&nbsp;EUR für Pro, 49&nbsp;EUR für
Züchter) wird einmalig für die gesamte Laufzeit im Voraus fällig.
</p>
<p style="${S.p};margin-top:var(--space-3)">
<strong>Kündigung:</strong> Die Kündigung kann jederzeit in den Einstellungen der App
erfolgen. Der Zugang bleibt bis zum Ende der bezahlten Laufzeit erhalten.
</p>
<p style="${S.p};margin-top:var(--space-3)">
<strong>Erstattung:</strong> Bei vorzeitiger Kündigung durch den Nutzer erfolgt keine
anteilige Rückerstattung des Jahresbeitrags. Der Zugang bleibt bis zum Ende der
bezahlten Laufzeit vollständig bestehen.
</p>
<p style="${S.p};margin-top:var(--space-3)">
<strong>Widerrufsrecht:</strong> Da die Nutzung sofort nach Freischaltung beginnt und
der Nutzer dem ausdrücklich zustimmt, erlischt das 14-tägige Widerrufsrecht gemäß
§&nbsp;356 Abs.&nbsp;4 BGB mit Beginn der Nutzung.
</p>`)}
${sec('Speicherdauer', `
<p style="${S.p}">
Deine Daten werden vollständig gelöscht, sobald du deinen Account löschst

View file

@ -313,6 +313,18 @@ window.Page_settings = (() => {
font-size:var(--text-xs);color:#c05000;line-height:1.6;margin-top:var(--space-2)">
💡 Tipp: Trag deine <strong>Rechnungsadresse</strong> im Profil ein dann können wir die Rechnung vollständig ausstellen.
</div>` : ''}
<div style="margin-top:var(--space-3);padding:var(--space-3);border-radius:var(--radius-md);
background:var(--c-surface-raised,rgba(0,0,0,.04));">
<label style="display:flex;align-items:flex-start;gap:var(--space-2);cursor:pointer;
font-size:var(--text-xs);color:var(--c-text-secondary);line-height:1.5">
<input type="checkbox" id="widerruf-checkbox"
style="margin-top:2px;flex-shrink:0;accent-color:${color}">
<span>
Ich stimme zu, dass mein Zugang sofort nach Freischaltung beginnt, und bestätige,
dass ich damit mein 14-tägiges Widerrufsrecht verliere (§&nbsp;356 Abs.&nbsp;4 BGB).
</span>
</label>
</div>
${breederForm}
</div>`,
footer: `
@ -330,6 +342,13 @@ window.Page_settings = (() => {
</button>`
});
const widerrufBox = document.getElementById('widerruf-checkbox');
const sendBtn = document.getElementById('upgrade-request-send-btn');
if (sendBtn) sendBtn.disabled = true;
widerrufBox?.addEventListener('change', () => {
if (sendBtn) sendBtn.disabled = !widerrufBox.checked;
});
document.getElementById('upgrade-request-send-btn')?.addEventListener('click', async () => {
const btn = document.getElementById('upgrade-request-send-btn');
if (!btn) return;
@ -363,7 +382,8 @@ window.Page_settings = (() => {
}
try {
const res = await API.auth.upgradeRequest(tier);
const widerrufAt = new Date().toLocaleString('de-DE');
const res = await API.auth.upgradeRequest(tier, `[Widerrufsrecht akzeptiert am ${widerrufAt}]`);
UI.modal.close();
if (res.already) {
UI.toast.info('Deine Anfrage liegt bereits vor — wir melden uns bald.');

View file

@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
const CACHE_VERSION = 'by-v969';
const CACHE_VERSION = 'by-v970';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache