Настройка подписки на снижение цены товара 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 дня |







