Розробка системи підписок (subscription) на 1С-Бітрикс
Система підписок — це модель, при якій клієнт платить регулярно (щомісячно, щоквартально, щорічно) і отримує доступ до сервісу або товарів. 1С-Бітрикс не має вбудованого механізму подписок з рекурентними платежами — його потрібно будувати на базі модулів sale, catalog та кастомного коду.
Типи підписок у контексті Бітрикс
Перед проектуванням важливо зафіксувати бізнес-модель за типом подписки:
| Тип | Опис | Технічна реалізація |
|---|---|---|
| Доступ до контенту | Платний розділ, закриті матеріали | Групи користувачів + обмеження доступу |
| Товарна подписка | Регулярна доставка товарів | Автоматичне створення замовлень |
| Сервісна подписка | SaaS, ліцензія, техпідтримка | Статус аккаунту + автопродовження |
Усі три типи мають спільне: періодичне списання грошей та управління статусом доступу.
Архітектура зберігання даних
Ядро системи — таблиця підписок. Створюється через ORM Бітрикс (успадкування від \Bitrix\Main\ORM\Data\DataManager):
class SubscriptionTable extends DataManager
{
public static function getTableName(): string
{
return 'b_local_subscription';
}
public static function getMap(): array
{
return [
new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
new IntegerField('USER_ID', ['required' => true]),
new IntegerField('PLAN_ID', ['required' => true]),
new EnumField('STATUS', ['values' => ['TRIAL', 'ACTIVE', 'PAST_DUE', 'CANCELLED', 'EXPIRED']]),
new DatetimeField('CURRENT_PERIOD_START'),
new DatetimeField('CURRENT_PERIOD_END'),
new DatetimeField('TRIAL_END'),
new StringField('PAYMENT_TOKEN'), // Токен рекурентної оплати
new IntegerField('PAY_SYSTEM_ID'),
new StringField('CANCEL_REASON'),
new DatetimeField('CANCELLED_AT'),
new DatetimeField('CREATED_AT'),
];
}
}
Таблиця планів подписки:
class SubscriptionPlanTable extends DataManager
{
// ID, NAME, PRICE, CURRENCY, PERIOD_DAYS, TRIAL_DAYS,
// IBLOCK_SECTION_IDS (доступні розділи), FEATURES (JSON)
}
Управління доступом за подпискою
Для подписок типу «контент» доступ реалізується через групи користувачів Бітрикс. Кожному плану відповідає група (b_group). При активації подписки — додати користувача в групу:
CUser::SetUserGroup($userId, array_merge(
CUser::GetUserGroup($userId),
[$plan->getGroupId()]
));
При закінченні або скасуванні — убрати з групи. Доступ до розділів обмежується правами доступу інфоблоків або компонентів.
Рекурентні платежі
Це найскладніша частина. Рекурент вимагає від платіжної системи зберігання методу оплати (карти) та автоматичного списання за запитом. Підтримують: ЮKassa (Яндекс.Касса), CloudPayments, Robokassa (рекурент), Stripe (якщо працюєте з міжнародними клієнтами).
Схема роботи:
-
Перший платіж: звичайна оплата через API платіжної системи. У відповіді —
payment_method_idабо токен збереженого методу. -
Зберегти токен: записати в
SubscriptionTable.PAYMENT_TOKEN. -
Автоспісання: при наступленні
CURRENT_PERIOD_END— викликати API платіжної системи з токеном:
// Приклад для ЮKassa
$payment = new \YooKassa\Client();
$payment->setAuth($shopId, $secretKey);
$response = $payment->createPayment([
'amount' => ['value' => $plan->getPrice(), 'currency' => 'RUB'],
'payment_method_id' => $subscription->getPaymentToken(),
'capture' => true,
'description' => 'Подписка ' . $plan->getName() . ' #' . $subscription->getId(),
]);
-
Обробка результату: успіх → оновити
CURRENT_PERIOD_START,CURRENT_PERIOD_END, зберегти новий статусACTIVE. Невдача → статусPAST_DUE, сповістити клієнта, повторити через 24/48 годин.
Пробний період
При створенні подписки з пробою:
$trialEnd = (new DateTime())->modify('+' . $plan->getTrialDays() . ' days');
SubscriptionTable::add([
'USER_ID' => $userId,
'PLAN_ID' => $planId,
'STATUS' => 'TRIAL',
'TRIAL_END' => $trialEnd,
'CURRENT_PERIOD_END' => $trialEnd,
]);
Агент за 1 день до закінчення пробу — сповістити користувача. У день закінчення — спроба першого списання. Якщо карта не привʻязана — перевести в статус EXPIRED.
Агент обробки подписок
Щоденний cron-скрипт:
// Знайти подписки, що потребують продовження
$expiring = SubscriptionTable::getList([
'filter' => [
'STATUS' => ['ACTIVE', 'PAST_DUE'],
'<=CURRENT_PERIOD_END' => new DateTime(),
]
])->fetchAll();
foreach ($expiring as $sub) {
try {
$result = chargeSubscription($sub);
if ($result->isSuccess()) {
SubscriptionTable::update($sub['ID'], [
'STATUS' => 'ACTIVE',
'CURRENT_PERIOD_START' => new DateTime(),
'CURRENT_PERIOD_END' => (new DateTime())->modify('+' . $planPeriodDays . ' days'),
]);
} else {
handlePaymentFailure($sub);
}
} catch (\Exception $e) {
logError($e, $sub);
}
}
Особистий кабінет підписувача
Мінімальний функціонал сторінки /personal/subscription/:
- Поточний план та статус.
- Дата наступного списання та сума.
- Історія платежів.
- Кнопка «Скасувати» (з опціональним питанням про причину).
- Зміна плану (апгрейд/даунгрейд).
- Оновлення платіжних даних.
При скасуванні: STATUS = 'CANCELLED', CANCELLED_AT = now(). Доступ зберігається до CURRENT_PERIOD_END — клієнт оплатив період, він використовує те, за що заплатив.
Сповіщення
Обов'язкові поштові події:
- SUBSCRIPTION_CREATED — підтвердження подписки.
- SUBSCRIPTION_PAYMENT_SUCCESS — успішне списання, квитанція.
- SUBSCRIPTION_PAYMENT_FAILED — помилка списання, оновити карту.
- SUBSCRIPTION_TRIAL_ENDING — за 1-3 дні до кінця пробу.
- SUBSCRIPTION_CANCELLED — підтвердження скасування.
- SUBSCRIPTION_EXPIRED — доступ закрито.
Строки розробки
| Варіант | Состав | Строк |
|---|---|---|
| Без рекуренту | Подписка з ручною оплатою, управління доступом | 5-7 днів |
| З рекурентними платежами | Автоспісання через ЮKassa або CloudPayments | 10-14 днів |
| Повна платформа | Кілька планів, пробу, ЛК, аналітика | 15-20 днів |







