Feature: Abo-Kündigung + Ablaufdatum + Dog-Auswahl nach Downgrade (SW by-v945)

This commit is contained in:
rene 2026-05-14 13:56:55 +02:00
parent 44b3fba191
commit 3b666c545f
10 changed files with 341 additions and 11 deletions

View file

@ -240,7 +240,8 @@ async def me(user=Depends(get_current_user)):
profil_sichtbarkeit, avatar_url, created_at,
is_founder, is_partner, founder_number, is_founder_pending,
notes_ki_enabled, gassi_stunde_push,
preferred_theme, subscription_tier
preferred_theme, subscription_tier,
subscription_expires_at, subscription_cancelled_at, needs_dog_selection
FROM users WHERE id=?""",
(user["id"],)
).fetchone()
@ -394,3 +395,70 @@ async def reset_password(data: ResetPasswordRequest, request: Request):
(hash_password(data.password), user["id"])
)
return {"ok": True}
@router.post("/subscription/cancel")
async def cancel_subscription(user=Depends(get_current_user)):
with db() as conn:
row = conn.execute(
"SELECT subscription_tier, subscription_expires_at, subscription_cancelled_at FROM users WHERE id=?",
(user["id"],)
).fetchone()
if not row or row["subscription_tier"] in ("standard", "standard_test"):
raise HTTPException(400, "Kein aktives Abo vorhanden.")
if row["subscription_cancelled_at"]:
raise HTTPException(400, "Abo ist bereits gekündigt.")
conn.execute(
"UPDATE users SET subscription_cancelled_at=strftime('%Y-%m-%dT%H:%M:%SZ','now') WHERE id=?",
(user["id"],)
)
expires = row["subscription_expires_at"]
# Bestätigungsmail
try:
from mailer import send_email, email_html
import html as _html
tier_label = {"pro": "Ban Yaro Pro", "breeder": "Züchter"}.get(row["subscription_tier"], row["subscription_tier"])
expires_fmt = expires[:10] if expires else ""
body_html = f"""
<p>Hallo {_html.escape(user['name'])},</p>
<p>deine Kündigung für <strong>{tier_label}</strong> wurde bestätigt.</p>
<p>Dein Abo ist weiterhin aktiv bis zum <strong>{expires_fmt}</strong>.
Ab diesem Datum wirst du automatisch auf den kostenlosen Tarif gesetzt.</p>
<p>Deine Daten (Tagebuch, Gesundheit, Notizen) bleiben vollständig erhalten.
Wenn du mehrere Hunde hast, kannst du vor dem Ablauf einen als Haupthund festlegen.</p>
<p>Wir hoffen, dich bald wieder begrüßen zu dürfen!</p>
<p>Viele Grüße<br>René &amp; das Ban Yaro Team</p>"""
html = email_html(body_html, cta_url="https://banyaro.app", cta_label="Ban Yaro öffnen")
plain = (f"Hallo {user['name']},\n\nKündigung bestätigt für {tier_label}.\n"
f"Aktiv bis: {expires_fmt}\n\nAlle Daten bleiben erhalten.\n\nViele Grüße\nRené")
await send_email(user["email"], f"Kündigung bestätigt — {tier_label}", html, plain)
except Exception:
pass
return {"ok": True, "expires_at": expires}
@router.post("/subscription/select-dog")
async def select_primary_dog(body: dict, user=Depends(get_current_user)):
"""Nach Downgrade: Haupthund auswählen, Rest bleibt erhalten aber inaktiv."""
dog_id = body.get("dog_id")
if not dog_id:
raise HTTPException(400, "dog_id fehlt.")
with db() as conn:
dog = conn.execute(
"SELECT id FROM dogs WHERE id=? AND user_id=?", (dog_id, user["id"])
).fetchone()
if not dog:
raise HTTPException(404, "Hund nicht gefunden.")
# Alle anderen Hunde deaktivieren
conn.execute(
"UPDATE dogs SET is_active=0 WHERE user_id=? AND id!=?", (user["id"], dog_id)
)
conn.execute(
"UPDATE dogs SET is_active=1 WHERE id=?", (dog_id,)
)
conn.execute(
"UPDATE users SET needs_dog_selection=0 WHERE id=?", (user["id"],)
)
return {"ok": True}