Настройка подписки на снижение цены товара 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Настройка подписки на снижение цены товара 1С-Битрикс
Простая
~1 рабочий день
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1181
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    813
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Разработка на базе Битрикс, Битрикс24, 1С для компании Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    747
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Настройка подписки на снижение цены товара 1С-Битрикс

Покупатель смотрит на дорогой товар, не покупает. Кнопка «Уведомить о снижении цены» даёт шанс вернуть его именно в момент, когда цена стала приемлемой. Это работает лучше любой рассылки по базе, потому что пользователь сам выразил интерес к конкретному товару.

Структура данных

HL-блок PriceSubscription для хранения подписок:

b_uts_price_subscription
├── ID
├── UF_USER_ID      — ID пользователя (0 = незарегистрированный)
├── UF_EMAIL        — email для уведомления
├── UF_PRODUCT_ID   — ID товара (b_iblock_element.ID)
├── UF_TARGET_PRICE — желаемая цена (0 = любое снижение)
├── UF_CURRENT_PRICE — цена на момент подписки
├── UF_ACTIVE       — активна ли подписка
├── UF_NOTIFIED     — было ли отправлено уведомление
└── UF_DATE_CREATE  — дата создания

Форма подписки

На карточке товара кнопка появляется рядом с ценой:

// В шаблоне карточки, под блоком цены
<?php if (!$arResult['CATALOG_ITEM']['CAN_BUY']): // только если нельзя купить сейчас ?>
<?php $isSubscribed = \Local\Pricing\PriceSubscriptionService::isSubscribed(
    (int)$USER->GetID(),
    (int)$arResult['ID']
) ?>
<button class="btn-price-subscribe js-price-subscribe
               <?= $isSubscribed ? 'is-active' : '' ?>"
        data-product-id="<?= $arResult['ID'] ?>"
        data-current-price="<?= $arResult['CATALOG_PRICE']['PRICE'] ?>">
    <?= $isSubscribed ? 'Подписка активна' : 'Уведомить о снижении цены' ?>
</button>
<?php else: ?>

Для авторизованных — AJAX-подписка. Для гостей — показываем модал с полем email:

document.querySelectorAll('.js-price-subscribe').forEach(btn => {
    btn.addEventListener('click', async () => {
        const productId    = btn.dataset.productId;
        const currentPrice = btn.dataset.currentPrice;
        const email        = window.__userEmail || null; // прокидываем из PHP

        if (!email) {
            // Показываем модал с полем email
            showPriceSubscribeModal(productId, currentPrice);
            return;
        }

        const res = await fetch('/local/ajax/price-subscribe.php', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ product_id: productId, email, current_price: currentPrice }),
        }).then(r => r.json());

        if (res.success) {
            btn.classList.add('is-active');
            btn.textContent = 'Подписка активна';
        }
    });
});

AJAX-обработчик подписки

// /local/ajax/price-subscribe.php
\Bitrix\Main\Application::getInstance()->initializeExtended();

global $USER;
$data = json_decode(file_get_contents('php://input'), true);

$productId    = (int)($data['product_id'] ?? 0);
$email        = filter_var($data['email'] ?? '', FILTER_VALIDATE_EMAIL);
$currentPrice = (float)($data['current_price'] ?? 0);

if (!$productId || !$email) {
    echo json_encode(['success' => false, 'error' => 'Invalid data']);
    exit;
}

$result = \Local\Pricing\PriceSubscriptionService::subscribe(
    userId:       (int)$USER->GetID(),
    email:        $email,
    productId:    $productId,
    currentPrice: $currentPrice,
    targetPrice:  (float)($data['target_price'] ?? 0),
);

echo json_encode(['success' => $result]);

Сервис управления подписками

namespace Local\Pricing;

use Bitrix\Highloadblock\HighloadBlockTable;

class PriceSubscriptionService
{
    public static function subscribe(
        int    $userId,
        string $email,
        int    $productId,
        float  $currentPrice,
        float  $targetPrice = 0
    ): bool {
        // Проверяем дубликат
        if (self::isSubscribed($userId, $productId, $email)) {
            return true; // уже подписан
        }

        $dataClass = self::getDataClass();
        $result    = $dataClass::add([
            'UF_USER_ID'      => $userId,
            'UF_EMAIL'        => $email,
            'UF_PRODUCT_ID'   => $productId,
            'UF_TARGET_PRICE' => $targetPrice,
            'UF_CURRENT_PRICE' => $currentPrice,
            'UF_ACTIVE'       => true,
            'UF_NOTIFIED'     => false,
        ]);

        return $result->isSuccess();
    }

    public static function isSubscribed(int $userId, int $productId, string $email = ''): bool
    {
        $dataClass = self::getDataClass();
        $filter    = ['UF_PRODUCT_ID' => $productId, 'UF_ACTIVE' => true];

        if ($userId > 0) {
            $filter['UF_USER_ID'] = $userId;
        } elseif ($email) {
            $filter['UF_EMAIL'] = $email;
        }

        return (bool)$dataClass::getRow(['filter' => $filter, 'select' => ['ID']]);
    }
}

Агент проверки снижения цен

Агент запускается раз в час, находит товары с упавшей ценой и отправляет уведомления:

namespace Local\Pricing;

class PriceDropNotifierAgent
{
    public static function run(): string
    {
        $dataClass   = PriceSubscriptionService::getDataClass();
        $subscriptions = $dataClass::getList([
            'filter' => ['UF_ACTIVE' => true, 'UF_NOTIFIED' => false],
            'select' => ['ID', 'UF_EMAIL', 'UF_PRODUCT_ID', 'UF_CURRENT_PRICE', 'UF_TARGET_PRICE'],
        ]);

        while ($sub = $subscriptions->fetch()) {
            $currentPrice = self::getCurrentPrice((int)$sub['UF_PRODUCT_ID']);

            if ($currentPrice === null) continue;

            $priceDropped = $currentPrice < $sub['UF_CURRENT_PRICE'];
            $targetReached = $sub['UF_TARGET_PRICE'] > 0
                ? $currentPrice <= $sub['UF_TARGET_PRICE']
                : $priceDropped;

            if ($targetReached) {
                self::sendNotification($sub, $currentPrice);
                $dataClass::update($sub['ID'], [
                    'UF_NOTIFIED'     => true,
                    'UF_CURRENT_PRICE' => $currentPrice,
                ]);
            }
        }

        return '\Local\Pricing\PriceDropNotifierAgent::run();';
    }

    private static function getCurrentPrice(int $productId): ?float
    {
        $res = \CCatalogPrice::GetList(
            [],
            ['PRODUCT_ID' => $productId, 'CATALOG_GROUP_ID' => 1]
        )->Fetch();

        return $res ? (float)$res['PRICE'] : null;
    }

    private static function sendNotification(array $sub, float $newPrice): void
    {
        $product = \CIBlockElement::GetByID($sub['UF_PRODUCT_ID'])->GetNext();
        if (!$product) return;

        \CEvent::Send('PRICE_DROP_NOTIFICATION', SITE_ID, [
            'EMAIL'       => $sub['UF_EMAIL'],
            'PRODUCT_NAME' => $product['NAME'],
            'PRODUCT_URL'  => 'https://' . SITE_SERVER_NAME . $product['DETAIL_PAGE_URL'],
            'OLD_PRICE'    => number_format($sub['UF_CURRENT_PRICE'], 0, '', ' '),
            'NEW_PRICE'    => number_format($newPrice, 0, '', ' '),
            'SAVINGS'      => number_format($sub['UF_CURRENT_PRICE'] - $newPrice, 0, '', ' '),
        ]);
    }
}

Сроки реализации

Конфигурация Срок
Подписка (кнопка + AJAX + HL-блок) 2–3 дня
+ агент проверки цен + email-уведомление +2 дня
+ целевая цена, личный кабинет подписок +2–3 дня