🎵 Muzibu Trial System

MANTIK & PRENSİPLER - Final Plan (KOD YOK!)

📅 2025-12-04 🎵 Tenant 1001 (muzibu.com) 💾 Central: tuufi_4ekim 💾 Tenant: tenant_muzibu_1528d0

✅ GERÇEKLER - Mevcut Sistem Durumu

📊 Database Yapısı - Nerede Ne Var?

Central Database (tuufi_4ekim):

  • users tablosu: Tüm kullanıcılar burada (Tenant 1, 2, 1001 hepsi)
  • subscription_plans tablosu: Abonelik planları burada (2 plan var: Premium Yıllık, Premium Plan)
  • subscriptions tablosu: Abonelik kayıtları burada (1 adet subscription var)
  • Yabancı anahtarlar: subscriptions.customer_id → users.id, subscriptions.plan_id → subscription_plans.id

Tenant Database (tenant_muzibu_1528d0):

  • subscription_plans tablosu: VAR ama boş (migration çalışmış)
  • subscriptions tablosu: VAR ama boş (0 kayıt)
  • Sonuç: Tenant DB'de subscription tabloları var ama KULLANILMIYOR!
💡 ÖNEMLİ BULGU:

Subscription sistemi SADECE CENTRAL DB'de çalışıyor!

  • Tenant DB'deki subscription tabloları boş ve kullanılmıyor
  • Migration'lar hem central hem tenant'a çalıştırılmış
  • Kod Central DB'yi kullanıyor (Model connection yok = central kullanılır)

🔍 Mevcut Yapı - Ne Eksik?

Alan Central DB Tenant DB Durum
users.has_used_trial ❌ YOK - Eklenecek (Central)
subscription_plans.is_trial ❌ YOK ❌ YOK Eklenecek (Her ikisine)
subscription_plans.trial_days ✓ VAR ✓ VAR Mevcut (kullanılacak)
subscriptions.has_trial ✓ VAR ✓ VAR Mevcut
subscriptions.trial_ends_at ✓ VAR ✓ VAR Mevcut
subscriptions.current_period_end ✓ VAR ✓ VAR Mevcut (bitiş kontrolü)

💻 Kod Durumu - Ne Çalışıyor?

  • isPremium() metodu: 1 saatlik cache kullanıyor, current_period_end > now kontrolü yapıyor
  • isTrialActive() metodu: Cache kullanmıyor (iyi!), status='trial' kontrolü yapıyor
  • SongStreamController: isPremium() kullanıyor (cached!)
  • Subscription Model: scopeActive(), scopeTrial(), scopeExpired() var
  • Dinamik Cycle Sistemi: billing_cycles JSON array ile çalışıyor

❌ SORUN - Neden Çalışmıyor?

🚨 Ana Sorun: Gerçek Zamanlı Kontrol Yok

Kullanıcı bir kere giriş yapar, 5 yıl çıkış yapmaz. Bu sürede:

  • Trial bittiğinde: Cache 1 saat daha premium görünür, sonra bile kullanıcı çıkış yapmadığı için kontrol edilmez
  • Premium bittiğinde: Aylar boyu ücretsiz dinlemeye devam eder
  • Her müzik isteğinde: Cached isPremium() çağrılır, gerçek durum kontrol edilmez
  • Cache yenilendiğinde: Yeni cache oluşur, yine 1 saat gecikmeli!

🎯 Senaryo Analizi

❌ Şu An Ne Oluyor?

  • 00:00: Trial bitti
  • 00:00-01:00: Cache sayesinde hala premium
  • 01:00: Cache yenilendi, tekrar kontrol edildi, artık premium değil
  • Ama: Kullanıcı müzik çalmaya devam ediyor çünkü token hala geçerli!
  • Sonuç: Çıkış yapana kadar dinlemeye devam eder

✅ Olması Gereken:

  • 00:00: Trial bitti
  • 00:00:01: İlk müzik isteğinde fresh check yapılır
  • 00:00:01: Subscription expired olarak işaretlenir
  • 00:00:01: Event fırlatılır (email, bildirim)
  • Sonuç: 0 saniye gecikmeli kontrol!

✅ ÇÖZÜM - Nasıl Çalışacak?

💡 TEK PRENSİP

Her müzik isteğinde gerçek zamanlı kontrol yap, cache kullanma, durum değişince event fırlat!

🎯 Prensip 1: Request-Level Fresh Check

Ne demek?

Her müzik isteğinde veritabanından FRESH kontrol yap, cache'e bakmadan.

Nasıl çalışır?

  • Kullanıcı müzik çalmak ister
  • Stream endpoint isPremiumFresh() metodunu çağırır
  • isPremiumFresh() CACHE KULLANMAZ, direkt database'den kontrol eder
  • current_period_end > now mu? Kontrol et
  • Eğer bitmişse: Status'ü expired yap, event fırlat, false dön
  • Eğer aktifse: true dön, müziği çal
💡 Neden Fresh?
  • Cache 1 saat gecikmeli → Fresh 0 saniye gecikmeli
  • Kullanıcı giriş/çıkış yapmasa bile kontrol edilir
  • Her müzik isteği = yeni kontrol

🎯 Prensip 2: Event-Based System

Ne demek?

Subscription bittiğinde olay (event) fırlat, listener'lar otomatik çalışsın.

Nasıl çalışır?

  • isPremiumFresh() subscription bitmiş olduğunu tespit eder
  • Status'ü active'ten expired'e günceller
  • TrialExpired veya SubscriptionExpired event'ini fırlatır
  • Listener'lar devreye girer:
    • SendTrialExpiredEmail: Email gönderir
    • SendNotification: Push bildirim atar
    • LogExpiredSubscription: Log tutar
    • TrackAnalytics: Analytics'e kaydeder
💡 Avantajları:
  • Otomatik: Status değişince her şey otomatik olur
  • Merkezi: Tüm subscription olayları aynı mantıkla çalışır
  • Genişletilebilir: Yeni listener eklemek kolay
  • Test edilebilir: Event'ları mock'layarak test edilir

🎯 Prensip 3: Cron Backup (Yedek)

Ne demek?

Günde 1 kere tüm subscript'ları tara, biten varsa temizle.

Neden gerekli?

  • Bazı kullanıcılar siteye hiç girmeyebilir
  • Request-level check sadece site kullanıldığında çalışır
  • Cron günlük toplu temizlik yapar
  • Ama ANA GÜVEN request-level check'tir!

🔄 AKIŞ - Adım Adım Ne Olacak?

📍 Trial Subscription Akışı

1

Kullanıcı Kayıt Olur

Yeni kullanıcı muzibu.com'ye kayıt olur. Sistem otomatik olarak:

  • users tablosunda has_used_trial kontrol eder (false)
  • Trial planını bulur (is_trial = true olan plan)
  • Yeni subscription oluşturur:
    • plan_id: Trial planının ID'si
    • status: 'trial'
    • trial_ends_at: 7 gün sonra
    • current_period_end: 7 gün sonra
  • has_used_trial'ı true yapar (bir daha trial alamaz)
2

Kullanıcı Müzik Dinler (Trial Aktif)

Kullanıcı 7 gün boyunca sınırsız dinler:

  • Her müzik isteğinde isPremiumFresh() çağrılır
  • current_period_end kontrol edilir
  • Henüz bitmemiş → true döner → Müzik çalar
3

Trial Süresi Biter

7. günün sonunda trial biter:

  • current_period_end tarihi geçmiş olur
  • Kullanıcı hala giriş yapmış durumda (5 yıldır çıkış yok!)
  • Sistem bekliyor, kullanıcı müzik çalmayı dener...
4

İlk Müzik İsteği (Trial Bitti)

Kullanıcı trial bittikten sonra ilk kez müzik çalmak ister:

  • Stream endpoint isPremiumFresh() çağırır
  • Database'den fresh kontrol: current_period_end < now (BİTMİŞ!)
  • Status'ü güncelle: 'trial' → 'expired'
  • TrialExpired event'ini fırlat
  • false dön (premium değil)
  • Kullanıcıya 30 saniye preview sun
5

Event Listener'lar Çalışır

TrialExpired event'i tetiklenir, listener'lar otomatik çalışır:

  • SendTrialExpiredEmail: "Trial'ınız bitti, premium'a geçin" emaili
  • SendNotification: Push bildirim gönderir
  • LogExpiredSubscription: Log tutar
  • TrackAnalytics: Analytics'e kaydeder
6

Kullanıcı Premium Alır (veya Almaz)

Kullanıcının iki seçeneği var:

  • Premium alırsa: Yeni subscription oluşturulur, sınırsız dinler
  • Premium almazsa: 30 saniye preview ile devam eder

⚙️ İMPLEMENTASYON - Mantık & Yaklaşım

📋 Genel Yaklaşım:

4 faz halinde yapılacak. Her faz bağımsız test edilebilir.

Faz 1: Veritabanı Hazırlığı

Ne yapılacak?

  • users.has_used_trial ekle: Boolean field, kullanıcı trial kullandı mı? (Central DB)
  • subscription_plans.is_trial ekle: Boolean field, bu plan trial mı? (Central & Tenant DB)
  • Index ekle: has_used_trial ve is_trial'a index (hızlı arama için)

Neden gerekli?

  • has_used_trial: Kullanıcı bir kere trial aldıysa bir daha almasın
  • is_trial: Trial planını kolayca bulabilmek için (slug yerine boolean daha güvenli)

Faz 2: Metod & Event Sistemi

Ne yapılacak?

  • isPremiumFresh() metodu: isPremium()'in cache KULLANMAYAN versiyonu
  • TrialExpired event: Trial bitince fırlatılacak olay
  • SubscriptionExpired event: Premium bitince fırlatılacak olay
  • Listener'lar: Email, bildirim, log, analytics

Mantık:

  • isPremiumFresh() çağrıldığında subscription kontrol edilir
  • Bitmiş ama status hala active ise: Status'ü güncelle, event fırlat
  • Event fırlatılınca listener'lar otomatik çalışır

Faz 3: Stream Endpoint Entegrasyonu

Ne yapılacak?

  • SongStreamController değişikliği: isPremium() yerine isPremiumFresh() kullan
  • HLS Playlist endpoint: Her chunk isteğinde fresh check
  • Middleware (opsiyonel): Tüm API route'larına otomatik kontrol

Mantık:

  • Kullanıcı müzik çalmak ister → Fresh check yapılır
  • Premium/Trial aktifse → HLS stream URL ver
  • Değilse → 30 saniye preview ver

Faz 4: Trial Planı & Otomatik Atama

Ne yapılacak?

  • Admin panelden trial planı oluştur: Manuel olarak, is_trial=true
  • Kayıt sırasında otomatik atama: Yeni kullanıcıya trial ver
  • Cycle'lardan trial field'ı kaldır: UI'dan temizle (artık gerekli değil)
  • Cron job: Günlük yedek temizlik

Mantık:

  • Kullanıcı kayıt olunca has_used_trial kontrol edilir
  • False ise trial planı bulunur (is_trial=true olan)
  • Subscription oluşturulur, has_used_trial=true yapılır

🎯 SON KARAR - Özet

⚡ ÜÇ KATMANLI SİSTEM

1️⃣ Request-Level Fresh Check

Her müzik isteğinde isPremiumFresh() çağrılır. Cache KULLANILMAZ, direkt database kontrol edilir. Subscription bitmişse status güncellenir, event fırlatılır.

2️⃣ Event-Based System

Subscription bittiğinde TrialExpired veya SubscriptionExpired event'i fırlatılır. Listener'lar otomatik çalışır: Email, bildirim, log, analytics.

3️⃣ Cron Backup (Yedek)

Günde 1 kere çalışır, biten subscription'ları toplu temizler. Ama ANA GÜVEN request-level check'tir!

⚠️ KRİTİK KURALLAR:
  • ASLA cache kullanma stream endpoint'lerde!
  • Her müzik isteğinde fresh check yap!
  • Subscription bitince event fırlat!
  • Giriş/çıkış bekleme! Kullanıcı 5 yıl girmeyebilir!
  • Central DB kullan! Subscription sistemi central'da!
✅ BEKLENTİLER:
  • ✅ Trial bitince 0 saniye gecikmeli kontrol
  • ✅ Cache yok → Gerçek zamanlı
  • ✅ Event-based → Otomatik email/bildirim
  • ✅ Tenant-aware → Sadece Muzibu'ya özel
  • ✅ Merkezi sistem → Central DB kullanımı
  • ✅ 4 faz → Sistematik implementation