Реалізація продажу SaaS-доступу через сайт
Продаж SaaS-підписок — технічно складніша задача, ніж продаж разових товарів. Потрібні: тарифні плани з різними лімітами, управління підписками (апгрейд, даунгрейд, відмена), біллінг по циклам, пробні періоди.
Архітектура біллінгу
Не реалізовуйте біллінг самостійно — використовуйте Stripe Billing, Paddle або Chargebee. Вони беруть на себе: повторюючі платежі, невдалі трансакції, dunning-механіки, податки по регіонам.
Інтеграція зі Stripe Billing
class SubscriptionService
{
public function createSubscription(User $user, string $priceId, array $options = []): Subscription
{
// Створюємо Stripe Customer якщо немає
if (!$user->stripe_customer_id) {
$customer = $this->stripe->customers->create([
'email' => $user->email,
'metadata' => ['user_id' => $user->id],
]);
$user->update(['stripe_customer_id' => $customer->id]);
}
$subscriptionData = [
'customer' => $user->stripe_customer_id,
'items' => [['price' => $priceId]],
'trial_period_days' => $options['trial_days'] ?? 0,
'payment_behavior' => 'default_incomplete',
'expand' => ['latest_invoice.payment_intent'],
];
$stripeSubscription = $this->stripe->subscriptions->create($subscriptionData);
return Subscription::create([
'user_id' => $user->id,
'stripe_subscription_id' => $stripeSubscription->id,
'plan' => $options['plan'],
'status' => $stripeSubscription->status,
'trial_ends_at' => $stripeSubscription->trial_end
? Carbon::createFromTimestamp($stripeSubscription->trial_end)
: null,
'current_period_end' => Carbon::createFromTimestamp($stripeSubscription->current_period_end),
]);
}
public function cancel(Subscription $subscription, bool $immediately = false): void
{
if ($immediately) {
$this->stripe->subscriptions->cancel($subscription->stripe_subscription_id);
} else {
// Відмена в кінці періоду
$this->stripe->subscriptions->update($subscription->stripe_subscription_id, [
'cancel_at_period_end' => true,
]);
}
}
}
Тарифні плани з обмеженнями
// Таблиця лімітів планів
class PlanLimits
{
private array $limits = [
'free' => ['projects' => 1, 'storage_gb' => 1, 'api_calls_month' => 1000],
'starter' => ['projects' => 5, 'storage_gb' => 10, 'api_calls_month' => 10000],
'pro' => ['projects' => 20, 'storage_gb' => 50, 'api_calls_month' => 100000],
'enterprise' => ['projects' => -1, 'storage_gb' => -1, 'api_calls_month' => -1], // -1 = без обмежень
];
public function check(string $plan, string $feature, mixed $currentUsage): bool
{
$limit = $this->limits[$plan][$feature] ?? 0;
if ($limit === -1) return true; // без обмежень
return $currentUsage < $limit;
}
}
// У контроллері
public function createProject(Request $request)
{
$user = auth()->user();
$plan = $user->subscription->plan;
$currentProjects = $user->projects()->count();
if (!app(PlanLimits::class)->check($plan, 'projects', $currentProjects)) {
return response()->json([
'error' => 'Перевищено лімітом проектів для вашого тарифу',
'upgrade_url' => route('billing.upgrade'),
], 402);
}
// Створюємо проект
}
Webhook обробник подій біллінгу
Route::post('/webhooks/stripe', function (Request $request) {
$event = \Stripe\Webhook::constructEvent(
$request->getContent(),
$request->header('Stripe-Signature'),
config('services.stripe.webhook_secret')
);
match($event->type) {
'customer.subscription.created' =>
HandleSubscriptionCreated::dispatch($event->data->object),
'customer.subscription.updated' =>
HandleSubscriptionUpdated::dispatch($event->data->object),
'customer.subscription.deleted' =>
HandleSubscriptionCancelled::dispatch($event->data->object),
'invoice.payment_failed' =>
HandlePaymentFailed::dispatch($event->data->object),
'invoice.payment_succeeded' =>
HandlePaymentSucceeded::dispatch($event->data->object),
default => null,
};
return response('ok');
});
Сроки
SaaS-біллінг зі Stripe, тарифними планами та управлінням підписками: 14–20 робочих днів.







