Üyelik Sistemi - Final Uygulama Rehberi

Versiyon 14 | 2025-11-23 | Tenant-Aware Membership System

1. Özet & Sayılar

5
Tablo Rename
1
Yeni Modül
9
Yeni Kolon
7
Yeni Model
Neden Yeni Tablo Yok?

Mevcut shop_* tabloları hiç kullanılmamış (0 kayıt). Bunları rename edip kullanacağız.

  • user_devicessessions tablosu kullanılacak
  • login_logsactivity_log tablosu kullanılacak
  • customer_groupssubscription_plans yeterli

2. Mevcut Durum

Kullanılacak Mevcut Tablolar

Tablo Kayıt Durum Açıklama
shop_subscription_plans 0 RENAME → subscription_plans
shop_subscriptions 0 RENAME → subscriptions
shop_coupons 0 RENAME → coupons
shop_coupon_usages 0 RENAME → coupon_usages
shop_customer_addresses 0 RENAME → customer_addresses
sessions Var MEVCUT Cihaz takibi için kullanılacak
activity_log Var MEVCUT Giriş logları için kullanılacak

Mevcut Altyapı

Payment Modülü
  • Polymorphic Payable interface
  • PayTR entegrasyonu hazır
  • Transaction logging
  • Webhook handling
Spatie Activity Log
  • activity_log tablosu mevcut
  • Tüm modellerde trait var
  • Otomatik log kaydı
  • JSON metadata desteği

3. Kesinleşen Kararlar

Özellik Karar Not
Ödeme Sistemi PayTR Mevcut Payment modülü kullanılacak
Oturum Süresi 1 yıl SESSION_LIFETIME=525600
Fiyatlandırma 299₺/ay - 2.999₺/yıl Sadece Muzibu için
Cihaz Limiti Kullanıcı bazlı Varsayılan: 1 (settings'den)
Deneme Süresi Aboneliğe eklenir Kalan günler kaybolmaz
2FA SMS ile (opsiyonel) Tenant SMS API kullanır
Kurumsal Sınırsız alt hesap Sadece Muzibu
Kupon Universal sistem Tüm tenant'larda çalışır

4. Migrations

Önemli: Çifte Migration

Her migration hem database/migrations/ hem database/migrations/tenant/ altında oluşturulmalı!

Migration 1: Tablo Rename

2025_11_23_001_rename_shop_tables_to_universal.php

Eski İsim Yeni İsim Amaç
shop_subscription_plans subscription_plans Abonelik planları (Aylık, Yıllık, Kurumsal)
shop_subscriptions subscriptions Kullanıcı abonelikleri
shop_coupons coupons İndirim kuponları
shop_coupon_usages coupon_usages Kupon kullanım geçmişi
shop_customer_addresses customer_addresses Fatura adresleri

Migration 2: Users Tablosu Güncelleme

2025_11_23_002_add_membership_fields_to_users_table.php

Kolon Tip Varsayılan Açıklama
device_limit integer nullable null Kullanıcıya özel cihaz limiti
null ise settings'deki default_device_limit kullanılır
is_approved boolean true Manuel onay gerektiren üyelikler için
false ise kullanıcı giriş yapamaz, admin onayı bekler
failed_login_attempts integer 0 Başarısız giriş sayacı
Belirli sayıdan sonra hesap kilitlenir
locked_until timestamp nullable null Hesap kilit bitiş zamanı
Bu tarihten önce giriş engellidir
two_factor_enabled boolean false 2FA aktif mi?
true ise girişte SMS kodu istenir
two_factor_phone string nullable null 2FA telefon numarası
Varsayılan telefon yerine farklı numara kullanılabilir
is_corporate boolean false Kurumsal ana hesap mı?
true ise alt hesap oluşturabilir
corporate_code string nullable unique null Kurumsal davet kodu
Alt hesaplar bu kodla kayıt olur: FIRMA-ABC123
parent_user_id foreignId nullable null Üst kullanıcı referansı
Alt hesaplar için ana hesabın ID'si
// Migration kodu Schema::table('users', function (Blueprint $table) { $table->integer('device_limit')->nullable(); $table->boolean('is_approved')->default(true); $table->integer('failed_login_attempts')->default(0); $table->timestamp('locked_until')->nullable(); $table->boolean('two_factor_enabled')->default(false); $table->string('two_factor_phone')->nullable(); $table->boolean('is_corporate')->default(false); $table->string('corporate_code')->nullable()->unique(); $table->foreignId('parent_user_id')->nullable()->constrained('users')->onDelete('set null'); });

5. Models

Konum: app/Models/

SubscriptionPlan.php

Abonelik planları modeli

  • hasMany: subscriptions
  • Scope: active, forTenant
Subscription.php

Kullanıcı abonelikleri (implements Payable)

  • belongsTo: user, plan
  • Payable interface metodları
  • Scope: active, expired, trial
Coupon.php

İndirim kuponları

  • hasMany: usages
  • Scope: valid, expired
  • Method: isValidFor($user)
CouponUsage.php

Kupon kullanım geçmişi

  • belongsTo: user, coupon
  • morphTo: usable
CustomerAddress.php

Fatura adresleri

  • belongsTo: user
  • Scope: default

User Model Güncellemesi

app/Models/User.php

// Yeni relationships public function subscription() { return $this->hasOne(Subscription::class)->active(); } public function subscriptions() { return $this->hasMany(Subscription::class); } public function addresses() { return $this->hasMany(CustomerAddress::class); } public function parentUser() { return $this->belongsTo(User::class, 'parent_user_id'); } public function subUsers() { return $this->hasMany(User::class, 'parent_user_id'); } // Yeni methods public function getDeviceLimit(): int { return $this->device_limit ?? setting('default_device_limit', 1); } public function hasActiveSubscription(): bool { return $this->subscription() !== null; } public function isLocked(): bool { return $this->locked_until && $this->locked_until->isFuture(); }

6. Services

Konum: app/Services/Auth/

Service Amaç Ana Metodlar
DeviceService.php Cihaz yönetimi
  • getActiveSessions($user)
  • checkDeviceLimit($user)
  • terminateSession($sessionId)
  • terminateOldestSession($user)
TwoFactorService.php 2FA işlemleri
  • sendCode($user)
  • verifyCode($user, $code)
  • enable($user, $phone)
  • disable($user)
SubscriptionService.php Abonelik işlemleri
  • create($user, $plan, $coupon)
  • renew($subscription)
  • cancel($subscription)
  • addTrialDays($subscription, $days)
CouponService.php Kupon işlemleri
  • validate($code, $user)
  • apply($coupon, $amount)
  • markAsUsed($coupon, $user, $model)
CorporateService.php Kurumsal hesap yönetimi
  • createSubUser($parent, $data)
  • sendInvite($parent, $email)
  • removeSubUser($parent, $subUser)
  • getSubUsers($parent)
LoginLogService.php Giriş log yönetimi
  • logSuccess($user, $request)
  • logFailure($email, $reason, $request)
  • getHistory($user)

7. Settings (Tinker ile DB'ye)

SettingManagement Yapısı

Ayarlar Tinker ile veritabanına eklenir. "Kullanıcı" grubu (ID=3) altında alt gruplar olarak.

  • settings_groups (CENTRAL) → Grup + prefix tanımı
  • settings (CENTRAL) → Ayar tanımları (key, type, default)
  • settings_values (TENANT) → Her tenant'ın değerleri

Grup Hiyerarşisi

Kullanıcı (ID=3) ├── Kayıt Ayarları (ID=20, prefix: auth_registration) ├── Oturum Ayarları (ID=21, prefix: auth_session) ├── Güvenlik Ayarları (ID=22, prefix: auth_security) ├── Abonelik Ayarları (ID=23, prefix: auth_subscription) └── Kurumsal Ayarlar (ID=24, prefix: corporate)

Tinker ile Grup Ekleme

# php artisan tinker // Kayıt Ayarları grubu DB::table('settings_groups')->insert([ 'id' => 20, 'name' => 'Kayıt Ayarları', 'slug' => 'kayit-ayarlari', 'parent_id' => 3, // Kullanıcı 'prefix' => 'auth_registration', 'icon' => 'fas fa-user-plus', 'created_at' => now(), 'updated_at' => now() ]); // Oturum Ayarları grubu DB::table('settings_groups')->insert([ 'id' => 21, 'name' => 'Oturum Ayarları', 'slug' => 'oturum-ayarlari', 'parent_id' => 3, 'prefix' => 'auth_session', 'icon' => 'fas fa-clock', 'created_at' => now(), 'updated_at' => now() ]); // Güvenlik Ayarları grubu DB::table('settings_groups')->insert([ 'id' => 22, 'name' => 'Güvenlik Ayarları', 'slug' => 'guvenlik-ayarlari', 'parent_id' => 3, 'prefix' => 'auth_security', 'icon' => 'fas fa-shield-alt', 'created_at' => now(), 'updated_at' => now() ]); // Abonelik Ayarları grubu DB::table('settings_groups')->insert([ 'id' => 23, 'name' => 'Abonelik Ayarları', 'slug' => 'abonelik-ayarlari', 'parent_id' => 3, 'prefix' => 'auth_subscription', 'icon' => 'fas fa-credit-card', 'created_at' => now(), 'updated_at' => now() ]); // Kurumsal Ayarlar grubu DB::table('settings_groups')->insert([ 'id' => 24, 'name' => 'Kurumsal Ayarlar', 'slug' => 'kurumsal-ayarlar', 'parent_id' => 3, 'prefix' => 'corporate', 'icon' => 'fas fa-building', 'created_at' => now(), 'updated_at' => now() ]);

Tinker ile Ayar Ekleme (Örnek)

// Kayıt Aktif ayarı DB::table('settings')->insert([ 'group_id' => 20, 'label' => 'Kayıt Aktif', 'key' => 'auth_registration_enabled', 'type' => 'select', 'options' => json_encode(['0' => 'Kapalı', '1' => 'Açık']), 'default_value' => '1', 'help' => 'Yeni üye kaydı açık mı?', 'created_at' => now(), 'updated_at' => now() ]); // Deneme Süresi ayarı DB::table('settings')->insert([ 'group_id' => 20, 'label' => 'Deneme Süresi (gün)', 'key' => 'auth_registration_trial_days', 'type' => 'text', 'default_value' => '7', 'help' => 'Ücretsiz deneme süresi', 'created_at' => now(), 'updated_at' => now() ]);

Oluşturulacak Gruplar (5 adet)

ID Grup Adı Prefix Icon
20 Kayıt Ayarları auth_registration fas fa-user-plus
21 Oturum Ayarları auth_session fas fa-clock
22 Güvenlik Ayarları auth_security fas fa-shield-alt
23 Abonelik Ayarları auth_subscription fas fa-credit-card
24 Kurumsal Ayarlar corporate fas fa-building

Ayarlar (settings tablosu)

auth_registration (Kayıt Ayarları)

Key Label Type Default
auth_registration_enabled Kayıt Aktif select 1
auth_registration_email_verify E-posta Doğrulama select 1
auth_registration_approval Admin Onayı select 0
auth_registration_trial_days Deneme Süresi (gün) text 7

auth_session (Oturum Ayarları)

Key Label Type Default
auth_session_lifetime Oturum Süresi (dk) text 525600
auth_session_device_limit Cihaz Limiti text 1

auth_security (Güvenlik Ayarları)

Key Label Type Default
auth_security_max_attempts Max Giriş Denemesi text 5
auth_security_lockout Kilitleme Süresi (dk) text 30
auth_security_2fa_enabled 2FA Aktif select 1
auth_security_2fa_expiry 2FA Kod Süresi (dk) text 5

auth_subscription (Abonelik Ayarları)

Key Label Type Default
auth_subscription_paid_enabled Ücretli Üyelik select 0
auth_subscription_auto_renewal Otomatik Yenileme select 1
auth_subscription_reminder_days Hatırlatma (gün önce) text 7
auth_subscription_grace_days Tolerans Süresi (gün) text 3

corporate (Kurumsal Ayarlar)

Key Label Type Default
corporate_enabled Kurumsal Üyelik select 0
corporate_max_users Max Alt Kullanıcı text 0

Seeder Çalıştırma

# Seeder'ı çalıştır php artisan db:seed --class="Modules\\SettingManagement\\Database\\Seeders\\AuthSettingsSeeder" # Veya ana seeder'a ekleyip çalıştır php artisan db:seed --class="Modules\\SettingManagement\\Database\\Seeders\\SettingManagementDatabaseSeeder"

Kodda Kullanım

// Helper ile $trialDays = setting('auth_registration_trial_days', 7); $deviceLimit = setting('auth_session_device_limit', 1); $isPaidEnabled = setting('auth_subscription_paid_enabled', false); // Her tenant kendi settings_values tablosundan okur // Değer yoksa default_value kullanılır

8. Middleware

Konum: app/Http/Middleware/

Middleware Route Key Amaç
CheckDeviceLimit.php device.limit Cihaz limitini kontrol eder. Aşılmışsa eski cihazı çıkarma sayfasına yönlendirir.
CheckSubscription.php subscription Aktif abonelik kontrolü. Premium içerik için gerekli.
CheckApproval.php approved Kullanıcı onaylı mı kontrol eder. is_approved=false ise engeller.
// Kernel.php'ye ekle protected $middlewareAliases = [ // ... mevcut middleware'ler 'device.limit' => \App\Http\Middleware\CheckDeviceLimit::class, 'subscription' => \App\Http\Middleware\CheckSubscription::class, 'approved' => \App\Http\Middleware\CheckApproval::class, ];

9. Mail Module (nwidart)

Universal Mail Modülü

Tüm mail şablonları nwidart modül yapısında oluşturulacak. Tüm tenant'lar bu modülü kullanacak.

Modül Oluşturma

# nwidart modül oluştur php artisan module:make Mail # veya manuel olarak Modules/Mail/ altında oluştur

Modül Yapısı

Modules/Mail/ ├── app/ │ ├── Mail/ │ │ ├── WelcomeMail.php │ │ ├── TrialEndingMail.php │ │ ├── SubscriptionRenewalMail.php │ │ ├── PaymentSuccessMail.php │ │ ├── PaymentFailedMail.php │ │ ├── NewDeviceLoginMail.php │ │ ├── TwoFactorCodeMail.php │ │ └── CorporateInviteMail.php │ ├── Services/ │ │ └── MailService.php │ └── Providers/ │ └── MailServiceProvider.php ├── resources/ │ └── views/ │ └── emails/ │ ├── welcome.blade.php │ ├── trial-ending.blade.php │ ├── subscription-renewal.blade.php │ ├── payment-success.blade.php │ ├── payment-failed.blade.php │ ├── new-device-login.blade.php │ ├── two-factor-code.blade.php │ └── corporate-invite.blade.php ├── config/ │ └── config.php └── module.json

Mail Class'ları

Mail Class Tetikleyici İçerik
WelcomeMail.php Kayıt sonrası Hoş geldin mesajı, deneme süresi bilgisi, başlangıç rehberi
TrialEndingMail.php Deneme bitmeden 2 gün önce Hatırlatma, abone ol butonu, kalan gün
SubscriptionRenewalMail.php Yenileme öncesi 7 gün Otomatik yenileme bildirimi, tutar, tarih
PaymentSuccessMail.php Ödeme başarılı Ödeme onayı, fatura linki, sonraki yenileme
PaymentFailedMail.php Ödeme başarısız Hata bildirimi, tekrar deneme linki, destek
NewDeviceLoginMail.php Yeni cihazdan giriş Güvenlik uyarısı, cihaz bilgisi, tanımıyorsan bildir
TwoFactorCodeMail.php 2FA SMS yedeği 6 haneli kod, geçerlilik süresi
CorporateInviteMail.php Kurumsal davet Davet mesajı, şifre oluşturma linki

MailService.php

Modules/Mail/app/Services/MailService.php

namespace Modules\Mail\App\Services; class MailService { public function sendWelcome(User $user): void { Mail::to($user)->send(new WelcomeMail($user)); } public function sendTrialEnding(User $user, int $daysLeft): void { Mail::to($user)->send(new TrialEndingMail($user, $daysLeft)); } public function sendPaymentSuccess(User $user, Subscription $subscription): void { Mail::to($user)->send(new PaymentSuccessMail($user, $subscription)); } // ... diğer metodlar }

Kullanım

use Modules\Mail\App\Services\MailService; // Service injection ile public function register(MailService $mailService) { // Kullanıcı oluştur... $mailService->sendWelcome($user); } // veya facade ile \Modules\Mail\App\Mail\WelcomeMail::dispatch($user);

10. Cron Jobs

Konum: app/Console/Commands/

Command Schedule Görev
CheckTrialExpiryCommand.php Günlük 09:00
  • Deneme süresi bitenleri bul
  • 2 gün kalanları bul (hatırlatma)
  • E-posta gönder
SendRenewalRemindersCommand.php Günlük 10:00
  • 7 gün içinde yenilenecekleri bul
  • Hatırlatma e-postası gönder
ProcessRecurringPaymentsCommand.php Günlük 06:00
  • Bugün yenilenecekleri bul
  • Otomatik ödeme işle
  • Başarılı/başarısız e-posta
CleanupExpiredSessionsCommand.php Haftalık Pazar 03:00
  • Süresi dolmuş oturumları temizle
  • 1 yıldan eski aktivite loglarını arşivle
// app/Console/Kernel.php protected function schedule(Schedule $schedule) { $schedule->command('subscription:check-trial')->dailyAt('09:00'); $schedule->command('subscription:send-reminders')->dailyAt('10:00'); $schedule->command('subscription:process-recurring')->dailyAt('06:00'); $schedule->command('auth:cleanup-sessions')->weeklyOn(0, '03:00'); }

11. Uygulama Sırası

Önerilen Uygulama Sırası

Her adım tamamlandıktan sonra test edilmeli.

1 Migrations

2 Models

3 Settings

4 Services

5 Middleware

6 Mail Module (nwidart)

7 Cron Jobs

8 UI & Controllers

9 Test & Deploy

12. Tenant Farkları

Özellik İxtif (Tenant 2) Muzibu (Tenant 1001)
Ücretli Üyelik Kapalı Açık (299/2999₺)
Kurumsal Kapalı Açık (Sınırsız)
Deneme Süresi - 7 gün
2FA Opsiyonel Opsiyonel
Cihaz Limiti 1 1 (kurumsal: özel)
Oturum Süresi 1 yıl 1 yıl
Kupon Sistemi Aktif Aktif
Tenant Ayar Örnekleri

Her tenant kendi settings değerlerini kullanır:

// İxtif ayarları paid_membership_enabled = false corporate_enabled = false trial_days = 0 // Muzibu ayarları paid_membership_enabled = true corporate_enabled = true trial_days = 7