Zarządzanie subskrypcjami i okresami próbnymi
1. Model subskrypcji
class Subscription(models.Model):
company = models.OneToOneField(Company)
plan = models.CharField(choices=[
('free', 'Free'),
('starter', 'Starter'),
('professional', 'Professional'),
('enterprise', 'Enterprise'),
])
status = models.CharField(choices=[
('trial', 'Okres próbny'),
('pilot', 'Pilot'),
('af_trial', 'Trial biura rachunkowego'),
('af_client_trial', 'Trial klienta BR'),
('active', 'Aktywna'),
('expired', 'Wygasła'),
('cancelled', 'Anulowana'),
])
trial_type = models.CharField(null=True, choices=[
('reverse_trial', 'Reverse trial'),
('pilot', 'Pilot'),
('af_trial', 'Trial BR'),
('af_client', 'Trial klienta BR'),
])
valid_until = models.DateTimeField()
features = models.JSONField(default=dict)
2. Typy okresów próbnych
| Typ | Czas trwania | Plan | Cel |
| Reverse trial | 14 dni | Professional | Nowa firma — pełny dostęp, potem downgrade do Free |
| Pilot | 6 miesięcy | Professional | Pierwsi klienci — bezpłatnie w zamian za feedback |
| AF trial | 3 miesiące | Enterprise | Biuro rachunkowe — wymagane min. 3 klientów |
| AF client trial | 3 miesiące | Professional | Klient zaproszony przez biuro rachunkowe |
3. Cykl życia triala
Rejestracja firmy
│
▼
Trial aktywny (Professional / Enterprise)
│ ← Banner: "Pozostało X dni trialu"
│ ← Email: 7, 3, 1 dzień przed końcem
│
▼
Trial wygasa
│
├─→ Użytkownik wykupił plan → status: 'active'
│
└─→ Nie wykupił → downgrade do Free
├── Dane zachowane (nie usuwane)
├── Funkcje premium zablokowane
└── Banner: "Uaktualnij plan"
4. Zachowanie przy downgrade
| Zasób | Zachowanie |
| Pojazdy ponad limit | Zachowane, ale oznaczone jako nieaktywne. Brak nowych tras. |
| Kierowcy ponad limit | Zachowani, ale brak możliwości logowania (oprócz 1). |
| Raporty | Istniejące PDF zachowane. Generowanie nowych zablokowane. |
| Eksporty FK | Zablokowane. Istniejące pliki dostępne 30 dni. |
| Mapa real-time | Zablokowana. |
| Dane GPS | Zachowane. Śledzenie GPS nadal aktywne (1 pojazd). |
5. Feature gating
class FeatureGatingMiddleware:
"""Sprawdza plan subskrypcji przed dostępem do chronionych endpointów."""
PLAN_REQUIREMENTS = {
'/api/v1/reports/': 'starter',
'/api/v1/exports/': 'professional',
'/api/v1/accounting-firms/': 'enterprise',
'/api/v1/map/live/': 'professional',
}
def __call__(self, request):
required_plan = self._get_required_plan(request.path)
if required_plan and not self._has_plan(request.user, required_plan):
return JsonResponse({
'error': 'plan_required',
'required_plan': required_plan,
'message': f'Ta funkcja wymaga planu {required_plan}'
}, status=403)
6. Powiadomienia email
| Kiedy | Treść |
| Rejestracja | Witaj! Twój 14-dniowy trial Professional się rozpoczął. |
| 7 dni przed końcem | Zostało 7 dni trialu. Poznaj plany. |
| 3 dni przed końcem | Zostały 3 dni. Wybierz plan, aby nie stracić funkcji. |
| 1 dzień przed końcem | Ostatni dzień trialu! Uaktualnij teraz. |
| Trial wygasł | Trial wygasł. Twoje dane są bezpieczne. Uaktualnij plan. |
7. Zadania Celery
| Task | Harmonogram | Opis |
check_expiring_trials | Codziennie 8:00 | Wysyłka emaili o wygasających trialach |
downgrade_expired_trials | Codziennie 0:05 | Downgrade wygasłych triali do Free |
track_usage_snapshots | Codziennie 2:00 | Zapis snapshotów użycia per firma |
cleanup_expired_exports | Co tydzień | Usunięcie plików eksportów starszych niż 30 dni |
8. Architektura billing-ready
System jest przygotowany na integrację ze Stripe Billing (planowane Post-Sprint 7):
- Model Subscription ma pola na
stripe_customer_id i stripe_subscription_id - Webhook endpoint dla zdarzeń Stripe (payment_succeeded, subscription_cancelled)
- Przejście z manual billing na automatyczny bez zmiany modelu danych