Files
aza/AzA march 2026 - Kopie (8)/deploy/STRIPE_LIVE_SETUP.md

251 lines
9.1 KiB
Markdown
Raw Normal View History

2026-04-16 13:32:32 +02:00
# 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 (monatlich) | Preis (jährlich) | Lookup-Key (monatlich) | Lookup-Key (jährlich) |
|---|---|---|---|---|
| AZA Basic (1 User) | CHF 59 / Monat | CHF 590 / Jahr | `aza_basic_monthly` | `aza_basic_yearly` |
| AZA Team (2 User) | CHF 89 / Monat | CHF 890 / Jahr | `aza_team_monthly` | `aza_team_yearly` |
### 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!) | Price-ID |
|---|---|---|---|
| CHF 59.00 | Monatlich | `aza_basic_monthly` | `price_1T53xHL5lREAW68VbuK43lmz` |
| CHF 590.00 | Jährlich | `aza_basic_yearly` | `price_1T542BL5lREAW68VNLQGCKWZ` |
#### Produkt 2: AZA Desktop Team (2 Benutzer)
| Feld | Wert |
|---|---|
| Name | AZA Desktop Team |
| Beschreibung | Für Gemeinschaftspraxen. Bis zu 2 Benutzer. |
Preise für dieses Produkt:
| Preis | Intervall | Lookup-Key (WICHTIG!) | Price-ID |
|---|---|---|---|
| CHF 89.00 | Monatlich | `aza_team_monthly` | `price_1T544tL5lREAW68VkmnmZ21Q` |
| CHF 890.00 | Jährlich | `aza_team_yearly` | `price_1T545RL5lREAW68VLbIh73AN` |
**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 (LIVE) | `https://api.aza-medwork.ch/stripe/webhook` |
| Hinweis | Webhook laeuft auf Hetzner (api.aza-medwork.ch), NICHT auf Hostpoint |
**Hinweis:** Der Webhook-Endpunkt laeuft auf dem Hetzner-Server (Docker/Caddy) unter
`https://api.aza-medwork.ch/stripe/webhook`. Dieser Pfad ist LIVE und funktional (2026-03-28).
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. **Basic (1 User)**: CHF 59/Monat → `aza_basic_monthly`, CHF 590/Jahr → `aza_basic_yearly`
2. **Team (2 User)**: CHF 89/Monat → `aza_team_monthly`, CHF 890/Jahr → `aza_team_yearly`
3. Alle vier Preise sind in Stripe angelegt mit korrekten Lookup-Keys
---
## 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`)
### Erledigt seit 2026-03-28 ✓
- ~~Live Stripe-Produkte + Preise~~ → Produkt AzA Office mit 4 Preisen angelegt (aza_basic_monthly, aza_basic_yearly, aza_team_monthly, aza_team_yearly)
- ~~Live Stripe Secret Key~~ → STRIPE_SECRET_KEY in deploy/.env gesetzt
- ~~Live Webhook Secret~~ → STRIPE_WEBHOOK_SECRET in deploy/.env gesetzt
- ~~Hetzner-Deploy~~ → api.aza-medwork.ch LIVE, Webhook functional (200 OK)
- Sandbox-Kauf erfolgreich verarbeitet (admin@aza-medwork.ch, aza_basic_monthly, active)
- lookup_key-Fallback aktiv via _lookup_key_from_price() fuer Sandbox ohne price.lookup_key
### Noch offen ✗
- **Jahreslizenz auf Landing-Page** (nur monatlich dargestellt)
- **Desktop-Lizenzpfad** gegen /stripe/license_debug validieren
- **WooCommerce-Grundkonfiguration** (getrennter Admin-Block)