lucasdotvin/laravel-soulbscription

一个简单的接口,用于处理订阅和功能消费。

4.2.0 2024-04-08 03:49 UTC

README

Latest Version on Packagist run-tests Check & fix styling Total Downloads

关于

一个简单的接口,用于处理订阅和功能消费。

安装

您可以通过composer安装此包

composer require lucasdotvin/laravel-soulbscription

包迁移会自动加载,但您仍然可以使用此命令发布它们

php artisan vendor:publish --tag="soulbscription-migrations"
php artisan migrate

升级

如果您已经使用此包并需要切换到新版本,别忘了发布升级迁移

php artisan vendor:publish --tag="soulbscription-migrations-upgrades-1.x-2.x"
php artisan migrate

查看可用的升级迁移,请查看 升级文件夹

用法

要开始使用它,只需将给定的特性添加到您的 User 模型(或任何您想要有订阅的实体)

<?php

namespace App\Models;

use LucasDotVin\Soulbscription\Models\Concerns\HasSubscriptions;

class User
{
    use HasSubscriptions;
}

这就完成了!

设置功能

首先,您必须定义您将提供的功能。在下面的示例中,我们创建了两个功能:一个用于处理每个用户可以使用部署的分钟数以及他们是否可以使用子域。

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use LucasDotVin\Soulbscription\Enums\PeriodicityType;
use LucasDotVin\Soulbscription\Models\Feature;

class FeatureSeeder extends Seeder
{
    public function run()
    {
        $deployMinutes = Feature::create([
            'consumable'       => true,
            'name'             => 'deploy-minutes',
            'periodicity_type' => PeriodicityType::Day,
            'periodicity'      => 1,
        ]);

        $customDomain = Feature::create([
            'consumable' => false,
            'name'       => 'custom-domain',
        ]);
    }
}

通过说 deploy-minutes 是一个可消耗功能,我们是在告诉用户他们可以有限次数地使用它(或直到给定的数量)。另一方面,通过传递 PeriodicityType::Day 和 1 作为其 periodicity_typeperiodicity,我们说它应该每天更新。因此,用户可以在今天消费他们的分钟,然后在明天获得它,例如。

重要的是要记住,计划和可消耗功能都有自己的周期性,因此您的用户可以有月度计划,同时还有周度功能。

我们定义的另一个功能是 $customDomain,这是一个不可消耗的功能。作为不可消耗的功能,这个功能意味着仅用户可以执行特定的操作(在这种情况下,使用自定义域)。

后付费功能

您可以将功能设置为超出其费用使用。为此,只需将 postpaid 属性设置为 true

$cpuUsage = Feature::create([
    'consumable' => true,
    'postpaid'   => true,
    'name'       => 'cpu-usage',
]);

这样,用户将能够使用功能直到周期结束,即使他们没有足够的费用使用它(您可以在以后收费)。

配额功能

当创建例如文件存储系统时,您必须根据用户上传和删除文件来增加和减少功能消费。为了轻松实现这一点,您可以使用配额功能。这些功能具有独特的、不可过期的消费,因此它们可以反映一个恒定值(如本例中使用的系统存储)。

class FeatureSeeder extends Seeder
{
    public function run()
    {
        $storage = Feature::create([
            'consumable' => true,
            'quota'      => true,
            'name'       => 'storage',
        ]);
    }
}

...

class PhotoController extends Controller
{
    public function store(Request $request)
    {
        $userFolder = auth()->id() . '-files';

        $request->file->store($userFolder);

        $usedSpace = collect(Storage::allFiles($userFolder))
            ->map(fn (string $subFile) => Storage::size($subFile))
            ->sum();

        auth()->user()->setConsumedQuota('storage', $usedSpace);

        return redirect()->route('files.index');
    }
}

在上面的示例中,我们在seeder中将 storage 设置为一个配额功能。然后,在控制器中,我们的代码将上传的文件存储在文件夹中,通过检索所有子文件来计算此文件夹的大小,最后将消耗的 storage 配额设置为目录的总大小。

创建计划

现在您需要定义您应用中可用的订阅计划

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use LucasDotVin\Soulbscription\Enums\PeriodicityType;
use LucasDotVin\Soulbscription\Models\Plan;

class PlanSeeder extends Seeder
{
    public function run()
    {
        $silver = Plan::create([
            'name'             => 'silver',
            'periodicity_type' => PeriodicityType::Month,
            'periodicity'      => 1,
        ]);

        $gold = Plan::create([
            'name'             => 'gold',
            'periodicity_type' => PeriodicityType::Month,
            'periodicity'      => 1,
        ]);
    }
}

这里的一切都很简单,但值得强调的是:通过接收上面的周期性选项,这两个计划被定义为按月。

无周期计划(“免费计划”或“永久计划”)

您可以为计划定义无周期性,这样您的用户可以永久订阅(或者直到他们取消订阅)。要这样做,只需将 null 值传递给 periodicity_typeperiodicity 属性

$free = Plan::create([
    'name'             => 'free',
    'periodicity_type' => null,
    'periodicity'      => null,
]);

宽限期

您可以为每个计划定义宽限期,这样您的用户在计划到期后不会立即失去对功能的访问

$gold = Plan::create([
    'name'             => 'gold',
    'periodicity_type' => PeriodicityType::Month,
    'periodicity'      => 1,
    'grace_days'       => 7,
]);

根据上述配置,"黄金"计划的订阅者将在计划到期和访问被暂停之间有七天的宽限期。

将计划与功能关联

由于每个功能可以属于多个计划(并且它们可以有多个功能),您必须将它们关联起来

use LucasDotVin\Soulbscription\Models\Feature;

// ...

$deployMinutes = Feature::whereName('deploy-minutes')->first();
$subdomains    = Feature::whereName('subdomains')->first();

$silver->features()->attach($deployMinutes, ['charges' => 15]);

$gold->features()->attach($deployMinutes, ['charges' => 25]);
$gold->features()->attach($subdomains);

将可消耗功能与计划关联时,必须为 charges 传递一个值。

在上面的示例中,我们为银用户提供15分钟的部署时间,为黄金用户提供25分钟。我们还允许黄金用户使用子域名。

订阅

现在您已经有一组具有自身功能的计划,是时候将用户订阅到它们了。注册订阅相当简单

<?php

namespace App\Listeners;

use App\Events\PaymentApproved;

class SubscribeUser
{
    public function handle(PaymentApproved $event)
    {
        $subscriber = $event->user;
        $plan       = $event->plan;

        $subscriber->subscribeTo($plan);
    }
}

在上面的示例中,我们模拟了一个在支付批准时为其用户订阅的应用程序。很容易看出,subscribeTo 方法只需要一个参数:用户正在订阅的计划。您还可以传递其他选项给它来处理下面将要介绍的特殊情况。

默认情况下,subscribeTo 方法会根据计划的周期性计算到期日期,所以您不必担心这一点。

定义到期和开始日期

您可以通过将 $expiration 参数传递给方法调用来覆盖订阅的到期日期。下面,我们将给定用户的订阅设置为仅在下一年到期。

$subscriber->subscribeTo($plan, expiration: today()->addYear());

您也可以定义订阅何时开始(默认行为是立即开始)

<?php

namespace App\Http\Controllers;

use App\Http\Requests\StudentStoreFormRequest;
use App\Models\Course;
use App\Models\User;
use LucasDotVin\Soulbscription\Models\Plan;

class StudentController extends Controller
{
    public function store(StudentStoreFormRequest $request, Course $course)
    {
        $student = User::make($request->validated());
        $student->course()->associate($course);
        $student->save();

        $plan = Plan::find($request->input('plan_id'));
        $student->subscribeTo($plan, startDate: $course->starts_at);

        return redirect()->route('admin.students.index');
    }
}

在上面的示例中,我们模拟了一个学校应用程序。它必须在学生注册时为其订阅,但同时也确保订阅只有在课程开始时才生效。

切换计划

用户经常改变主意,您必须应对这种情况。如果您需要更改用户的当前计划,只需调用 switchTo 方法

$student->switchTo($newPlan);

如果您不传递任何参数,该方法将取消当前订阅并立即启动新的订阅。

此调用将触发 SubscriptionStarted(Subscription $subscription) 事件。

获取当前余额

如果您需要用户的剩余费用,只需调用 balance 方法。想象一下这样一个场景,学生有一个名为 notes-download 的可消耗功能。要获取剩余下载限制

$student->balance('notes-download');

这只是为丰富开发者体验而添加的 getRemainingCharges 的别名。

安排切换

如果您想保持用户在当前计划到期前保持该计划,将 $immediately 参数设置为 false

$primeMonthly = Plan::whereName('prime-monthly')->first();
$user->subscribeTo($primeMonthly);

...

$primeYearly = Plan::whereName('prime-yearly')->first();
$user->switchTo($primeYearly, immediately: false);

在上面的示例中,用户将保持其月度订阅,直到到期,然后开始年度计划。这在您不想处理部分退款时非常有用,因为您只能在当前付费计划到期时向用户收费。

在幕后,此调用将创建一个起始日期等于当前到期日期的订阅,因此它不会在那时影响您的应用程序。

此调用将触发 SubscriptionScheduled(Subscription $subscription) 事件。

续订

要续订订阅,只需调用 renew() 方法

$subscriber->subscription->renew();

此方法将触发 SubscriptionRenewed(Subscription $subscription) 事件。

它将根据当前日期计算新的到期日期。

已过期订阅

要检索已过期的订阅,您可以使用 lastSubscription 方法

$subscriber->lastSubscription();

此方法将返回用户的最后一个订阅,无论其状态如何,因此您可以获取已过期的订阅进行续订。

$subscriber->lastSubscription()->renew();

取消

在取消订阅时,请注意一点:它不会立即撤销访问权限。为了避免您需要处理任何类型的退款,我们保持订阅活跃,只是将其标记为已取消,因此您只需在将来不续订即可。如果您需要立即抑制订阅,请查看 suppress() 方法。

要取消订阅,请使用 cancel() 方法

$subscriber->subscription->cancel();

此方法将通过将列 canceled_at 填充当前时间戳来将订阅标记为已取消。

此方法将触发 SubscriptionCanceled(Subscription $subscription) 事件。

抑制

要抑制订阅(并立即撤销它),请使用 suppress() 方法

$subscriber->subscription->suppress();

此方法将通过将列 suppressed_at 填充当前时间戳来将订阅标记为已抑制。

此方法将触发 SubscriptionSuppressed(Subscription $subscription) 事件。

启动

要启动订阅,请使用 start() 方法

$subscriber->subscription->start(); // To start it immediately
$subscriber->subscription->start($startDate); // To determine when to start

当没有传递参数时,此方法将触发 SubscriptionStarted(Subscription $subscription) 事件,并且当提供的开始日期为未来时,也会触发 SubscriptionStarted(Subscription $subscription) 事件。

此方法将通过填充列 started_at 将订阅标记为已启动(或计划启动)。

功能消耗

要注册给定功能的消耗,只需调用 consume 方法并传递功能名称和消耗量(对于不可消耗的功能,无需提供)。

$subscriber->consume('deploy-minutes', 4.5);

此方法将检查功能是否可用,并在不可用时抛出异常:如果功能不可用于该计划,则抛出 OutOfBoundsException;如果功能可用,但费用不足以覆盖消耗,则抛出 OverflowException

此调用将触发 FeatureConsumed($subscriber, Feature $feature, FeatureConsumption $featureConsumption) 事件。

检查可用性

要检查功能是否可用于消耗,您可以使用以下方法之一

$subscriber->canConsume('deploy-minutes', 10);

要检查用户是否可以消耗一定数量的给定功能(它检查用户是否有权访问该功能以及他是否有足够的剩余费用)。

$subscriber->cantConsume('deploy-minutes', 10);

它调用底层的 canConsume() 方法并反转返回值。

$subscriber->hasFeature('deploy-minutes');

简单地检查用户是否有权访问给定的功能(而不查找其费用)。

$subscriber->missingFeature('deploy-minutes');

类似于 cantConsume,它返回 hasFeature 的反转。

功能券

券是允许您的订阅者获得功能费用的简单方法。当用户收到券时,他可以消耗其费用,就像在正常订阅中做的那样。券可以用于扩展基于订阅的常规系统(例如,您可以出售更多特定功能的费用)或甚至构建一个完全预付费的服务,用户只为他们想要使用的部分付费。

启用券

为了使用此功能,您必须在配置文件中启用券。首先,发布包配置

php artisan vendor:publish --tag="soulbscription-config"

最后,打开 subscription.php 文件并将 feature_tickets 标志设置为 true。就这样,您现在可以使用券了!

创建券

要创建券,您可以使用 giveTicketFor 方法。此方法期望功能名称、过期时间和可选的收费数量(对于不可消耗的功能,您可以忽略它)。

$subscriber->giveTicketFor('deploy-minutes', today()->addMonth(), 10);

此方法将触发 FeatureTicketCreated($subscriber, Feature $feature, FeatureTicket $featureTicket) 事件。

在上面的示例中,用户将获得额外的十分钟执行部署,直到下一个月。

不可消耗的功能

您可以为不可消耗的功能创建票据,这样您的订阅者将只可以在一定期限内访问它们。

class UserFeatureTrialController extends Controller
{
    public function store(FeatureTrialRequest $request, User $user)
    {
        $featureName = $request->input('feature_name');
        $expiration = today()->addDays($request->input('trial_days'));
        $user->giveTicketFor($featureName, $expiration);

        return redirect()->route('admin.users.show', $user);
    }
}

永久有效票据

您可以创建永久有效的票据,这样您的订阅者将永远可以访问它们。

$subscriber->giveTicketFor('deploy-minutes', null, 10);

当您的用户取消订阅时,别忘了删除这些票据。否则,他们将能够永久消耗费用。

测试

composer test

变更日志

请参阅变更日志,了解最近发生了哪些变化。

贡献

请参阅贡献指南以获取详细信息。

安全漏洞

请查阅我们的安全策略了解如何报告安全漏洞。

鸣谢

许可证

MIT许可证(MIT)。有关更多信息,请参阅许可证文件