diff --git a/docs/sprint-6/cannamanage-sprint6-plan.md b/docs/sprint-6/cannamanage-sprint6-plan.md index 6491a01..41d43e0 100644 --- a/docs/sprint-6/cannamanage-sprint6-plan.md +++ b/docs/sprint-6/cannamanage-sprint6-plan.md @@ -2,7 +2,7 @@ **Date:** 2026-06-12 **Author:** Patrick Plate / Lumen (Planner) -**Status:** Draft v2 +**Status:** Draft v3 (post-review) **Base Branch:** `main` **Sprint Branch:** `sprint/6-production` **Sprint Goal:** Production readiness — deploy, DSGVO compliance, Stripe payments, immutable audit log, grow calendar, notifications, launch @@ -143,6 +143,7 @@ server { add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always; add_header Referrer-Policy strict-origin-when-cross-origin always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' js.stripe.com; frame-src js.stripe.com hooks.stripe.com; img-src 'self' data: *.stripe.com;" always; # Rate limiting limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s; @@ -267,8 +268,10 @@ WEEKLY_RETENTION=28 TIMESTAMP=$(date +%Y%m%d_%H%M%S) DAY_OF_WEEK=$(date +%u) +# GPG-encrypted backup (DSGVO-compliant) docker exec cannamanage-db pg_dump -U ${DB_USER} -d ${DB_NAME} \ - | gzip > "${BACKUP_DIR}/cannamanage_daily_${TIMESTAMP}.sql.gz" + | gpg --encrypt --recipient cannamanage-backup \ + > "${BACKUP_DIR}/cannamanage_daily_${TIMESTAMP}.sql.gpg" # Weekly backup on Sundays if [ "$DAY_OF_WEEK" -eq 7 ]; then @@ -462,9 +465,10 @@ public ResponseEntity requestErasure(@AuthenticationPrincipal UserDetails | Tier | Price | Features | Stripe Price ID | |------|-------|----------|-----------------| -| **Free Trial** | €0 (14 days) | Full features, 1 club, max 7 members | — (trial period on Starter) | -| **Starter** | €29/month | 1 club, up to 50 members, email support | `price_starter_monthly` | -| **Pro** | €79/month | 1 club, unlimited members, priority support, API access | `price_pro_monthly` | +| **Free Trial** | €0 (3 months) | Full features, 1 club, ≤30 members | — (trial period on Starter) | +| **Starter** | €19/month | 1 club, ≤30 members, email support | `price_starter_monthly` | +| **Pro** | €49/month | 1 club, ≤100 members, priority support, API access | `price_pro_monthly` | +| **Enterprise** | Custom | Multiple clubs, unlimited members, dedicated support, SLA | Contact sales | **Backend — StripeService:** @@ -508,7 +512,7 @@ public class StripeService { .setSuccessUrl(successUrl + "?session_id={CHECKOUT_SESSION_ID}") .setCancelUrl(cancelUrl) .setSubscriptionData(SessionCreateParams.SubscriptionData.builder() - .setTrialPeriodDays(14L) + .setTrialPeriodDays(90L) // 3-month free trial .build()) .build(); return Session.create(params); @@ -919,13 +923,13 @@ public class NotificationService { ## 4. Phase Details -### Phase 1: Production Deployment (Hetzner VPS) +### Phase 1: Production Deployment (IONOS VPS) **Effort:** 3 days | Step | Task | Files | |------|------|-------| -| 1.1 | Provision Hetzner VPS (CX31), install Docker + Docker Compose + Nginx + Certbot | Server setup (manual) | +| 1.1 | Configure IONOS VPS (existing plate-software.de), install Docker + Docker Compose + Nginx + Certbot | Server setup (manual) | | 1.2 | Register domain, configure DNS A/AAAA records | DNS config (manual) | | 1.3 | Create `docker-compose.prod.yml` with env vars, volumes, restart policies | `docker-compose.prod.yml` | | 1.4 | Create `.env.prod.example` with all required env vars documented | `.env.prod.example` | @@ -1005,7 +1009,7 @@ public class NotificationService { - [ ] Club owner can subscribe via SEPA Lastschrift (direct debit) - [ ] Club owner can subscribe via PayPal or Card as alternatives -- [ ] 14-day free trial starts automatically for new clubs +- [ ] 3-month free trial starts automatically for new clubs - [ ] After trial expiry without payment → club access locked with "Please subscribe" message - [ ] `invoice.paid` webhook extends access, stores payment in audit log - [ ] `invoice.payment_failed` webhook shows warning banner to club owner @@ -1015,6 +1019,8 @@ public class NotificationService { - [ ] Webhook endpoint validates Stripe signature (rejects spoofed events) - [ ] All payment events logged in audit log +**Voraussetzung:** Auftragsverarbeitungsvertrag (AVV) mit Stripe abschließen (DSGVO Art. 28). Stripe bietet standardisiertes DPA unter stripe.com/de/legal/dpa. + --- ### Phase 4: Immutable Audit Log @@ -1046,6 +1052,16 @@ public class NotificationService { - [ ] Search works across description field (full-text) - [ ] Pagination works (default 50 per page) +**Database-level protection:** + +```sql +REVOKE DELETE ON audit_events FROM cannamanage_app; +``` + +Only the DBA role can delete audit records — the application user has INSERT + SELECT only. + +**Aufbewahrungsfrist:** 10 Jahre (KCanG-konform). Automatische Löschung nach 10 Jahren via Scheduled Task. + --- ### Phase 5: Grow Calendar (FULL) @@ -1166,7 +1182,7 @@ public class NotificationService { | R1 | Stripe integration complexity (SEPA mandates, webhook reliability) | Medium | High | Start with test mode, use Stripe CLI for local webhook testing, extensive logging | | R2 | DSGVO compliance gaps (incomplete data export, retention conflicts) | Medium | High | Legal review of Datenschutzerklärung before launch, document what data is retained and why | | R3 | Production deployment issues (SSL, DNS propagation, Docker networking) | Low | Medium | Test with staging subdomain first (`staging.cannamanage.de`), blue-green deploy | -| R4 | WebSocket scalability on single VPS | Low | Low | Hetzner CX31 handles 1000+ concurrent WS connections easily for MVP; scale later if needed | +| R4 | WebSocket scalability on single VPS | Low | Low | IONOS VPS handles 1000+ concurrent WS connections easily for MVP; scale later if needed | | R5 | Stripe webhook delivery failures (lost events) | Low | High | Implement idempotent webhook handler + periodic reconciliation job (check subscription status every 6h) | | R6 | Backup corruption / restore failure | Low | Critical | Test restore procedure monthly, verify backup integrity with checksum | | R7 | FullCalendar bundle size (large JS library) | Low | Low | Dynamic import, lazy-load only on `/grow` route |