Налаштування підписки на зниження ціни товару 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 дні |







