248 lines
8.4 KiB
Markdown
248 lines
8.4 KiB
Markdown
# AZA Stripe Live-Setup — Vollständige Anleitung
|
||
|
||
Stand: 14. März 2026
|
||
|
||
---
|
||
|
||
## 1. Ist-Analyse: Was existiert im Code
|
||
|
||
### Zwei Stripe-Integrationen (parallel)
|
||
|
||
| Datei | Env-Prefix | Methode | Status |
|
||
|---|---|---|---|
|
||
| `stripe_routes.py` | `STRIPE_*` (kein Prefix) | **Lookup-Keys** (`aza_basic_monthly` etc.) | **Primär / Aktiv** |
|
||
| `license_server.py` | `AZA_STRIPE_*` | Direkte Price-IDs | Legacy / Parallel |
|
||
|
||
**Wichtig:** `stripe_routes.py` ist der aktive Pfad. Die Landing-Page (`web/index.html`)
|
||
ruft `/stripe/create_checkout_session` auf und übergibt einen `lookup_key`.
|
||
|
||
### Erwartete Lookup-Keys im Code
|
||
|
||
| Lookup-Key | Plan | Policy |
|
||
|---|---|---|
|
||
| `aza_basic_monthly` | Basic (1 User, 2 Geräte) | Checkout auf Landing-Page |
|
||
| `aza_basic_yearly` | Basic (1 User, 2 Geräte) | Noch nicht auf Landing-Page |
|
||
| `aza_team_monthly` | Team (3 User, 2 Geräte/User) | Checkout auf Landing-Page |
|
||
| `aza_team_yearly` | Team (3 User, 2 Geräte/User) | Noch nicht auf Landing-Page |
|
||
|
||
### Aktuelle Preise auf der Landing-Page (`web/index.html`)
|
||
|
||
| Plan | Preis | Lookup-Key |
|
||
|---|---|---|
|
||
| AZA Praxis (empfohlen) | CHF 89 / Monat | `aza_basic_monthly` |
|
||
| AZA Team | CHF 199 / Monat | `aza_team_monthly` |
|
||
|
||
### Fehlend auf der Landing-Page
|
||
- **Jahreslizenz** (17 % günstiger) ist NICHT dargestellt
|
||
- Nur monatliche Preise sind sichtbar
|
||
- Kein Umschalter Monatlich/Jährlich
|
||
|
||
---
|
||
|
||
## 2. Env-Variablen-Diskrepanz (KRITISCH)
|
||
|
||
### `stripe_routes.py` liest:
|
||
```
|
||
STRIPE_SECRET_KEY ← deploy/.env setzt diese ✓
|
||
STRIPE_WEBHOOK_SECRET ← deploy/.env setzt diese ✓
|
||
STRIPE_SUCCESS_URL ← deploy/.env setzt diese ✓
|
||
STRIPE_CANCEL_URL ← deploy/.env setzt diese ✓
|
||
STRIPE_PORTAL_RETURN_URL ← deploy/.env setzt diese ✓
|
||
```
|
||
|
||
### `license_server.py` liest:
|
||
```
|
||
AZA_STRIPE_SECRET_KEY ← deploy/.env setzt diese NICHT ✗
|
||
AZA_STRIPE_WEBHOOK_SECRET ← deploy/.env setzt diese NICHT ✗
|
||
AZA_STRIPE_PRICE_BASIC ← deploy/.env setzt diese NICHT ✗
|
||
AZA_STRIPE_PRICE_TEAM ← deploy/.env setzt diese NICHT ✗
|
||
AZA_STRIPE_PRICE_BASIC_YEARLY ← deploy/.env setzt diese NICHT ✗
|
||
AZA_STRIPE_PRICE_TEAM_YEARLY ← deploy/.env setzt diese NICHT ✗
|
||
AZA_STRIPE_SUCCESS_URL ← deploy/.env setzt diese NICHT ✗
|
||
AZA_STRIPE_CANCEL_URL ← deploy/.env setzt diese NICHT ✗
|
||
```
|
||
|
||
### Zusätzlich in `license_server.py` (Zeile 629, 867):
|
||
```python
|
||
webhook_secret = os.getenv("STRIPE_WEBHOOK_SECRET") # ohne AZA_
|
||
secret_key = os.getenv("STRIPE_SECRET_KEY") or os.getenv("STRIPE_API_KEY") # ohne AZA_
|
||
```
|
||
→ Inkonsistenz innerhalb derselben Datei.
|
||
|
||
### Konsequenz
|
||
- `stripe_routes.py` funktioniert mit `deploy/.env` → **OK**
|
||
- `license_server.py` Checkout-Endpoint bekommt leere Price-IDs → **Broken**
|
||
- Aber: Landing-Page nutzt `/stripe/create_checkout_session` (= `stripe_routes.py`) → **OK**
|
||
- Der `license_server.py` Checkout ist ein Parallel-Pfad, der aktuell nicht aufgerufen wird
|
||
|
||
### Empfehlung
|
||
Für Go-Live: `stripe_routes.py` ist der korrekte Pfad. Kein Umbau nötig.
|
||
Die `license_server.py` Env-Vars mit `AZA_*` Prefix können später bereinigt werden.
|
||
|
||
---
|
||
|
||
## 3. Was im Stripe-Dashboard angelegt werden muss
|
||
|
||
### Produkte und Preise
|
||
|
||
Gehe zu: **Stripe Dashboard → Produkte → + Produkt hinzufügen**
|
||
|
||
#### Produkt 1: AZA Desktop Praxis
|
||
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| Name | AZA Desktop – Praxis |
|
||
| Beschreibung | Medizinische KI-Software für Einzelpraxen. Alle 6 Module. |
|
||
|
||
Preise für dieses Produkt:
|
||
|
||
| Preis | Intervall | Lookup-Key (WICHTIG!) |
|
||
|---|---|---|
|
||
| CHF 89.00 | Monatlich | `aza_basic_monthly` |
|
||
| CHF 886.00 | Jährlich | `aza_basic_yearly` |
|
||
|
||
#### Produkt 2: AZA Desktop Team (optional, für später)
|
||
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| Name | AZA Desktop – Team |
|
||
| Beschreibung | Für Gemeinschaftspraxen. Bis zu 3 Benutzer. |
|
||
|
||
Preise für dieses Produkt:
|
||
|
||
| Preis | Intervall | Lookup-Key (WICHTIG!) |
|
||
|---|---|---|
|
||
| CHF 199.00 | Monatlich | `aza_team_monthly` |
|
||
| CHF 1'982.00 | Jährlich | `aza_team_yearly` |
|
||
|
||
**KRITISCH:** Beim Erstellen jedes Preises den **Lookup-Key** setzen!
|
||
In Stripe: Preis erstellen → Erweiterte Optionen → Lookup Key → exakt den Key eingeben.
|
||
|
||
---
|
||
|
||
## 4. Webhook-Setup
|
||
|
||
### Webhook-Endpunkt
|
||
|
||
| Feld | Wert |
|
||
|---|---|
|
||
| URL | `https://app.aza-medwork.ch/stripe/webhook` |
|
||
| Oder (falls auf Hostpoint) | `https://aza-medwork.ch/stripe/webhook` |
|
||
|
||
**Hinweis:** Der Webhook-Endpunkt muss auf dem Server laufen, auf dem `stripe_routes.py`
|
||
läuft. Aktuell ist das der **Hetzner-Server** (Docker/Caddy). Erst relevant, wenn
|
||
der Backend-Server auf Hetzner live ist.
|
||
|
||
Für den Start über WooCommerce/Hostpoint: WooCommerce Stripe Plugin hat
|
||
seinen eigenen Webhook — der wird automatisch konfiguriert.
|
||
|
||
### Benötigte Webhook-Events
|
||
|
||
| Event | Zweck |
|
||
|---|---|
|
||
| `checkout.session.completed` | Neue Subscription registrieren, Lizenz anlegen |
|
||
| `customer.subscription.updated` | Statusänderungen (Verlängerung, Pause, Planwechsel) |
|
||
| `customer.subscription.deleted` | Kündigung, Lizenz deaktivieren |
|
||
|
||
### Webhook-Secret
|
||
Nach dem Erstellen des Webhooks zeigt Stripe ein `whsec_...` Secret an.
|
||
Dieses muss in `deploy/.env` als `STRIPE_WEBHOOK_SECRET` eingetragen werden.
|
||
|
||
---
|
||
|
||
## 5. Success/Cancel-Flow — Prüfung
|
||
|
||
### Success-Flow ✓
|
||
- URL: `https://aza-medwork.ch/billing/success?session_id={CHECKOUT_SESSION_ID}`
|
||
- Endpunkt: `backend_main.py` → `@app.get("/billing/success")`
|
||
- Liest `session_id` aus Query-Parameter
|
||
- Ruft `stripe.checkout.Session.retrieve(session_id)` auf
|
||
- Zeigt Bestätigungsseite mit Download-Link und Anleitung
|
||
- **Funktioniert korrekt** (lokal verifiziert)
|
||
|
||
### Cancel-Flow ✓
|
||
- URL: `https://aza-medwork.ch/billing/cancel`
|
||
- Endpunkt: `backend_main.py` → `@app.get("/billing/cancel")`
|
||
- Zeigt Info-Seite "Checkout abgebrochen" mit Link zurück
|
||
- **Funktioniert korrekt** (lokal verifiziert)
|
||
|
||
### Portal-Return ✓
|
||
- URL: `https://aza-medwork.ch/`
|
||
- Für Stripe Billing Portal (Abo verwalten, kündigen)
|
||
|
||
---
|
||
|
||
## 6. Benötigte Live-Secrets für deploy/.env
|
||
|
||
```bash
|
||
# 1. Stripe Secret Key (Dashboard → Developers → API keys → Secret key)
|
||
STRIPE_SECRET_KEY=sk_live_XXXXXXXXXXXXXXXXXXXXXX
|
||
|
||
# 2. Stripe Webhook Secret (Dashboard → Developers → Webhooks → Signing secret)
|
||
STRIPE_WEBHOOK_SECRET=whsec_XXXXXXXXXXXXXXXXXXXXXX
|
||
|
||
# 3. API-Token für Backend-Zugriff (selbst generieren, z.B. openssl rand -hex 32)
|
||
MEDWORK_API_TOKENS=<TOKEN_1>,<TOKEN_2>
|
||
```
|
||
|
||
Die übrigen Werte in `deploy/.env` sind bereits korrekt gesetzt:
|
||
- `STRIPE_SUCCESS_URL` ✓
|
||
- `STRIPE_CANCEL_URL` ✓
|
||
- `STRIPE_PORTAL_RETURN_URL` ✓
|
||
- `AZA_DOMAIN` ✓
|
||
- `ACME_EMAIL` ✓
|
||
|
||
---
|
||
|
||
## 7. Offene Punkte / Landing-Page-Update nötig
|
||
|
||
### web/index.html muss aktualisiert werden:
|
||
|
||
1. **Jahreslizenz hinzufügen**: Monatlich/Jährlich-Umschalter mit 17 % Ersparnis
|
||
2. **Praxis-Monatsplan**: CHF 89 → `aza_basic_monthly` (bereits vorhanden ✓)
|
||
3. **Praxis-Jahresplan**: CHF 886 → `aza_basic_yearly` (fehlt ✗)
|
||
4. **Team-Plan**: Entscheiden, ob für v1.0 angeboten oder ausblenden
|
||
|
||
---
|
||
|
||
## 8. Zwei Wege zum Verkauf (Entscheidung nötig)
|
||
|
||
### Weg A: WooCommerce Subscriptions (Hostpoint)
|
||
- Alles auf der bestehenden WordPress-Seite
|
||
- WooCommerce Subscriptions Plugin nötig (kostenpflichtig, ca. $200/Jahr)
|
||
- WooCommerce Stripe Gateway macht Webhooks automatisch
|
||
- Kein eigener Backend-Server nötig für den Verkauf
|
||
- Download-Auslieferung über WooCommerce
|
||
- **Vorteil:** Kein Hetzner-Deploy nötig für den Start
|
||
|
||
### Weg B: Eigener Stripe-Flow (Hetzner)
|
||
- Landing-Page + `stripe_routes.py` + Backend auf Hetzner
|
||
- Voller Kontrolle über den Checkout-Flow
|
||
- Webhook an eigenen Server
|
||
- **Nachteil:** Hetzner muss erst deployed werden
|
||
|
||
### Empfehlung für sofortigen Start
|
||
**Weg A (WooCommerce)** für den Verkauf. Weg B später für Browser-AZA.
|
||
|
||
---
|
||
|
||
## Zusammenfassung
|
||
|
||
### Vorhanden ✓
|
||
- Stripe-Integration im Code komplett (`stripe_routes.py`)
|
||
- Checkout-Session-Erstellung mit Lookup-Keys
|
||
- Webhook-Handler für 3 Events (checkout.completed, subscription.updated/deleted)
|
||
- Idempotente Event-Verarbeitung (Deduplizierung)
|
||
- Lizenz-Datenbank (SQLite) mit Upsert-Logik
|
||
- Success/Cancel-Seiten implementiert
|
||
- Billing-Portal-Integration
|
||
- Env-Variablen-Template (`deploy/.env.example`)
|
||
|
||
### Fehlt ✗
|
||
- **Live Stripe-Produkte + Preise** im Stripe-Dashboard (mit korrekten Lookup-Keys)
|
||
- **Live Stripe Secret Key** in `deploy/.env`
|
||
- **Live Webhook Secret** in `deploy/.env`
|
||
- **Jahreslizenz auf Landing-Page** (nur monatlich dargestellt)
|
||
- **Entscheidung: WooCommerce vs. eigener Flow** für den sofortigen Start
|
||
- **Hetzner-Deploy** (nur nötig für Weg B)
|