Реалізація підписочної моделі оплати на веб-сайті
Підписочна модель — один з найбільш передбачуваних джерел доходу для онлайн-сервісів. Але технічна реалізація складніша за разові платежі: потрібно керувати життєвим циклом підписки, обробляти збої при повторних списаннях, забезпечувати grace period і коректно працювати з апгрейдами/даунгрейдами тарифу.
Модель даних
plans (
id, name, billing_period: monthly | yearly | weekly,
price, currency,
trial_days, features (jsonb),
is_active
)
subscriptions (
id, user_id, plan_id,
status: trialing | active | past_due | canceled | expired,
current_period_start, current_period_end,
trial_ends_at,
cancel_at_period_end (boolean),
canceled_at,
payment_method_id,
external_subscription_id (id у платіжній системі)
)
subscription_invoices (
id, subscription_id, amount, currency,
status: draft | open | paid | failed | void,
attempt_count, next_attempt_at,
paid_at, payment_id
)
Вибір платіжного провайдера
Stripe Billing — найкращий варіант для міжнародного SaaS. Вбудоване управління підписками, Smart Retries, Customer Portal з коробки. Stripe сам керує логікою retry при неудачних списаннях.
YooKassa + власна логіка — для російського ринку. YooKassa підтримує автоплатежі через збережені карти (рекурентні платежі). Логіку управління підпиской потрібно будувати самостійно.
CloudPayments — хороший варіант для РФ, є вбудовані підписки з Webhooks.
Життєвий цикл підписки
Реєстрація
↓
Trial (7/14/30 днів) → Auto-convert to Active
↓
Active → списання в кінці періоду → новий період
↓ ↓
Скасування Payment Failed
↓ ↓
Скасування в Past Due (grace period 3-7 днів)
кінці періоду ↓
↓ Retry (1, 3, 7 днів)
Expired ↓
Expired (після N неудачних спроб)
Автоматичні списання
При використанні Stripe: підписка створюється один раз, Stripe сам керує renewals. При використанні YooKassa — потрібен власний scheduler:
// Scheduled Job: кожну годину
$dueSubscriptions = Subscription::where('status', 'active')
->where('current_period_end', '<=', now())
->get();
foreach ($dueSubscriptions as $subscription) {
dispatch(new RenewSubscriptionJob($subscription));
}
RenewSubscriptionJob намагається провести списання через збережений payment method. При успіху — оновлює current_period_end. При неудачі — переводить у past_due і планує повторні спроби.
Grace Period та розумні повторні спроби
Після першого неудачного списання підписка переходить у past_due — користувач зберігає доступ, але отримує сповіщення. Повторні спроби:
- +1 день: перший retry
- +3 дні: другий retry з email-нагадуванням
- +7 днів: фінальна спроба, попередження про відключення
- +10 днів: статус
expired, доступ відозваний
Stripe Dunning Management робить це автоматично з ML-виділом часу retry.
Апгрейд і даунгрейд тарифу
Смена тарифу — нетривіальна задача. Пересчет суми виконується пропорційно залишку періоду:
- Апгрейд (на дорожчий тариф): негайно, доплата за залишене часу періоду
- Даунгрейд (на дешевший тариф): вступає в силу з початку наступного періоду, кредит урахується
$unusedDays = $subscription->daysRemainingInPeriod();
$creditAmount = $unusedDays * ($currentPlan->dailyPrice());
$chargeAmount = $unusedDays * ($newPlan->dailyPrice()) - $creditAmount;
Клієнтський портал і управління підпиской
Користувач повинен мати можливість:
- Переглядати поточний тариф і дату наступного списання
- Змінювати тариф (з розрахунком пропорції)
- Оновлювати платіжний метод (введення нової карти через hosted fields)
- Скасовувати підписку з поясненням причини (exit survey)
- Завантажувати рахунки-фактури
Stripe надає готовий Customer Portal — hosted-сторінку для управління. Для кастомного UI на YooKassa потрібно будувати своє.
Запобігання churn
Технічні рішення для зменшення відмови від підписок:
- Email за 3 і 7 днів до списання з нагадуванням суми
- Сповіщення про закінчення карти за 30 днів
- Можливість поставити підписку на паузу (замість скасування)
- Оффер при скасуванні: скидка 20% на наступний місяць
Аналітика підписок
Ключові метрики: MRR (Monthly Recurring Revenue), Churn Rate, LTV, Trial-to-Paid Conversion, Average Revenue Per User. Для розрахунку потрібні спеціалізовані запити по історичним даним підписок — не достатньо просто суммувати платежі.
Час розробки: 4–6 тижнів для повної системи з управлінням lifecycle, retry-логікою, клієнтським порталом і базовою аналітикою.







