Автоплатежи в Bitrix через YooKassa: подписка, сохранение карты, webhook и повторные списания
Автоплатежи или рекуррентные платежи в 1С-Битрикс — это не просто кнопка «оплатить» в личном кабинете. В реальном проекте это отдельная платежная логика: пользователь активирует подписку, сайт сохраняет способ оплаты, YooKassa присылает webhook, система продлевает доступ, а cron выполняет повторные списания по строгому расписанию.
Такая задача часто возникает в сервисах по подписке, личных кабинетах, SaaS-проектах, клубных программах, закрытых разделах сайта и любых проектах, где услуга должна автоматически продлеваться без ручного участия менеджера.
В этой статье разберем, как была реализована надежная система автоплатежей в Bitrix: Highload-блок для хранения подписок, собственный обработчик YooKassa, webhook вместо ненадежного success.php, автоматические списания по cron, защита от фейковых транзакций, повторные попытки при ошибках и управление подпиской со стороны пользователя.
Подобные задачи относятся к кастомной разработке на Bitrix и PHP: подключение платежных систем, доработка личного кабинета, настройка webhook, автоматизация списаний и работа с Highload-блоками. Такие работы можно выполнить в рамках услуги доработки Bitrix и PHP-задачи.
Задача проекта
Нужно было реализовать механизм подписки с автоматическим продлением. Пользователь должен был иметь возможность включить или отключить автоплатеж в личном кабинете, а система — запомнить платежный метод и в дальнейшем автоматически списывать оплату за следующий период.
Основные требования:
- дать пользователю возможность активировать автоплатеж;
- сохранить
payment_method_idпосле первой успешной оплаты с использованием штатного механизма рекуррентных платежей YooKassa; - хранить данные подписки в отдельной сущности;
- автоматически продлевать подписку по расписанию через cron;
- обрабатывать неудачные списания по кастомному алгоритму (Retry Policy);
- делать несколько повторных попыток оплаты;
- отключать автоплатеж после превышения лимита ошибок;
- отправлять пользователю уведомления о состоянии подписки.
Снаружи для пользователя это выглядит просто: включил автоплатеж — подписка продлевается сама. Но внутри сайта это полноценная платежная подсистема с хранением статусов, дат, попыток, идентификаторов платежей и связи с заказами Bitrix.
Общая архитектура решения
Решение было разделено на несколько независимых частей, чтобы не смешивать интерфейс, платежную логику и фоновые задачи.
- Highload-блок для хранения подписок и данных автоплатежей.
- Собственный обработчик платежной системы Bitrix для работы с YooKassa API.
- Webhook-обработчик с обязательной встречной верификацией статуса для фиксации успешных платежей.
- Изолированный Cron-скрипт для запуска автоматических списаний.
- Пользовательский AJAX-компонент для включения и отключения автоплатежа.
- Почтовые события для уведомлений о подписке, успешных и неудачных списаниях.
Такой подход удобен тем, что каждая часть отвечает за свою задачу: компонент управляет интерфейсом, webhook подтверждает оплату, Highload-блок хранит состояние, а cron выполняет регулярную проверку подписок.
Хранение данных автоплатежей в Highload-блоке
В качестве центрального хранилища был выбран Highload-блок. Это удобный вариант для Bitrix, когда нужно хранить отдельную бизнес-сущность: подписки, платежные методы, технические статусы, количество попыток списания и дату следующего продления.
Основные поля Highload-блока:
UF_PAYMENT_USERID — ID пользователя
UF_PAYMENT_ID — payment_method_id / сохраненный токен способа оплаты
UF_PAYMENT_DATE — дата последней успешной оплаты
UF_PAYMENT_AMOUNT — сумма списания
UF_SUBSCRIPTION_PERIOD — период подписки в месяцах
UF_PAYMENT_CHECKAUTO — включен ли автоплатеж (boolean)
UF_ATTEMPT_COUNT — количество неудачных попыток списания
UF_PAYMENT_STATUS — статус последней попытки оплаты
UF_PAYMENT_DATETIME — дата и время последней операции
Такая структура позволяет не только понять, активна ли подписка сейчас, но и обслуживать всю дальнейшую логику: когда списывать деньги, сколько попыток уже было, нужно ли отключать автоплатеж, какой платежный метод использовать и какой статус показать пользователю.
Если на проекте уже есть старая логика заказов, нестандартный личный кабинет или собственная система доступов, перед внедрением автоплатежей важно проверить архитектуру. Иногда сначала требуется навести порядок в коде, исправить ошибки после старых доработок или оптимизировать работу фоновых задач. В таких случаях полезен технический аудит сайта и производительности.
Собственная платежная система Bitrix для YooKassa
Для реализации этой задачи стандартных модулей «из коробки» обычно недостаточно. Здесь требуется кастомная интеграция платежных систем, в рамках которой был создан отдельный обработчик платежной системы Bitrix. Пользовательские обработчики можно размещать в директории:
/bitrix/php_interface/include/sale_payment/my_yookassa/
Внутри структуры были вынесены основные файлы:
/bitrix/php_interface/include/sale_payment/my_yookassa/
handler.php
description.php
payment.php
result.php
Через обработчик создается платеж в YooKassa, передается сумма, пользователь, технические метаданные и URL возврата. Для работы с API использовался официальный SDK YooKassa через Composer.
Важный момент: для автоплатежей недостаточно просто провести обычную оплату.
Нужно получить и сохранить payment_method_id, передавая при первой транзакции параметр
save_payment_method => true. Именно этот идентификатор (токен карты) позволяет
в будущем выполнять повторные списания без повторного ввода реквизитов пользователем.
Активация автоплатежа пользователем
Если пользователь ранее уже оплатил подписку, но платежный метод не был сохранен, простого
переключателя «Включить автоплатеж» недостаточно. Сначала нужно снова провести пользователя
через платеж YooKassa, получить разрешение на сохранение способа оплаты и сохранить
payment_method_id.
Логика активации была построена так:
- Пользователь нажимает «Включить автоплатеж» в личном кабинете.
- Сайт создает проверочный платеж в YooKassa (или привязывает карту в процессе первой реальной покупки).
- Пользователь переходит на страницу оплаты.
- После успешной оплаты система получает сохраненный
payment_method_id. - Далее выполняется автоматический возврат средств, если оплата была сугубо тестовой для привязки метода.
- В Highload-блок записывается токен способа оплаты.
- Флаг автоплатежа переводится в активное состояние.
Такой сценарий позволяет не брать деньги просто за подключение автоплатежа, но при этом корректно получить платежный метод для будущих списаний.
Почему webhook надежнее, чем success.php
На первом этапе можно подумать, что успешную оплату достаточно обработать через страницу возврата,
например success.php. Но для платежной логики такой подход абсолютно ненадежен.
Причины:
- пользователь может закрыть вкладку браузера сразу после авторизации платежа в банке;
- браузер или сеть могут моргнуть и не выполнить редирект обратно на сайт;
- клиентский редирект в принципе не является гарантией успешного прохождения транзакции;
- статус оплаты должен фиксироваться строго на уровне взаимодействия сервер-сервер;
- страница возврата может быть открыта пользователем повторно из истории или вручную.
Поэтому основная обработка была реализована через webhook YooKassa. Платежная система сама отправляет асинхронное уведомление на сервер после изменения статуса платежа. На основании этого события сайт проверяет платеж и обновляет подписку.
/local/yookassa/webhook.php
Такой подход особенно важен для автоплатежей: если webhook не обработан корректно, пользователь может оплатить, но подписка не продлится, заказ не станет оплаченным или письмо не уйдет. Поэтому webhook — это центральная точка всей платежной интеграции.
Сохранение данных после успешной оплаты
После успешной оплаты нужно создать или обновить запись в Highload-блоке. Ниже представлен пример корректного вызова API Битрикса через современное ядро D7 с подключением необходимых пространств имен:
<?php
use Bitrix\Main\Loader;
use Bitrix\Highloadblock\HighloadBlockTable;
Loader::includeModule("highloadblock");
// Получаем скомпилированную сущность Highload-блока
$hlblock = HighloadBlockTable::getById(HL_BLOCK_ID_PAYMENT)->fetch();
$entity = HighloadBlockTable::compileEntity($hlblock);
$entityDataClass = $entity->getDataClass();
$rsData = $entityDataClass::getList([
'filter' => [
'UF_PAYMENT_USERID' => $userId
],
]);
if (!$arItem = $rsData->fetch()) {
$entityDataClass::add([
'UF_PAYMENT_ID' => $paymentMethodId,
'UF_PAYMENT_DATE' => $paymentDate,
'UF_PAYMENT_USERID' => $userId,
'UF_PAYMENT_CHECKAUTO' => $isAutoPayment,
'UF_PAYMENT_AMOUNT' => $paymentAmount,
'UF_SUBSCRIPTION_PERIOD' => $subsPeriod
]);
} else {
$entityDataClass::update($arItem['ID'], [
'UF_PAYMENT_ID' => $paymentMethodId,
'UF_PAYMENT_DATE' => $paymentDate,
'UF_PAYMENT_CHECKAUTO' => $isAutoPayment,
'UF_PAYMENT_AMOUNT' => $paymentAmount,
'UF_SUBSCRIPTION_PERIOD' => $subsPeriod
]);
}
?>
Так Highload-блок становится единым источником данных по подписке. Именно от него дальше зависит расчет даты следующего списания, отображение статуса в личном кабинете и запуск повторной оплаты.
Расчет даты окончания подписки
Один из важных моментов — корректно определить, когда нужно делать следующее списание. Нельзя просто каждый месяц списывать деньги у всех пользователей, потому что подписка может быть оплачена на 1, 2, 3, 6 или 12 месяцев.
Поэтому дата окончания оплаченного периода рассчитывается так:
дата последней оплаты + UF_SUBSCRIPTION_PERIOD месяцев
Пример расчета на PHP:
$lastPaymentDate = new DateTime($arItem['UF_PAYMENT_DATE']);
$subscriptionPeriod = (int)$arItem['UF_SUBSCRIPTION_PERIOD'];
$expiryDate = (clone $lastPaymentDate)->modify("+{$subscriptionPeriod} months");
Если... пользователь оплатил подписку на 6 месяцев, система не будет пытаться списывать деньги каждый месяц. Следующее автосписание произойдет только после окончания оплаченного периода.
Автоматические списания по расписанию (Cron)
Для запуска автоплатежей используется кастомный фоновый скрипт, выполняющийся на сервере по расписанию. Он выбирает активные подписки, рассчитывает дату окончания периода и проверяет, нужно ли выполнять очередное списание.
Основной сценарий фонового скрипта:
- Получить все записи из Highload-блока, где
UF_PAYMENT_CHECKAUTO = true. - Для каждой подписки взять дату последней оплаты.
- Прибавить
UF_SUBSCRIPTION_PERIODмесяцев. - Сравнить рассчитанную дату окончания периода с текущей датой.
- Если период закончился — инициировать рекуррентное списание через YooKassa API по сохраненному токену.
- При успехе обновить дату последней оплаты в базе и обнулить счетчик ошибок.
- При ошибке увеличить количество неудачных попыток.
Файл фоновой задачи вынесен в изолированную директорию:
/local/php_interface/cron/auto_payment_cron.php
Пример настройки планировщика Cron на хостинге/сервере для прямого ежесуточного вызова именно этого скрипта:
0 0 * * * /usr/bin/php -f /home/bitrix/www/local/php_interface/cron/auto_payment_cron.php
Проверку подписок удобно запускать один раз в день, например ночью. Для большинства подписочных сценариев этого достаточно, потому что списание не требует моментального запуска каждую минуту.
Если cron или фоновые задачи на сервере работают нестабильно, подписки могут не продлеваться вовремя. Это уже задача не только платежной интеграции, но и технического сопровождения сайта: проверка cron, прав доступа, системных логов, ошибок PHP и общей нагрузки на сервер.
Повторные попытки списания
Неудачное списание не всегда означает, что подписку нужно сразу отключать.
Например, на карте может временно не быть средств, банк может отклонить операцию,
платежная система может вернуть статус canceled или пользователь может решить проблему
с балансом в течение нескольких дней.
Поэтому была реализована мягкая логика повторных попыток (Retry Policy). При неудачной оплате увеличивается счетчик:
UF_ATTEMPT_COUNT = UF_ATTEMPT_COUNT + 1
Алгоритм обработки ошибок:
- первая неудачная попытка — повторить списание на следующий день;
- вторая неудачная попытка — выполнить еще один повтор через 24 часа;
- третья неудачная попытка — сделать финальную попытку списания;
- после превышения лимита — автоматически деактивировать автоплатеж, чтобы не спамить банк.
if ($attemptCount >= 3) {
$entityDataClass->update($recordId, [
'UF_PAYMENT_CHECKAUTO' => false
]);
}
Это защищает систему от бесконечных бессмысленных запросов к платежному шлюзу и одновременно дает пользователю время пополнить карту или выбрать другой способ оплаты в личном кабинете.
Безопасность решения и защита от фейковых оплат
Разработка систем автоматических списаний накладывает повышенную ответственность. Финтех-логика не прощает халатности, поэтому в решении были реализованы фундаментальные слои защиты:
-
Соответствие PCI DSS (Токенизация): Наш сайт физически не имеет права собирать, обрабатывать или хранить CVV/CVC коды и полные номера карт, так как это требует дорогостоящего ежегодного аудита безопасности. Вместо этого вся платежная форма рендерится на стороне YooKassa. Битрикс получает лишь безопасный токен —
payment_method_id. Он бесполезен для злоумышленников, поскольку привязан исключительно к нашему ID мерчанта. -
Встречная проверка вебхуков: Сам по себе файл
webhook.phpоткрыт для внешнего мира. Если злоумышленник узнает его адрес, он может симулировать успешную оплату, отправив поддельный POST-запрос с JSON. Чтобы полностью исключить эту уязвимость, наш обработчик не верит входящему JSON на слово. Скрипт перехватываетpayment_id, сам делает защищенный встречный запрос к официальному API YooKassa и проверяет реальный статус транзакции непосредственно в платежной системе. -
Принцип идемпотентности: Из-за нестабильного сетевого соединения или таймаутов YooKassa может прислать один и тот же вебхук несколько раз. Без защиты от дублирования система могла бы дважды продлить подписку или повторно списать средства. Наш код перед обработкой проверяет текущее состояние заказа и записи в HL-блоке. Если транзакция уже была успешно проведена, скрипт мгновенно прерывает выполнение и просто отдает платежке статус
200 OK. -
Изоляция скриптов автоматизации: Скрипт
auto_payment_cron.phpсодержит критическую бизнес-логику, инициирующую финансовые транзакции. Мы исключили риск его вызова через браузер посторонними лицами, добавив жесткую проверку среды выполнения в самое начало файла:
Это гарантирует запуск логики только из консоли сервера по расписанию планировщика.if (php_sapi_name() !== 'cli') { die('Access denied'); }
Компонент управления автоплатежом в личном кабинете
Для пользователя был разработан отдельный компонент Bitrix, который показывает текущий статус автоплатежа и позволяет включить или отключить подписку без обращения к администратору.
Пример структуры компонента:
/local/components/mycompany/auto_payment/
.description.php
.parameters.php
component.php
ajax.php
class.php
templates/.default/template.php
Логика компонента:
- если автоплатеж активен — пользователь может его отключить в один клик;
- если автоплатеж выключен, но
payment_method_idуже сохранен в базе — достаточно просто заново включить флаг; - если платежного метода нет — пользователь перенаправляется на шлюз YooKassa для первичной привязки карты;
- после изменения статуса интерфейс личного кабинета динамически обновляется через AJAX.
Такая доработка относится к разработке личного кабинета на Bitrix: здесь важно не только сохранить значение в базе, но и правильно связать интерфейс, текущую подписку, платежную систему и пользовательские уведомления.
Обновление статуса автоплатежа через AJAX
Для переключения автоплатежа использовался AJAX-обработчик. Он определяет текущего авторизованного пользователя,
находит его запись в Highload-блоке и обновляет поле UF_PAYMENT_CHECKAUTO.
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['autopayment'])) {
$userId = $USER->GetID();
$autopaymentStatus = ($_POST['autopayment'] === 'Y') ? true : false;
$rsData = $entityDataClass::getList([
'filter' => [
'UF_PAYMENT_USERID' => $userId
],
]);
if ($arItem = $rsData->fetch()) {
$result = $entityDataClass->update($arItem['ID'], [
'UF_PAYMENT_CHECKAUTO' => $autopaymentStatus
]);
}
}
Важно, чтобы интерфейс показывал не значение по умолчанию, а фактическое состояние подписки из базы данных. Иначе пользователь может видеть включенный автоплатеж, хотя в базе он отключен, или наоборот.
Обновление заказа Bitrix после оплаты
После подтверждения успешной оплаты через вебхук нужно обновить не только Highload-блок, но и сам заказ Bitrix. Это необходимо для корректной работы штатного модуля sale, изменения статусов заказов, отображения истории оплат в админке и корректного сведения бухгалтерии.
$paymentCollection = $order->getPaymentCollection();
foreach ($paymentCollection as $payment) {
if (!$payment->isPaid()) {
$payment->setPaid("Y");
}
}
$order->setField("STATUS_ID", "P"); // Переводим заказ в статус "Оплачен"
$order->save();
В результате данные синхронизируются на двух уровнях: платеж в заказе отмечается как оплаченный на уровне ядра CMS, а Highload-блок сохраняет обновленное состояние подписки и технические данные для следующих автоматических списаний.
Почтовые уведомления
Для пользователя были подготовлены почтовые события и шаблоны. Такой подход удобен тем, что текст писем можно легко редактировать через стандартный визуальный интерфейс в админке Bitrix, не вмешиваясь в исходный код системы автоплатежей.
Были предусмотрены уведомления для следующих сценариев:
- об успешном оформлении и активации подписки;
- об успешном автоматическом списании средств за новый период;
- о неудачной попытке транзакции (с мягким предупреждением о необходимости пополнить баланс);
- об автоматическом отключении подписки после превышения лимита ошибок списания;
- о необходимости обновить привязанную платежную карту (например, при истечении срока ее действия).
В макросы писем передаются все необходимые метаданные: имя пользователя, email, точная сумма, дата операции, период подписки, статус платежа и прямая ссылка на управление услугами в личном кабинете.
Типовые ошибки при реализации автоплатежей
Обработка оплаты через success.php вместо webhook
Если завязать продление подписки только на страницу возврата, часть оплат гарантированно потеряется. Пользователь оплатит, но закроет вкладку платежной системы до редиректа, и сайт никогда не узнает об успешном платеже.
Не сохраняется payment_method_id
Без сохраненного платежного метода невозможно выполнять автоматические списания. Обычный разовый платеж и платеж с сохранением метода оплаты — это принципиально разные сценарии интеграции API.
Неправильно считается дата следующего списания
Если не учитывать индивидуальный UF_SUBSCRIPTION_PERIOD, система может списывать деньги слишком рано
или слишком поздно. Особенно это критично, если на одном сайте есть тарифы на 1, 3 и 12 месяцев.
Нет ограничения повторных попыток
Если не ограничить количество повторов, скрипт по крону будет бесконечно пытаться списывать деньги с пустой карты пользователя. Это перегружает логи, портит техническую репутацию мерчанта в платежной системе и вызывает негатив у клиентов.
Отсутствие верификации входящих запросов и идемпотентности
Доверие входящему JSON-телу вебхука без встречного запроса в API YooKassa открывает уязвимость для фейковых продлений. А отсутствие контроля дублирующих запросов рано или поздно приведет к случайному двойному списанию средств у клиента из-за сетевых сбоев.
Исправление подобных архитектурных ошибок — это типичный пример технической доработки Bitrix-проекта: нужно смотреть код обработчика оплаты, webhook, Highload-блоки, cron, статусы заказа и пользовательский интерфейс.
Что проверить после внедрения
После реализации автоплатежей нужно протестировать не только первую оплату, но и весь жизненный цикл подписки.
- Пользователь может включить автоплатеж в личном кабинете.
- YooKassa корректно создает платеж.
- После оплаты сохраняется
payment_method_id. - Webhook приходит на сайт и обрабатывается сервером.
- Highload-блок обновляется после успешной оплаты.
- Заказ Bitrix получает статус оплаченного.
- Дата следующего списания рассчитывается с учетом периода подписки.
- Cron или агент запускает проверку подписок.
- Повторное списание выполняется через сохраненный платежный метод.
- Неудачные попытки увеличивают счетчик ошибок.
- После лимита ошибок автоплатеж отключается.
- Пользователь получает уведомления о статусе подписки.
Где может пригодиться такая система
Автоплатежи через YooKassa подходят для разных типов проектов:
- сайты с платной подпиской;
- SaaS-сервисы;
- онлайн-школы и закрытые курсы;
- клубные программы;
- личные кабинеты с регулярным доступом;
- платные разделы сайта;
- B2B-порталы с ежемесячной оплатой;
- сервисы, где нужно автоматическое продление услуг.
Посмотреть похожие задачи по платежным интеграциям, личным кабинетам, Bitrix, PHP, автоматизации и нестандартной бизнес-логике можно в разделе примеры работ.
Почему автоплатежи нельзя делать “на костылях”
В автоплатежах любая мелкая ошибка может привести к серьезной проблеме: деньги списались, а подписка не продлилась; webhook пришел, но не обработался; пользователь отключил автоплатеж, а cron все равно сделал списание; заказ в админке не оплачен, хотя услуга уже активирована.
Поэтому при разработке важно заранее продумать:
- структуру хранения подписок;
- связь подписки с пользователем и заказом;
- логику сохранения платежного метода;
- обработку webhook;
- проверку повторных уведомлений;
- расчет даты следующего списания;
- ограничение количества попыток;
- синхронизацию Highload-блока и заказа Bitrix;
- уведомления пользователю;
- ручную проверку спорных ситуаций администратором.
В AG Studio такие задачи решаются не как разовая вставка платежной кнопки, а как полноценная интеграция в существующую бизнес-логику проекта. Подробнее о подходе к разработке, поддержке и техническим задачам можно прочитать на странице о студии.
Результат
В результате была реализована полноценная система подписок и автоплатежей внутри Bitrix. Пользователь может включить автоплатеж в личном кабинете, система сохраняет платежный метод, webhook подтверждает оплату, Highload-блок хранит состояние подписки, а cron автоматически запускает следующие списания.
Дополнительно была реализована обработка неудачных попыток: система не отключает подписку сразу, а делает несколько повторов и только после превышения лимита выключает автоплатеж. Это делает решение более устойчивым и удобным для пользователя.
Если вам необходима качественная интеграция платежных систем, подключение YooKassa, реализация автоплатежей, настройка webhook, доработка личного кабинета, исправление ошибок в оплате или синхронизация подписок в Bitrix, можно отправить задачу через страницу контактов.