Subscriptionify - це Laravel-пакет, створений Rasel Islam Rafi, для моделювання планів підписки та функцій, які вони відкривають. Якщо Laravel Cashier обгортає API виставлення рахунків платіжного провайдера, то Subscriptionify залишається незалежним від шлюзів: він відстежує плани, квоти функцій та використання у вашій власній базі даних, залишаючи питання оплати на ваш розсуд. Це робить його придатним для додатків, які виставляють рахунки через провайдера, не охопленого Cashier, стягують плату з попередньо оплаченого балансу або надають доступ взагалі без оплати. Пакет вимагає PHP 8.2 і підтримує Laravel 11, 12 та 13.
Встановлення та налаштування
Встановлення публікує конфігураційний файл та міграції, які створюють шість таблиць для планів, функцій, зв'язку план/функція, підписок, записів використання та прямих надань функцій:
composer require revoltify/subscriptionify
php artisan vendor:publish --tag=subscriptionify-config
php artisan vendor:publish --tag=subscriptionify-migrations
php artisan migrate
Будь-яка модель може стати платоспроможною, реалізувавши контракт Subscribable та додавши трейт InteractsWithSubscriptions. Це дозволяє прикріплювати підписки до User, Organisation або Workspace, залежно від структури вашого додатка:
use Revoltify\Subscriptionify\Concerns\InteractsWithSubscriptions;
use Revoltify\Subscriptionify\Contracts\Subscribable;
class Workspace extends Model implements Subscribable
{
use InteractsWithSubscriptions;
}
Чотири типи функцій для різних патернів квот
Основна ідея полягає в тому, що не всі функції поводяться однаково. План може обмежувати доступ до можливості, вимірювати вичерпний місячний ліміт або обмежувати загальну суму. Subscriptionify моделює це як чотири окремі типи функцій, кожен зі своїми правилами споживання:
- Toggle - простий перемикач увімкнено/вимкнено, використовується для можливостей, які план або включає, або ні.
- Consumable - вичерпна квота, яка скидається за розкладом, наприклад, щомісячний ліміт викликів API.
- Limit - жорсткий ліміт на загальну суму, який можна звільнити знову, наприклад, активні проєкти або місця, де видалення одного звільняє слот.
- Metered - відстежує споживання з оплатою за використання без обмежень, стягуючи плату за одиницю.
Функції створюються один раз, потім прикріплюються до планів через pivot-таблицю, яка містить виділення для цього плану:
use Revoltify\Subscriptionify\Models\Feature;
use Revoltify\Subscriptionify\Enums\FeatureType;
$reports = Feature::create([
'name' => 'Exported Reports',
'slug' => 'reports',
'type' => FeatureType::Consumable,
]);
$plan->features()->attach($reports, [
'value' => 500, // 0 означає необмежено
'unit_price' => '0.02000000', // ціна за одиницю після перевищення квоти
'reset_period' => 1,
'reset_interval' => 'month',
]);
Відстеження використання на моделі з підпискою
Після оформлення підписки методи використання живуть безпосередньо на моделі. Ви перевіряєте доступ, тестуєте, чи доступна кількість одиниць, і записуєте споживання без звернення до записів підписки або pivot:
$workspace->subscribe($plan);
$workspace->hasFeature('reports'); // чи доступна функція взагалі?
$workspace->canConsume('reports', 10); // чи доступні 10 одиниць прямо зараз?
$workspace->consume('reports', 10); // записати використання, викидає виняток при перевищенні квоти
$workspace->tryConsume('reports', 10); // те саме, але повертає false замість винятку
$workspace->remainingUsage('reports'); // одиниць залишилося в поточному періоді
Для функцій типу Limit метод release() повертає одиниці назад, щоб звільнити слот, що відрізняє ліміт від consumable, який тільки зменшується:
$workspace->consume('projects', 1); // створення проєкту
$workspace->release('projects', 1); // видалення звільняє слот
Прямі надання незалежно від плану
Плани - не єдиний спосіб виділення функцій. Метод grantFeature() призначає функцію безпосередньо підписнику і надає її поверх того, що забезпечує план. Якщо план включає 500 звітів, а ви надаєте ще 1000, доступна квота стає 1500. Це охоплює разові поповнення, рекламні бонуси та коригування для окремих клієнтів без створення індивідуального плану для кожного випадку:
$workspace->grantFeature('reports', value: 1_000);
// З автоматичним скиданням за власним розкладом
use Revoltify\Subscriptionify\Enums\Interval;
$workspace->grantFeature('reports', value: 100, resetPeriod: 1, resetInterval: Interval::Month);
// Видалити надання; виділення плану все ще діє
$workspace->revokeFeature('reports');
Опціональне перевищення та тарифікація за використанням
Виставлення рахунків є опціональним. Реалізуйте контракт HasFunds поряд з Subscribable, і пакет починає стягувати плату з балансу, яким ви керуєте. З HasFunds функції consumable та limit стягують плату за перевищення після вичерпання квоти (за умови встановленої unit_price), а metered-функції стягують плату за одиницю з першого використання:
use Revoltify\Subscriptionify\Contracts\HasFunds;
class Workspace extends Model implements Subscribable, HasFunds
{
use InteractsWithSubscriptions;
public function getBalance(): string
{
return $this->balance;
}
public function hasSufficientFunds(string $amount): bool
{
return bccomp($this->balance, $amount, 8) >= 0;
}
public function deductFunds(string $amount, string $description): void
{
$this->update(['balance' => bcsub($this->balance, $amount, 8)]);
}
}
Оскільки суми передаються як рядки і порівнюються з bccomp, математика виконується з довільною точністю, а не з плаваючою комою. Без HasFunds ті самі функції повертаються до жорстких лімітів, які викидають виняток при перевищенні, тому ви можете спочатку впровадити контроль квот, а виставлення рахунків додати пізніше без переписування коду споживання.
Обмеження доступу за допомогою middleware та Blade
Для захисту маршрутів зареєстровано три псевдоніми middleware, які повертають 403 при невдачі:
Route::middleware('subscribed')->group(function () {
// будь-яка активна підписка або пробна
});
Route::middleware('plan:pro')->group(function () {
// конкретний план
});
Route::middleware('feature:reports')->group(function () {
// конкретна функція
});
Відповідні директиви Blade обмежують контент у виглядах, включаючи стани для пробних версій, безкоштовних планів та пільгового періоду після скасування:
@feature('custom-branding')
{{-- показується лише коли функція доступна --}}
@endfeature
@onTrial
{{-- банер зворотного відліку пробного періоду --}}
@endonTrial
За замовчуванням обидва визначають підписника з auth()->user(), що можна змінити за допомогою Subscriptionify::resolveSubscribableUsing(), якщо ваша платоспроможна модель не є автентифікованим користувачем.
Життєвий цикл, події та заплановане закінчення терміну дії
Підписки мають звичайні методи життєвого циклу (changePlan(), renew(), cancel(), cancelNow() та resume() протягом пільгового періоду), і кожен перехід відправляє подію, таку як SubscriptionCreated, SubscriptionCancelled або FeatureConsumed, на які можуть реагувати ваші власні слухачі. Включена artisan-команда переводить активні підписки після їхньої дати закінчення у статус прострочених:
use Illuminate\Support\Facades\Schedule;
Schedule::command('subscriptionify:expire-overdue')->hourly();
Щоб ознайомитися з повним списком функцій, параметрами конфігурації та хуками налаштування, відвідайте пакет на GitHub.