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

arkadaşlar herkese hayırlı geceler diliyorum bir projeye başlıyacağım fakat şu veritabanı kurgusu ve senaryoların da hiç kendime güvenim yok yada gereksiz stress yapıyorum bilmiyorum. Sizlerin yardımına ihtiyacım var.

Bir site düşünün. Prize Plan paketleri olan 3-5 adet ve bunların aylık haftalık yıllık lifetime gibi hizmet süreleri olacak. Bunların satışı yapılacağı ödeme entegrasyonu yapacağım. Ve Auth işlemlerimiz olacak. Şimdi Satış sonrası paketlerin zaman aşımı ve bitimi için olsun paket satış tipi bakiye tarzındamı yoksa hiç gerek duymadan direkt paketin tutarı kadarmı satış gerçekleşmeli birde en son ödeme yapan kullanıcıların paketleri ve paket satın almışmı bunu check edebileceğim bir yapıda olması lazım api ile account check ettirmem gerekebilir. Bu kısımları hallederimde kurguyu kuramıyorum aslında laravel bilgim mevcut fakat hep edit ve basit proje işlemlerinde ilerledim böyle biraz kompleks işlemlerde gözüm korkuyor bana bir schema cıkarırsanız valla dua ederim herkese çok teşekkür ederim.

    • mgsmus

      Seviye 1382
    • Düzenlendi

    emreee7834 Komple bir abonelik sistemini burada anlatamayacağım için yol göstermesi açısından bu şekilde bir başlangıç yapabilirsiniz:

    Modeller:

    Plan
    Sizin ana planlarınızı bu model tutacak. Ücretsiz, Başlangıç, Pro gibi.

    PlanFeature
    Plan'a eklenebilecek ek özellikleri tutacak

    PlanPricing
    Plan ödeme tiplerini tutacak

    Subscription
    Kullanıcının hangi planı kullandığını tutacak

    Temel ilişkilere gelince;

    Plan BelongsToMany PlanPricing $plan->pricing
    Plan BelongsToMany PlanFeature $plan->features
    User HasMany Subscription $user->subscriptions
    User HasOne (ofMany) Subscription (Aktif abonelik) $user->subscription
    User HasOne (ofMany) Subscription (Son biten abonelik) $user->lastSubscription

    app/Models/User.php:

    public function subscriptions(): HasMany
    {
        return $this->hasMany(Subscription::class);
    }
    
    public function subscription(): HasOne
    {
       return $this
           ->hasOne(Subscription::class)
           ->ofMany([
                'id' => 'max',
            ], function ($query) {
                $query->whereNull('ends_at');
            });
    }
    
    public function lastSubscription(): HasOne
    {
       return $this
           ->hasOne(Subscription::class)
           ->ofMany([
                'id' => 'max',
            ], function ($query) {
                $query->whereNotNull('ends_at');
            });
    }

    app/Models/Subscription.php:

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
    
    public function plan(): BelongsTo
    {
        return $this->belongsTo(Plan::class);
    }
    
    public function getFeature(string $slug): ?PlanFeature
    {
        return $this
            ->plan
            ->features
            ->firstWhere('slug', $slug);
    }
    
    public function getPricing(): PlanPricing
    {
        $return $this
            ->plan
            ->pricing
            ->firstWhere('id', $this->pricing_id);
    }

    app/Models/Plan.php

    public function subscriptions(): HasMany
    {
        return $this->hasMany(Subscription::class);
    }
    
    public function pricing(): BelongsToMany
    {
        return $this
            ->belongsToMany(Pricing::class, 'plan_pricing', 'plan_id', 'pricing_id')
            ->as('cost')
            ->withPivot('price', 'interval');
    }
    
    public function features(): BelongsToMany
    {
        return $this
            ->belongsToMany(PlanFeature::class, 'plan_feature', 'plan_id', 'feature_id')
            ->as('cost')
            ->withPivot('price', 'per_feature');
    }

    Örneğin:

    $user = User::find(1);
    
    // Başlanıç planı
    $starterPlan = Plan::create([
        'name' => 'Başlangıç',
        'slug' => 'starter',
    ]);
    
    // Pro plan
    $proPlan = Plan::create([
        'name' => 'Profesyonel',
        'slug' => 'pro',
    ]);
    
    // Aylık ödeme tipi
    $pricingMonthly = Pricing::create([
        'name' => 'Aylık',
        'slug' => 'monthly',
    ]);
    
    // Yıllık ödeme tipi
    $pricingYearly = Pricing::create([
        'name' => 'Yıllık',
        'slug' => 'yearly',
    ]);
    
    // Mail ek özelliği. Plan fiyatına ekstra yansıtılabilir.
    $mailFeature = PlanFeature::create([
        'name' => 'Mail',
        'slug' => 'mail',
    ]);
    
    // Destek ek özelliği. Plan fiyatına ekstra yansıtılabilir.
    $supportFeature = PlanFeature::create([
        'name' => '7/24 Destek',
        'slug' => 'support',
    ]);
    
    // Vide çevirme özelliği. Plan fiyatına ekstra yansıtılabilir.
    $videoConvertFeature = PlanFeature::create([
        'name' => 'Video Çevirme Hizmeti',
        'slug' => 'video-convert',
    ]);
    
    // Başlangıç planı aylık ve yıllık olarak iki ödeme tipine sahip
    $starterPlan
        ->pricing()
        ->attach([
            $pricingMonthly => [
                'price' => 19.90,
                'interval' => 1, // 19.90 her 1 ay için
            ],
            $pricingYearly => [
                'price' => 199.90,
                'interval' => 1, // 199.90 her 1 yıl için
            ]
        ]);
    
    // Pro plan aylık ve yıllık olarak iki ödeme tipine sahip
    $proPlan
        ->pricing()
        ->attach([
            $pricingMonthly => [
                'price' => 29.90,
                'interval' => 1,
            ],
            $pricingYearly => [
                'price' => 299.90,
                'interval' => 1,
            ]
        ]);
    
    // Başlangıç planında mail ek özelliği seçilebilir.
    // Örneğin kullanıcı bu plana aylık abone olmuş ise ve 3 mail açmış ise aylık
    // 19.90 plan ücreti + (3 * 10 TL mail ücreti) = 49.90 TL para ödeyecek demektir.
    $starterPlan
        ->features()
        ->attach($mailFeature, [
            'price' => 10, // Ücreti 10 TL
            'per_feature' => 1, // 10 TL her 1 mail için geçerli.
        ]);
    
    // Pro planda ek olarak mail, destek ve video çevirme hizmeti var
    $proPlan
        ->features()
        ->attach([
            $mailFeature => [
                'price' => 10,
                'per_feature' => 1,
            ],
            $supportFeature => [
                'price' => 5,
                'per_feature' => 1,
            ],
            $videoConvertFeature => [ 
                'price' => .10, // Dakika ücreti 10 kuruş
                'per_feature' => 1, // 10 kuruş her 1 dakika için istenen ücret
            ]
        ]);
    
    // Kullacını Pro planın aylık ödeme tipine şimdi abone oldu
    $user
        ->subscriptions()
        ->create([
            'plan_id' => $proPlan->id,
            'pricing_id' => $pricingMonthly->id,
            'started_at' => now(),
            'ended_at' => null, 
            'is_trial' => false, // Deneme değil
            'trial_ends_at' => null, // Deneme olsaydı bu tarihte bitecekti.
        ]);

    Mesela pro kullanıcılar video çevirim yapıyor, kullanımları bir tabloda tutuyorsunuz. Ay sonunda bir task, ne kadar ödeme yapılacağını hesaplayacaksa:

    // Bu ay...
    $date = Date::parse('2022-01-01');
    $plan = Plan::firstWhere('slug', 'pro');
    $pricing = Pricing::firstWhere('slug', 'monthy');
    
    $users = User::whereHas('subscription', function($query) use ($plan, $pricing) {
        $query
            ->where('plan_id', $plan->id)
            ->where('pricing_id', $pricing->id)
        })
            ->with([
                'subscription.plan.features',
                'subscription.plan.pricing',
                'videoConverts:id,user_id,minutes' => function($query) use ($date) {
                    $query->whereBetween('created_at', [
                        $date->firstOfMonth(), 
                        $date->endOfMonth()
                    ]);
                }
            ])
            ->get();
    
    $billingAmounts = [];
    
    foreach($users as $user) {
        // Parasal işlemlerde bölme çarpma yapılmaz pay edilir. Buraları hızlı geçtim.
        // Konuyla ilgili olarak forumda ve internette moneyphp aratıp araştırın.
        $planPricing = $user->subscription->getPricing();
        $planCost = $planPricing->cost->price / $planPricing->cost->interval;
        $videoConvertCost = 0;
        $totalMinutesSpent = 0;
          
        if($videoConvertFeature = $user->subscription->getFeature('video-convert')) {
            if($user->videoConverts) {
                $totalMinutesSpent = $user->videoConverts->sum('minutes');
                $pricePerMinute = $videoConvertFeature->cost->price / $videoConvertFeature->cost->per_feature;
                $videoConvertCost = $totalMinutesSpent * $pricePerMinute;
            }
        }
        
        $billingAmounts[] = [
            'user_id' => $user->id,
            'plan' => $planCost,
            'video-convert' => $videoConvertCost,
            'video-convert-minutes' => $totalMinutesSpent,
            'total' => $planCost + $videoConvertCost
        ];
    }
    // $billingAmounts
    [
        {
            "user_id": 1,
            "plan": 29.90,
            "video-convert": 1,
            "video-convert-minutes": 10,
            "total": 39.90
        }
        ...
    ]

      Yoğurdu nasıl yediğinizi soruyorsunuz bize?
      standart olarak mgsmus un yazdığı gibi yapılabilir.
      Ama anlamadan yaparsanız,muhtemelen 58.976 tane bug ureteceksiniz 🙂