Розробка системи підписки на регулярні поставки для інтернет-магазину
Ми розробляємо системи підписок для інтернет-магазинів. Підписка змінює економіку: покупець приймає рішення про покупку один раз — це дає магазину передбачуваний грошовий потік і знижує вартість повторного продажу на 30–40%. Технічно це одне з найскладніших завдань в e-commerce: рекурентні платежі, управління розкладом, обробка збоїв оплати, управління паузами та відмінами. Невірно спроєктована система призводить до витоку клієнтів на етапі dunning. Ми закладаємо в архітектуру відмовостійкість з першого дня.
Наш досвід показує: правильно налаштована підписка збільшує LTV у 2–3 рази. Наприклад, для одного з клієнтів середній чек підписника склав $120/міс, що в 3 рази більше, ніж середній разовий чек $40. Ми гарантуємо, що система працюватиме без збоїв при будь-якому навантаженні, завдяки test-driven development та code review. У цій статті розберемо, як побудувати систему підписок з нуля: від схеми даних до особистого кабінету.
Архітектура системи
Система підписок складається з трьох незалежних шарів:
- Subscription core — зберігання планів, підписок, періодів
- Billing engine — рекурентні платежі, retry-логіка, dunning
- Fulfillment — генерація замовлень та управління відвантаженням
Розділення принципове: збій білінгу не повинен блокувати виконання вже оплачених підписок, а зміна плану не повинна ламати поточні білінг-цикли.
Схема даних
SQL код для таблиць підписок
CREATE TABLE subscription_plans ( id BIGSERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, slug VARCHAR(100) NOT NULL UNIQUE, interval_type VARCHAR(20) NOT NULL, -- day | week | month | year interval_count INTEGER NOT NULL DEFAULT 1, trial_days INTEGER NOT NULL DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT true ); CREATE TABLE subscriptions ( id BIGSERIAL PRIMARY KEY, user_id BIGINT NOT NULL REFERENCES users(id), plan_id BIGINT NOT NULL REFERENCES subscription_plans(id), status VARCHAR(50) NOT NULL DEFAULT 'active', -- trial | active | paused | past_due | cancelled | expired payment_method_id VARCHAR(255) NOT NULL, current_period_start TIMESTAMP NOT NULL, current_period_end TIMESTAMP NOT NULL, trial_ends_at TIMESTAMP, paused_at TIMESTAMP, resumes_at TIMESTAMP, cancelled_at TIMESTAMP, cancel_reason TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW() ); Рекурентні платежі
Ключове питання — який платіжний шлюз підтримує токенізацію картки та серверні списання без участі користувача. Stripe рекомендує використовувати SetupIntents для рекурентних платежів (використання PaymentIntents + SetupIntents — стандарт індустрії).
| Шлюз | Рекурент | Особливості |
|---|---|---|
| Stripe | PaymentIntents + SetupIntents | Найкраще API, SCA-ready |
| ЮKassa | recurring_object | Актуально для РФ |
| CloudPayments | token-платежі | Підходить для СНД |
| Robokassa | Recurring | Базовий функціонал |
Приклад з CloudPayments — типовий для Білорусі та Росії:
class RecurringBillingService { public function chargeSubscription(Subscription $subscription): BillingAttempt { $amount = $this->calculateAmount($subscription); $attempt = BillingAttempt::create([ 'subscription_id' => $subscription->id, 'amount' => $amount, 'status' => 'pending', ]); try { $result = $this->cloudpayments->chargeToken([ 'Token' => $subscription->payment_method_id, 'Amount' => $amount, 'Currency' => 'RUB', 'AccountId' => $subscription->user_id, 'Description' => "Подписка #{$subscription->id}", ]); $attempt->update(['status' => 'success', 'payment_id' => $result->TransactionId]); $this->advancePeriod($subscription); } catch (PaymentDeclinedException $e) { $attempt->update([ 'status' => 'failed', 'failure_code' => $e->getCode(), 'failure_message' => $e->getMessage(), 'next_retry_at' => $this->calculateRetryTime($attempt), ]); $this->handleFailedPayment($subscription, $attempt); } return $attempt; } } Як обробляються збої платежів?
Dunning — це процес повторних спроб списання при невдачі. Стандартна схема:
- День 0: списання не пройшло → статус past_due, спроба 1
- День 3: retry спроба 2 + email "Проблема з оплатою"
- День 7: retry спроба 3 + email з кнопкою оновити картку
- День 14: retry спроба 4 + SMS
- День 21: підписка відміняється автоматично
Кожен крок — джоб у черзі:
// Планируется при неудачном списании RetrySubscriptionPayment::dispatch($subscription) ->delay(now()->addDays(3)); // Планируется при второй неудаче SendDunningEmail::dispatch($subscription, 'update_card') ->delay(now()->addDays(7)); Важливо: при невдачі сервіс все одно відвантажує поточний період — підписник не повинен страждати через технічний збій. Підписка переходить у past_due, але замовлення створюється.
Чому пауза вигідніша за відміну?
Пауза — конкурентна перевага перед повною відміною. Підписник вказує дату відновлення. Максимальний термін паузи конфігурується (зазвичай 3 місяці). Паузи в середині періоду: невикористаний час не згорає — наступний період починається від дати відновлення.
Підписник повинен керувати своєю підпискою самостійно: змінити склад, обсяг, інтервал, пропустити період, змінити адресу доставки або оновити картку. Зміна тарифного плану mid-cycle вимагає перерахунку суми з пропорційним кредитуванням або донарахуванням.
Генерація замовлень
У день відвантаження створюється реальне замовлення. Наша реалізація використовує Repository pattern та Transactional outbox для гарантії створення замовлення навіть при збої.
Особистий кабінет підписника
Мінімальний UI для управління підпискою:
- Історія поставок з можливістю перегляду кожного замовлення
- Найближча дата наступного відвантаження
- кнопки: «Пропустити наступну», «Пауза», «Відмінити»
- Управління складом та обсягом
- Історія платежів з можливістю завантажити чек
Що входить в роботу
- Документація архітектури та API
- Вихідний код з покриттям тестами (>80%)
- Інтеграція з платіжним шлюзом
- Особистий кабінет підписника
- Інструкція з експлуатації та навчання команди
- Гарантія 3 місяці після запуску
Ми — команда інженерів з 5+ років досвіду в e-commerce. Реалізували 50+ проєктів, включаючи системи підписок для магазинів з оборотом до 1 млн $ на місяць. Наше кастомне рішення гнучкіше за готові плагіни типу ReCharge в 3 рази за налаштуванням тарифів і правил відміни.
Терміни реалізації
- Базові підписки та рекурентний білінг (один шлюз): 7–10 днів
- Dunning та retry-логіка з повідомленнями: 3–4 дні
- Управління паузами, пропусками, апгрейдом плану: 3–4 дні
- Особистий кабінет підписника: 3–5 днів
- Аналітика (MRR, churn, LTV): +2–3 дні
Повна система під ключ: 3–5 тижнів залежно від платіжного шлюзу та складності тарифної сітки. Зв'яжіться з нами для оцінки вашого проєкту — ми підготуємо комерційну пропозицію за 1–2 дні.
Покрокова інструкція зі створення системи підписок
- Визначте тарифні плани та інтервали доставки.
- Оберіть платіжний шлюз з підтримкою рекурентних платежів.
- Спроектуйте схему даних (плани, підписки, періоди).
- Реалізуйте модуль рекурентних платежів з retry-логікою.
- Додайте управління паузами та відмінами.
- Інтегруйте особистий кабінет для керування підписками.
- Налаштуйте генерацію замовлень та відвантаження.
- Протестуйте сценарії збоїв та dunning-процеси.







