Laravel Türkiye Discord Kanalı Forumda kod paylaşılırken dikkat edilmesi gerekenler!Birlikte proje geliştirmek ister misiniz?

Merhabalar
Laravel de Kullanabileceğim Abonelik Sistemi Arıyorum
Gelişmiş Bir Abonelik Paketi Bilen Varmı Acaba Baya bi Araştırdım Fakat İstediğim Gibi Bir Paket Bulamadım
İstedigim Paket Tam Olarak
https://github.com/Laragear/SubscriptionsDemo
Linkteki Gibi
Birden Fazla Model İçin Birden Fazla Abonelik Türleri vs Gibi Çeşitli Abonelik Türlerim Olacak
Buda Beni Baya Karmaşık Bir yola Soktu Bu Yüzden Bir Paket Arayısına Girdim.
Herhangi Bir Ödeme Sistemi Entegrasyonu Olmak Zorunda Değil
Ödeme Sistemini Kendim Entegre Etmeyi Planlıyorum
Linkteki Paket İsteklerimi Karsılayacak Aslında Sponsor Olup Pakete Erişmeyi Planlıyorum Fakat
Pakettin Ne Derece Saglıklı Oldugu ve Pakette Anlatılan Özellikleri Barındırıp Barındırmadıgı Konusunda Sorun Olurmu Acaba
Varsa Benzer Bildiğiniz Paket Önerileri Alabilirmiyim
Simdiden Herkese Teşekkürler.

  • mgsmus bunu yanıtladı.
  • Serhatt

    plans
    +----+-------+
    | id | name  |
    +----+-------+
    |  1 | Basic |
    |  2 | Pro   |
    +----+-------+
    subscriptions
    +----+---------+---------+---------------------+---------------------+---------------+
    | id | user_id | plan_id |      starts_at      |       ends_at       | trial_ends_at |
    +----+---------+---------+---------------------+---------------------+---------------+
    |  1 |       1 |       1 | 2022-11-20 23:40:56 | 2022-12-20 23:40:56 |               |
    +----+---------+---------+---------------------+---------------------+---------------+
    features
    +----+----------+-----------------------+
    | id |   slug   |         name          |
    +----+----------+-----------------------+
    |  1 | domain   | Domain Subscription   |
    |  2 | bot      | Bot Subscription      |
    |  3 | category | Category Subscription |
    +----+----------+-----------------------+
    plan_feature
    +----+---------+------------+----------+-------+--------+
    | id | plan_id | feature_id | pricing  | price | limits |
    +----+---------+------------+----------+-------+--------+
    |  1 |       1 |          1 | per_subs |  4.95 |      3 |
    +----+---------+------------+----------+-------+--------+
    planables
    +---------+------------+-----------------+-------------+-------------------+
    | plan_id | feature_id | subscription_id | planable_id |   planable_type   |
    +---------+------------+-----------------+-------------+-------------------+
    |       1 |          1 |               1 |           1 | App\Models\Domain |
    +---------+------------+-----------------+-------------+-------------------+

    ---

    User

    public function subscriptions(): HasMany
    {
        return $this->hasMany(Subscription::class);
    }
    
    public function currentSubscription(): HasOne
    {
        return $this->hasOne(Subscription::class)
            ->latestOfMany();
    }

    Subscription

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
    
    public function plan(): BelongsTo
    {
        return $this->belongsTo(Plan::class);
    }
    
    public function domains(): MorphedByMany
    {
        return $this->morphedByMany(Domain::class, 'planable');
    }
    
    public function bots(): MorphedByMany
    {
        return $this->morphedByMany(Bot::class, 'planable');
    }
    
    public function categories(): MorphedByMany
    {
        return $this->morphedByMany(Category::class, 'planable');
    }
    
    public function isTrial(): bool
    {
        return !is_null($this->trial_ends_at);
    }
    
    public function hasExpired(): bool
    {
        if($this->isTrial()) {
            return $this->trial_ends_at
                ->isPast();
        }
        
        return $this->ends_at
            ->isPast();
    }
    
    public function expiresIn(string $interval = 'days'): int
    {
        if($this->hasExpired()) {
            return 0;
        }
    
        $endsAt = $this->isTrial()
            ? $this->trial_ends_at
            : $this->ends_at;
    
        return match($interval) {
            'days' => $endsAt->diffInDays(),
            'hours' => $endsAt->diffInHours(),
        };
    }

    Plan

    public function subscriptions(): HasMany
    {
        return $this->hasMany(Subscription::class);
    }
    
    public function features(): BelongsToMany
    {
        return $this->belongsToMany(Feature::class, 'plan_feature', 'plan_id', 'feature_id')
            ->withPivot('pricing', 'price', 'limits');
    }
    
    public function domains(): MorphedByMany
    {
        return $this->morphedByMany(Domain::class, 'planable');
    }
    
    public function bots(): MorphedByMany
    {
        return $this->morphedByMany(Bot::class, 'planable');
    }
    
    public function categories(): MorphedByMany
    {
        return $this->morphedByMany(Category::class, 'planable');
    }

    Domain, Bot, Category

    public function plans(): MorphToMany
    {
        return $this->morphToMany(Plan::class, 'planable');
    }

    ---

    $now = now()->toImmutable();
    
    $user = User::findOrFail(1);
    
    $plan = Plan::findOrFail(1); // Basic
    
    // Kullanıcı Basic plana şimdiden başlayıp 1 ay boyunca abone oluyor
    $subscription = $user->subscriptions()
        ->create([
            'plan_id' => $plan->id,
            'starts_at' => $now,
            'ends_at' => $now->addMonth()
        ]);
    // Mevcut abonelik
    $currentSubscription = $user->currentSubscription;
    
    // Mevcut aboneliğin süresi doldu mu?
    $currentSubscription->hasExpired();
    
    // Mevcut abonelik deneme mi?
    $currentSubscription->isTrial();
    
    // Mevcut abonelik kaç gün/saat içinde bitiyor?
    $currentSubscription->expiresIn(); // ->expiresIn('hours')
    
    // Mevcut abone olunan plan
    $plan = $currentSubscription->plan;
    
    // Plan özellikleri
    $features = $plan->features;
    
    foreach($features as $feature) {
        $feature->pricing; // Fiyatlandırma şekli. Kullanım sayısına göre vs
        $feature->price; // Fiyatı
        $feature->limits; // Kullanım limiti
        $feature->feature->name; // "Domain Aboneliği"
        $feature->feature->slug; // "domain"
    }
    
    // Kullanıcının abone olduğu planda domain özelliği var mı?
    $features->contains('domain');
    
    // Plan dahilinde kullanıcının abone olduğu domainler, botlar ve kategorier
    $domains = $currentSubscription->where('plan_id', $plan->id)
        ->domains()
        ->get();
        
    $bots = $currentSubscription->where('plan_id', $plan->id)
        ->bots()
        ->get();
        
    $categories = $currentSubscription->where('plan_id', $plan->id)
        ->categories()
        ->get();
    
    // Kullanıcı mevcut aboneliğin planında example.com domainine abone olmuş mu?
    $domains->contains('name', 'example.com');
    
    // Kullanıcı mevcut aboneliğin planında hangi kategori id'lerini görebilir
    $categories->pluck('id');

    şeklinde gidiyor... Daha yazılacak çok kod var ama benden bu kadar, gerisi sizin.

    https://laravel.com/docs/9.x/eloquent-relationships
    https://laravel.com/docs/9.x/collections#method-contains

    Serhatt Neden ihtiyaçlarınıza göre kendiniz geliştirmiyorsunuz? Abonelik SaaS ürünlerde önemli bir bölüm ve kontrolünün tamamen sizde olması daha iyi olur.

      mgsmus
      kontrol saglamam gereken alanları ve değişkenler için bir türlü mantıgı oturtamadım malesef
      biraz mantıgı acayım sizlere belki mantıken de yardımınız olur

      sistemimde bulunanlar
      users, domains, bots ve categories, posts tabloları
      kullanıcılar domainlere ve botlara abone olacaklar bir kullanıcını aboneligi 1 domain içeriyorsa 1 domainle kısıtlı kalacak
      birden fazla domain eklemeyi içeren bir abonelige sahipse aboneliginin içerdiği kadar domain ekleyebilecek örnegin 5-10 domain gibi
      benzer kosullar bots tablosu için de geçerli bots tablosuna üyeler ekleme yapmayacak sadece burda bulunan aboneligini kapsayan botlara ait postları görüntüleyebilecek
      örnegin 3 adet bot aboneligi var o 3 botun içeriklerini görüntüleyebilecek
      yada tüm botlara erişimi var tüm botlara ait postları görüntüleyebilecek
      aynı mantık yine kategoriler içinde geçerli örnegin üye sadece 2 kategori den içerik almak istiyor diger kategorileri istemiyor
      bot farketmeksizin o iki kategori içeriklerini alabilmeli
      yada 5 bota ait 3 kategori gibi değişik varyasyonlar mevcut olacak

        Serhatt

        plans
        +----+-------+
        | id | name  |
        +----+-------+
        |  1 | Basic |
        |  2 | Pro   |
        +----+-------+
        subscriptions
        +----+---------+---------+---------------------+---------------------+---------------+
        | id | user_id | plan_id |      starts_at      |       ends_at       | trial_ends_at |
        +----+---------+---------+---------------------+---------------------+---------------+
        |  1 |       1 |       1 | 2022-11-20 23:40:56 | 2022-12-20 23:40:56 |               |
        +----+---------+---------+---------------------+---------------------+---------------+
        features
        +----+----------+-----------------------+
        | id |   slug   |         name          |
        +----+----------+-----------------------+
        |  1 | domain   | Domain Subscription   |
        |  2 | bot      | Bot Subscription      |
        |  3 | category | Category Subscription |
        +----+----------+-----------------------+
        plan_feature
        +----+---------+------------+----------+-------+--------+
        | id | plan_id | feature_id | pricing  | price | limits |
        +----+---------+------------+----------+-------+--------+
        |  1 |       1 |          1 | per_subs |  4.95 |      3 |
        +----+---------+------------+----------+-------+--------+
        planables
        +---------+------------+-----------------+-------------+-------------------+
        | plan_id | feature_id | subscription_id | planable_id |   planable_type   |
        +---------+------------+-----------------+-------------+-------------------+
        |       1 |          1 |               1 |           1 | App\Models\Domain |
        +---------+------------+-----------------+-------------+-------------------+

        ---

        User

        public function subscriptions(): HasMany
        {
            return $this->hasMany(Subscription::class);
        }
        
        public function currentSubscription(): HasOne
        {
            return $this->hasOne(Subscription::class)
                ->latestOfMany();
        }

        Subscription

        public function user(): BelongsTo
        {
            return $this->belongsTo(User::class);
        }
        
        public function plan(): BelongsTo
        {
            return $this->belongsTo(Plan::class);
        }
        
        public function domains(): MorphedByMany
        {
            return $this->morphedByMany(Domain::class, 'planable');
        }
        
        public function bots(): MorphedByMany
        {
            return $this->morphedByMany(Bot::class, 'planable');
        }
        
        public function categories(): MorphedByMany
        {
            return $this->morphedByMany(Category::class, 'planable');
        }
        
        public function isTrial(): bool
        {
            return !is_null($this->trial_ends_at);
        }
        
        public function hasExpired(): bool
        {
            if($this->isTrial()) {
                return $this->trial_ends_at
                    ->isPast();
            }
            
            return $this->ends_at
                ->isPast();
        }
        
        public function expiresIn(string $interval = 'days'): int
        {
            if($this->hasExpired()) {
                return 0;
            }
        
            $endsAt = $this->isTrial()
                ? $this->trial_ends_at
                : $this->ends_at;
        
            return match($interval) {
                'days' => $endsAt->diffInDays(),
                'hours' => $endsAt->diffInHours(),
            };
        }

        Plan

        public function subscriptions(): HasMany
        {
            return $this->hasMany(Subscription::class);
        }
        
        public function features(): BelongsToMany
        {
            return $this->belongsToMany(Feature::class, 'plan_feature', 'plan_id', 'feature_id')
                ->withPivot('pricing', 'price', 'limits');
        }
        
        public function domains(): MorphedByMany
        {
            return $this->morphedByMany(Domain::class, 'planable');
        }
        
        public function bots(): MorphedByMany
        {
            return $this->morphedByMany(Bot::class, 'planable');
        }
        
        public function categories(): MorphedByMany
        {
            return $this->morphedByMany(Category::class, 'planable');
        }

        Domain, Bot, Category

        public function plans(): MorphToMany
        {
            return $this->morphToMany(Plan::class, 'planable');
        }

        ---

        $now = now()->toImmutable();
        
        $user = User::findOrFail(1);
        
        $plan = Plan::findOrFail(1); // Basic
        
        // Kullanıcı Basic plana şimdiden başlayıp 1 ay boyunca abone oluyor
        $subscription = $user->subscriptions()
            ->create([
                'plan_id' => $plan->id,
                'starts_at' => $now,
                'ends_at' => $now->addMonth()
            ]);
        // Mevcut abonelik
        $currentSubscription = $user->currentSubscription;
        
        // Mevcut aboneliğin süresi doldu mu?
        $currentSubscription->hasExpired();
        
        // Mevcut abonelik deneme mi?
        $currentSubscription->isTrial();
        
        // Mevcut abonelik kaç gün/saat içinde bitiyor?
        $currentSubscription->expiresIn(); // ->expiresIn('hours')
        
        // Mevcut abone olunan plan
        $plan = $currentSubscription->plan;
        
        // Plan özellikleri
        $features = $plan->features;
        
        foreach($features as $feature) {
            $feature->pricing; // Fiyatlandırma şekli. Kullanım sayısına göre vs
            $feature->price; // Fiyatı
            $feature->limits; // Kullanım limiti
            $feature->feature->name; // "Domain Aboneliği"
            $feature->feature->slug; // "domain"
        }
        
        // Kullanıcının abone olduğu planda domain özelliği var mı?
        $features->contains('domain');
        
        // Plan dahilinde kullanıcının abone olduğu domainler, botlar ve kategorier
        $domains = $currentSubscription->where('plan_id', $plan->id)
            ->domains()
            ->get();
            
        $bots = $currentSubscription->where('plan_id', $plan->id)
            ->bots()
            ->get();
            
        $categories = $currentSubscription->where('plan_id', $plan->id)
            ->categories()
            ->get();
        
        // Kullanıcı mevcut aboneliğin planında example.com domainine abone olmuş mu?
        $domains->contains('name', 'example.com');
        
        // Kullanıcı mevcut aboneliğin planında hangi kategori id'lerini görebilir
        $categories->pluck('id');

        şeklinde gidiyor... Daha yazılacak çok kod var ama benden bu kadar, gerisi sizin.

        https://laravel.com/docs/9.x/eloquent-relationships
        https://laravel.com/docs/9.x/collections#method-contains

          @mgsmus detaylı yapı için cok teşekkürler başlangıç için bana yeterli ilerleyen zamanlarda takıldıgım yer olursa sorarım yine insallah

          @mgsmus
          abi yeniden selamlar
          plans
          features
          plan_feature
          bu üç tablo arasındaki ilişkide bir problem mi var acaba
          Planlarım

          Özelliklerim

          Plan Özellik İlişki Tablom

          Adımlarım
          Plan Ekle (plan adı,plan süresi) kaydet
          Özellik Ekle

          kaydederken kodum su sekilde
          $feature = Feature::create($request->only('name'));
          $plan = Plan::find($request->plan_id);
          $plan->features()->attach([
          [
          'feature_id' => $feature->id,
          'price' => $request->price ?? 0,
          'limits' => $request->limits,
          ]
          ]);

          burada plana sorgu atıp plan üzerinden özelliği kaydetmem gerekiyor
          düzenlerken plan_feature tablosundaki verileri alamıyorum
          Feature üzerinden de bir ilişki mi kurulması lazım plan_feature deki verileri almak için
          ve aynı sekilde Feature den Plan ı almak için

          Ben yanlıs mantıkta ilerliyormusum şuan için bu bölüm tamam
          planları ve özellikleri bagımsız ekliyorum
          plan düzenleme bölümünde plana ait özellikleri listeleyip
          eklemek istedigim özellikleri multi select içinden seçip planı kaydediyorum
          $plan->features()->sync($request->features);
          seçili olanları direk plana dahil ediyor yada cıkarıyor
          plan düzenlemenin altında bir tablo koydum tabloda da
          fiyat ve limite güncelleme attırıyorum

          mgsmus
          abi selamlar
          yeni konus acmak istemedim direk aynı konu üzerinde devam edeyim dedim

          planables tablosu ve ilişkileri problemlimi abi yukarıdaki anlatımında yoksa benim bakıs acımmı yanlıs

          ilk olarak features tablomuza özelliklerimizi herseyden bagımsız olarak ekliyoruz
          ardından plans tablomuza planlarımızı ekliyoruz
          $plan = Plan::create($request->only('name', 'interval'));
          $plan->features()->attach($request->features);

          plan adı süresi ve multiselect ile seçtigim özellikler plana dahil oluyor
          plan düzenle bölümümde plana ait özelliklerin fiyatlarını ve limitlerini düzenliyorum
          bu adımlarda problem yok
          ardından bir kullanıcıya abonelik ekliyorum
          abonelik ekleme de
          user_id,plan_id,trial_ends_at bots ve categories alanlarımı gönderiyorum formdan
          controller tarafında kodum su sekilde
          $now = now()->toImmutable();
          $plan = Plan::find($request->plan_id);
          $subscription = new Subscription();
          $subscription->user_id = $request->user_id;
          $subscription->plan_id = $request->plan_id;
          $subscription->starts_at = $now;
          $subscription->ends_at = $now->addMonth($plan->interval);
          $subscription->trial_ends_at = $request->trial_ends_at;
          $subscription->save();
          if ($plan->features->contains('id', 5)) {
          $subscription->bots()->attach(Bot::all()->pluck('id'));
          }else{
          $subscription->bots()->attach($request->bots);
          }
          if ($plan->features->contains('id', 6)) {
          $subscription->categories()->attach(Category::all()->pluck('id'));
          }else{
          $subscription->categories()->attach($request->categories);
          }

          burda yapmak istedigim formdan gelen planın
          özelliklerinde tüm botlar varsa tüm botları abonelige eklemek
          özelliklerinde tüm kategoriler varsa tüm kategorileri abonelige eklemek
          formdan veriyi gönderdigimde
          feature_id ve plan_id sütunları boş geliyor diye hata alıyorum
          bu tablo da sadece aboneligin kullanacagı botlar kategoriler ve domainler olmayacakmı
          feature_id ve plan_id nin bu tablodaki amacını anlayamadım
          bu tablo subscribable isminde
          subscription_id,subscribable_id,subscribable_type
          sütunlarından olusması gerekmiyormu
          burda benim kacırdıgım bir mantık vs mi var acaba

          abonelige baglı domainleri kullanıcı kendisi ekleyecek kaç domain ekleme limiti varsa ona göre

            Serhatt Siz plan oluştururken, plana örneğin 3'lü paket diye bir bot paketi ekleyeceğinizde bu 3 botu siz mi seçip botları ön tanımlı bir plan oluşturacaksınız yoksa sadece 3 bot seçileceğini belirtip kullanıcının daha sonra 3 tane bot seçmesini mi sağlayacaksınız?

              üstteki sql yapımda sürelere göre paket belirlemesi yapmıstım bu kullanımımında yanlıs oldugunu anladım
              böyle kullanırsam aboneligin özelliklerini listeledigimde
              aynı özellik birden fazla dönmüş oluyor o yapıyı düzenledim suan
              sizin belirttiginiz gibi
              Basic
              Premium
              Enterprise
              planları ekledim
              Özelliklerde Domain, Kategori, Bot Standart Kalıyor
              Basic te 1 Domain
              premiumda 5 Domain
              Enterprise da Domain özelliğinin limit alanı null geçiyorum
              başlangıç ve bitiş süreleri abonelik tablomda zaten var
              satın alırken kac aylık seçtiyse müşteri seçtigi ay*özellikler ücreti toplamı diyecegim
              sadece planable tablo yapısı sorunlu gibi duruyor suanda

                Serhatt Şöyle bir kod yazmışsınız:

                $subscription->bots()->attach($request->bots); 
                // Bu arada daima $request->input('bots') şeklinde kullanın,
                // $request->bots şeklinde kullanmayın

                Bu kodun planables tablosuna nasıl kayıt eklediğini görsel olarak anlatmaya çalışayım. $request->input('bots') değeri [3,5,9] olmak üzere;

                planables
                +---------+------------+-----------------+----------------+-------------------+
                | plan_id | feature_id | subscription_id | planable_type  |    planable_id    |
                +---------+------------+-----------------+----------------+-------------------+
                | null    | null       | 1               | App\Models\Bot | 3                 |
                | null    | null       | 1               | App\Models\Bot | 5                 |
                | null    | null       | 1               | App\Models\Bot | 9                 |
                +---------+------------+-----------------+----------------+-------------------+
                | -       | -          | $subscription   | ->bots()       | ->attach([3,5,9]) |
                +---------+------------+-----------------+----------------+-------------------+

                En alttaki satırda hangi kod segmenti hangi alanı dolduruyor onu gösteriyor.

                • Ana model olarak $subscription kullanınca subscription_id doluyor.
                • bots isimli MorphedByMany ilişkisini kullanınca planable_type alanı doluyor.
                • attach([1,2,3]) yaparak da her bir planable_type için planable_id satırı oluşuyor.

                Burada yaptığınız hata ise, plan_id ve feature_id vermiyorsunuz. Yazdığınız koda ve verdiğim tabloya vs bakarsanız içinde hiç plan ve feature ile ilgili bir şeyler olmadığını zaten görüyorsunuz. Peki nasıl olması gerekiyordu? Dokümanda da belirttiği gibi:
                https://laravel.com/docs/9.x/eloquent-relationships#updating-many-to-many-relationships

                ilişki alanları haricindeki alanları her bir planable_id için de ekstra alan olarak vermeniz gerekiyor. Yani açık veri şeklinde ifade etmek gerekirse şunu elde etmelisiniz:

                $subscription                  // subscription_id alanını doldur
                    ->bots()                   // planable_type alanını doldur
                    ->attach([
                        3 => [                 // planable_id alanını doldur
                            'plan_id' => 1,    // plan_id alanını doldur
                            'feature_id' => 1  // feature_id alanını doldur
                        ],
                        5 => [
                            'plan_id' => 1,
                            'feature_id' => 1
                        ],
                        9 => [
                            'plan_id' => 1,
                            'feature_id' => 1
                        ]
                    ]); 
                bir yıl sonra

                benim için en temel seviyede plans ve subscriptions yeterli işime yararmı bilemedim ama belki plan_feature tablosuda da olabilir .
                Kullanıcılar Free , Basic , Premium planlarından birini secer yıllık ödeme yada aylık ödeme vs tüm kullanıcılar default olarak free dir ancak bazı rotalar ve adresleri ve bazı işlemleri aboneliğe göre yapabiliyo olması gerek örneğin bir rotaya girememesi yada bir formu bellli sayıda oluşturabilmesi gibi işlemler için middlware mı yazmalıyım yada controllerda bu planını mı kontrol edicem