Tithe 为您的 Laravel 应用程序提供订阅计费服务

v1.0.2 2023-04-27 10:05 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

这里应该放置您的描述。请将其限制在一两段之内。考虑添加一个小示例。

安装

./tailwindcss -i ./resources/css/tithe.css -o ./resources/dist/tithe.css --watch

您可以通过 composer 安装此包

composer require starfolksoftware/tithe

您可以使用以下命令发布配置并运行迁移

php artisan tithe:install
php artisan migrate

这是已发布配置文件的内容

return [
];

可选地,您可以使用以下命令发布视图

php artisan vendor:publish --tag="tithe-views"

配置

要禁用迁移,请在该服务提供程序中添加以下内容

Redo::ignoreMigrations();

用法

要开始使用它,只需将给定的特质添加到您的 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\PeriodicityTypeEnum;
use LucasDotVin\Soulbscription\Models\Feature;

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

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

通过说 deploy-minutes 是一个可消耗功能,我们告诉用户他们可以使用有限次数(或直到给定数量)。另一方面,通过传递 PeriodicityTypeEnum::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 Seeder
{
    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');
    }
}

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

创建计划

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

<?php

namespace Database\Seeders;

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

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

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

这里的内容相当简单,但值得强调的是:通过接收上述周期性选项,这两个计划被定义为按月。

宽限期

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

$gold = Plan::create([
    'name'             => 'gold',
    'periodicity_type' => PeriodicityTypeEnum::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="tithe-config"

最后,打开tithe.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)。有关更多信息,请参阅许可文件