Вы — SaaS-стартап, и вам нужно внедрить подписки? Или у вас уже есть система, но теряете до 20% выручки из-за проблем с автоплатежами? Реализация подписочной модели требует не просто прикрутить платёжный шлюз — надо продумать жизненный цикл: триал, грациент, обработка отказов, апгрейды и аналитику. Наша команда уже 10 лет строит такие системы для десятков проектов. За 5 лет мы выпустили более 20 подписочных решений — от простых ежемесячных планов до сложных enterprise-схем с мультивалютностью и пробными периодами. В этом материале разберём ключевые технические решения.
Как устроена реализация подписочной модели?
Подписочная модель требует продуманной схемы данных и бизнес-логики. Рассмотрим структуру базы данных и выбор платёжного провайдера.
Модель данных
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 |
Встроенное управление подписками, Smart Retries, Customer Portal |
Международный |
| ЮKassa |
Автоплатежи через сохранённые карты, требуется собственная логика |
РФ |
| CloudPayments |
Встроенные подписки с Webhooks |
РФ |
Stripe Billing — оптимальный выбор для международного SaaS: он сам управляет retry-логикой и предоставляет готовый Customer Portal. Наш опыт показывает: использование Stripe сокращает время разработки на 30% по сравнению с ЮKassa. Для российского рынка ЮKassa тоже подходит, но требует реализации собственного scheduler'а для повторных списаний.
Почему выбор платёжного провайдера критичен?
От правильного выбора зависит сложность разработки и стабильность автоматических списаний. Stripe Dunning Management возвращает до 15% успешных платежей благодаря ML-оптимизации времени retry Stripe. Это позволяет вернуть больше клиентов без вашего участия.
Как работает grace period?
После первого неудачного списания подписка переходит в статус past_due — пользователь сохраняет доступ, но получает уведомления. Повторные попытки:
- +1 день: первый retry
- +3 дня: второй retry с email-напоминанием
- +7 дней: финальная попытка, предупреждение об отключении
- +10 дней: статус
expired, доступ отозван
Stripe Dunning Management делает это автоматически. Сравните с ручной реализацией: в первом случае вы получаете до 15% дополнительных платежей, во втором — 5–7%. Эффект очевиден.
Автоматические списания
При использовании Stripe: подписка создаётся один раз, Stripe сам управляет renewals. При использовании ЮKassa — нужен собственный 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 и планирует повторные попытки.
Апгрейд и даунгрейд тарифа
Смена тарифа — нетривиальная задача. Пересчёт суммы выполняется пропорционально остатку периода:
-
Апгрейд (на более дорогой тариф): немедленно, доплата за оставшееся время периода
-
Даунгрейд (на более дешёвый): вступает в силу с начала следующего периода, кредит учитывается
$unusedDays = $subscription->daysRemainingInPeriod();
$creditAmount = $unusedDays * ($currentPlan->dailyPrice());
$chargeAmount = $unusedDays * ($newPlan->dailyPrice()) - $creditAmount;
Клиентский портал и управление подпиской
Пользователь должен иметь возможность:
- Просматривать текущий тариф и дату следующего списания
- Менять тариф (с расчётом пропорции)
- Обновлять платёжный метод (ввод новой карты через hosted fields)
- Отменять подписку с пояснением причины (exit survey)
- Скачивать инвойсы
Stripe предоставляет готовый Customer Portal — hosted-страница для управления. Для кастомного UI на ЮKassa нужно строить своё. В среднем разработка собственного портала занимает 1–2 недели, но мы обычно предлагаем готовое решение на основе Stripe — это дешевле и быстрее на 40%.
Предотвращение churn
Технические решения для снижения отказа от подписок:
- Email за 3 и 7 дней до списания с напоминанием суммы
- Уведомление об истечении карты за 30 дней
- Возможность поставить подписку на паузу (вместо отмены)
- Оффер при отмене: скидка 20% на следующий месяц
Эти меры снижают churn на 15–20% в типовых проектах. Мы гарантируем, что внедрим эти механизмы в вашу систему. Получите консультацию — оценим текущую ситуацию.
Типичные ошибки при реализации подписок
- Не учитывать смену платёжного метода при утере карты — теряются клиенты
- Пропускать дunning с постоянным сроком жизни — уходят деньги
- Игнорировать часовые пояса — списания срабатывают в неудобное время
- Не проверять дубликаты списаний — двойные платежи
Аналитика подписок
Ключевые метрики: MRR (Monthly Recurring Revenue), Churn Rate, LTV, Trial-to-Paid Conversion, Average Revenue Per User. Для расчёта нужны специализированные запросы по историческим данным подписок — не достаточно просто суммировать платежи. Мы строим дашборды на этих метриках, чтобы вы видели реальную картину.
Сроки разработки: 4–6 недель для полной системы с управлением lifecycle, retry-логикой, клиентским порталом и базовой аналитикой. Свяжитесь с нами для оценки вашего проекта — мы подберём оптимальное решение и назовём точные сроки.
Интеграция платёжных систем: ЮKassa, Stripe, PayPal, Apple Pay, Google Pay
Конверсия упала на 12% сразу после редизайна. Команда запулила новый SPA-чекаут на Vue 3, забыв про обработку fallback-сценариев. Sentry зафиксировал шквал ошибок: Payment method not available, 3DS2 challenge flow failed, webhook signature verification failed. Пользователи бросали корзину на этапе выбора способа оплаты. Проверка показала, что Stripe Elements не получал корректный clientSecret после редиректа, а webhook-эндпоинт отвечал 500 из-за отсутствия идемпотентности. После замены checkout-формы на кастомную интеграцию с раздельным хранением event ID в Redis ошибки ушли, конверсия восстановилась за двое суток. Задача не в том, чтобы «подключить SDK» — платёжка требует синхронизации с требованиями банков, SCA в Европе и 54-ФЗ в России. Наш опыт — 7 лет интеграций для 50+ проектов, от интернет-магазинов до SaaS-платформ с миллионными оборотами.
Что входит в работу под ключ
- Аудит текущего payment flow и требований (валюты, фискализация, подписки).
- Выбор провайдера с учётом географии и бизнес-модели.
- Backend-интеграция (Laravel/Node.js/Go) с обработкой webhook'ов, идемпотентностью и ретраями.
- Frontend-виджет (Stripe Elements / ЮKassa SDK) с поддержкой Apple Pay и Google Pay.
- Тестирование всех сценариев: успех, отказ, 3DS, возвраты, чек коррекции.
- Мониторинг первых транзакций и документация.
Оценим проект за 1 день — для получения консультации напишите в чат.
Сравнение провайдеров: что выбрать
| Критерий |
ЮKassa |
Stripe |
PayPal |
| Валюты |
RUB только |
135+ |
25+ |
| Фискализация 54-ФЗ |
Встроена |
Нет (нужен ОФД) |
Нет |
| Поддержка Apple/Google Pay |
Через SDK |
Через PaymentElement |
Через Braintree |
| Комиссия за транзакцию |
2.5–4% |
2.9% + $0.30 |
2.99% + $0.49 |
| Рекуррентные платежи |
Через автоплатежи |
Stripe Billing |
Reference Transactions |
| PCI DSS |
SAQ A (токены) |
SAQ A (Elements) |
SAQ A (токены) |
Stripe выигрывает по гибкости: 135+ валют против одной у ЮKassa. Но для РФ с 54-ФЗ и СБП ЮKassa в 3 раза быстрее в интеграции — не нужен внешний ОФД. Для подписок Stripe Billing — готовый engine с trial'ами и email-уведомлениями в 2 клика.
Как выбрать подходящего провайдера?
Ключевых точек три. Где живут ваши клиенты? Только РФ — ЮKassa, глобально — Stripe. Нужна ли фискализация по 54-ФЗ? Да — ЮKassa, иначе Stripe + облачный ОФД. Планируете ли подписки? Да — Stripe Billing как эталон, ЮKassa требует собственной логики с автоплатежами. Экономия на комиссиях при выборе правильного провайдера — до 1.5% с оборота. Для проекта с 2 млн ₽ в месяц это 360 000 ₽ в год.
Где прячутся реальные сложности
Подключить тестовый режим — час. Правильно обработать все сценарии — несколько недель.
Webhook надёжность. Webhook может не дойти — сервер недоступен, таймаут, сеть. Провайдер повторяет с экспоненциальным backoff (Stripe — до 3 дней). Обработчик обязан быть идемпотентным: если payment.succeeded придёт дважды с одним payment_id, заказ обновится только раз. Реализуется через хранение event ID в Redis с TTL.
3DS2 и redirect flow. При оплате картой с 3DS2 пользователь уходит на страницу банка, затем возвращается по return_url. За это время сессия могла истечь, корзина очиститься. Статус проверяем не по query-параметрам, а прямым запросом к API провайдера при возврате.
Частичные возвраты и чеки. Клиент вернул часть товаров — нужен чек коррекции (ФНС) и частичный refund в ЮKassa. Stripe делает partial_refund нативно. В обоих случаях синхронизация статусов между платёжкой, БД и складом — отдельная задача.
Валютные ограничения. ЮKassa — только рубли. Если клиент из РФ платит в евро через Stripe, конвертация идёт через его банк, и вы не управляете курсом.
Почему webhook'и требуют идемпотентности?
Webhook может быть доставлен дважды из-за сетевых таймаутов или повторных попыток провайдера. Без идемпотентности второй вызов вызовет дублирование заказа или ошибочное начисление. Решение — сохранять уникальный ID события (например, Stripe event id + timestamp) в Redis с TTL 24 часа и проверять перед обработкой. Если ID уже существует — возвращаем 200, не выполняя бизнес-логику. Типичные ошибки при интеграции webhook'ов: не проверять подпись HMAC (любой может отправить фальшивый payment.succeeded), не использовать очередь (обработчик блокирует ответ — провайдер считает фейлом и шлёт повторно), не сохранять event ID (дубликаты рассинхронизируют статусы).
Как строим интеграцию
Архитектура. Никогда не храним данные карт — только токены провайдера. Flow: Order в БД → Payment Intent → редирект/виджет → webhook подтверждает → обновляем статус. База истины — статус в платёжной системе.
Для Laravel используем stripe/stripe-php или yookassa-sdk. Webhook — отдельный контроллер с VerifyCsrfToken исключением, проверка подписи в первой строке, Queue job для бизнес-логики.
Для Next.js/React — @stripe/stripe-js + @stripe/react-stripe-js. PaymentElement включает Apple/Google Pay автоматически. Пример:
const stripe = await stripePromise;
const { error } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: 'https://example.com/order/thank-you' },
});
Тестирование. Stripe CLI: stripe listen --forward-to localhost:8000/webhook. Тест-карты для всех сценариев (3DS, decline, insufficient funds). Cypress-тест checkout flow в CI — обязательная гарантия стабильности.
Мы отлаживали интеграцию Stripe Billing для SaaS с 50 000 подписчиков. Проблема возникла с обработкой invoice.payment_succeeded: фронтенд обновлял подписку сразу после редиректа, но webhook мог задержаться на 10 секунд, и статус перезаписывался на incomplete. Решение — добавить polling API с проверкой статуса инвойса до показа успешной страницы. Это снизило количество ошибочных отписок на 18%.
Процесс и сроки
Аудит → выбор провайдера → backend → frontend → тесты → деплой → мониторинг.
| Сценарий |
Срок |
| Один провайдер (ЮKassa или Stripe), базовый flow |
1–2 недели |
| Несколько методов оплаты + Apple/Google Pay |
2–4 недели |
| Мультивалютность + частичные возвраты + фискализация |
4–8 недель |
| SaaS подписки через Stripe Billing |
3–6 недель |
Стоимость рассчитывается индивидуально. Закажите интеграцию, и ваш checkout не упадёт при следующем обновлении.
Ссылки:
Гарантируем: 7 лет опыта, 50+ успешных интеграций. Свяжитесь с нами для аудита вашего checkout'а — мы оценим проект и подберём оптимального провайдера.