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
}
...
]