Налаштування підписочних платежів на 1С-Бітрікс
Підписочна модель оплати — monthly або annual billing з автоматичним продовженням — потребує окремої інфраструктури в 1С-Бітрікс: сутності «підписка», періодичних списань за збереженою карткою, управління станом (active, paused, cancelled) та роботи з пробними періодами. Стандартний модуль sale для цього не призначений, потрібна надбудова.
Модель даних підписки
// Структура таблиці підписок (ORM D7)
class SubscriptionTable extends \Bitrix\Main\Entity\DataManager
{
public static function getTableName(): string
{
return 'b_subscription';
}
public static function getMap(): array
{
return [
'ID' => new IntegerField('ID', ['primary' => true, 'autocomplete' => true]),
'USER_ID' => new IntegerField('USER_ID', ['required' => true]),
'PLAN_ID' => new IntegerField('PLAN_ID', ['required' => true]),
'STATUS' => new StringField('STATUS'), // ACTIVE, PAUSED, CANCELLED, TRIAL
'PAYMENT_METHOD' => new StringField('PAYMENT_METHOD'), // токен картки
'AMOUNT' => new FloatField('AMOUNT'),
'CURRENCY' => new StringField('CURRENCY', ['default_value' => 'RUB']),
'BILLING_CYCLE' => new StringField('BILLING_CYCLE'), // MONTHLY, ANNUAL
'NEXT_BILLING' => new DatetimeField('NEXT_BILLING'),
'TRIAL_END' => new DatetimeField('TRIAL_END'),
'CREATED_AT' => new DatetimeField('CREATED_AT'),
'CANCELLED_AT' => new DatetimeField('CANCELLED_AT'),
];
}
}
Створення підписки при оформленні
class SubscriptionService
{
public function create(int $userId, int $planId, bool $trial = false): int
{
$plan = SubscriptionPlanTable::getById($planId)->fetch();
$trialEnd = $trial ? (new \DateTime())->modify('+' . $plan['TRIAL_DAYS'] . ' days') : null;
$nextBill = $trial
? $trialEnd
: (new \DateTime())->modify('+1 ' . strtolower($plan['CYCLE']));
$result = SubscriptionTable::add([
'USER_ID' => $userId,
'PLAN_ID' => $planId,
'STATUS' => $trial ? 'TRIAL' : 'ACTIVE',
'AMOUNT' => $plan['PRICE'],
'BILLING_CYCLE' => $plan['CYCLE'],
'NEXT_BILLING' => \Bitrix\Main\Type\DateTime::createFromPhp($nextBill),
'TRIAL_END' => $trialEnd
? \Bitrix\Main\Type\DateTime::createFromPhp($trialEnd)
: null,
'CREATED_AT' => new \Bitrix\Main\Type\DateTime(),
]);
return $result->getId();
}
// Прив'язка картки до підписки після першого платежу
public function attachPaymentMethod(int $subscriptionId, string $paymentMethodId): void
{
SubscriptionTable::update($subscriptionId, [
'PAYMENT_METHOD' => $paymentMethodId,
]);
}
}
Білінг-агент: щоденний обхід підписок
class SubscriptionBillingAgent
{
public static function run(): string
{
// Вибираємо всі підписки з NEXT_BILLING <= сьогодні
$subscriptions = SubscriptionTable::getList([
'filter' => [
'STATUS' => ['ACTIVE', 'TRIAL'],
'<=NEXT_BILLING' => new \Bitrix\Main\Type\DateTime(),
],
]);
$paymentService = new RecurringPaymentService();
while ($sub = $subscriptions->fetch()) {
if ($sub['STATUS'] === 'TRIAL') {
// Пробний період завершився — перше списання
$sub['STATUS'] = 'ACTIVE';
}
$charged = $paymentService->chargeRecurrent(
$sub['USER_ID'],
$sub['PAYMENT_METHOD'],
$sub['AMOUNT'],
'Підписка: продовження #' . $sub['ID']
);
if ($charged) {
// Зсуваємо дату наступного білінгу
$next = (new \DateTime())->modify(
$sub['BILLING_CYCLE'] === 'ANNUAL' ? '+1 year' : '+1 month'
);
SubscriptionTable::update($sub['ID'], [
'NEXT_BILLING' => \Bitrix\Main\Type\DateTime::createFromPhp($next),
'STATUS' => 'ACTIVE',
]);
} else {
self::handleFailedPayment($sub);
}
}
return '\MyModule\SubscriptionBillingAgent::run();';
}
private static function handleFailedPayment(array $sub): void
{
$attempts = (int)SubscriptionAttemptTable::countFailures($sub['ID'], '-7 days');
if ($attempts >= 3) {
// Скасовуємо підписку після 3 невдач за 7 днів
SubscriptionTable::update($sub['ID'], [
'STATUS' => 'CANCELLED',
'CANCELLED_AT' => new \Bitrix\Main\Type\DateTime(),
]);
// Email покупцю
} else {
// Повтор через добу — зсуваємо NEXT_BILLING на 1 день
SubscriptionTable::update($sub['ID'], [
'NEXT_BILLING' => \Bitrix\Main\Type\DateTime::createFromPhp(
(new \DateTime())->modify('+1 day')
),
]);
}
}
}
Особистий кабінет покупця
Управління підпискою в ОК: пауза, скасування, зміна картки. Кнопки надсилають AJAX-запити до компонента:
// Пауза підписки (зупинити білінг, не скасовувати)
if ($action === 'pause') {
SubscriptionTable::update($subscriptionId, ['STATUS' => 'PAUSED']);
}
// Відновлення
if ($action === 'resume') {
$nextBilling = (new \DateTime())->modify('+1 month');
SubscriptionTable::update($subscriptionId, [
'STATUS' => 'ACTIVE',
'NEXT_BILLING' => \Bitrix\Main\Type\DateTime::createFromPhp($nextBilling),
]);
}
Терміни
| Завдання | Термін |
|---|---|
| Модель даних + сервіс підписок | 2 дні |
| Білінг-агент + retry-логіка | 1–2 дні |
| ОК покупця (пауза, скасування, зміна картки) | 1–2 дні |







