Разработка системы подписок (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 дней |







