Промокоды и скидки
Поптимайзер поддерживает три вида скидок на подписку: промокоды (купоны), вводная скидка для новых подписчиков и скидка за лояльность. Все скидки складываются и считаются от базовой цены тарифа.
Как работают скидки
Итоговая цена рассчитывается так:
- Берётся базовая цена тарифа (с учётом годовой скидки, если выбран годовой интервал)
- Каждая скидка рассчитывается от базовой цены независимо
- Все скидки складываются и вычитаются из базовой цены
- Минимальная цена — 1 ₽ (ЮKassa не принимает платёж на 0 ₽)
Пример: тариф 990 ₽/мес, вводная скидка 20% и промокод 10%:
- Вводная: 990 × 20% = 198 ₽
- Промокод: 990 × 10% = 99 ₽
- Итого скидка: 297 ₽
- К оплате: 693 ₽/мес
Промокоды (купоны)
Промокод — это уникальный код, который даёт скидку при оформлении подписки. Пользователь вводит код на странице биллинга перед оплатой.
Параметры промокода
| Параметр | Описание |
|----------|----------|
| Код | Уникальный, латинские буквы, цифры, дефис, подчёркивание (мин. 3 символа) |
| Тип скидки |
percentage — процент (0.2 = 20%) или fixed_amount — фиксированная сумма в рублях |
| Срок действия | Дата истечения (необязательно, без срока = бессрочный) |
| Лимит использований | Сколько раз можно применить (необязательно, без лимита = безлимит) |
| Периоды действия | На сколько периодов оплаты действует скидка (1 = только первый платёж, без значения = постоянный) |
| Ограничение по тарифу | Применим только к конкретным тарифам (необязательно, без ограничения = все тарифы) |Жизненный цикл промокода
- Админ создаёт промокод через Server Action
- Пользователь вводит код на странице биллинга → видит предпросмотр скидки
- Пользователь нажимает «Оформить» → переходит на оплату в ЮKassa
- После подтверждения оплаты (webhook
payment.succeeded) промокод погашается - Если пользователь не завершает оплату — промокод не расходуется
[!NOTE] Промокод погашается только после успешной оплаты. Если пользователь начал оплату, но не завершил — купон остаётся доступным.
Ограничения
- Промокоды нельзя использовать при апгрейде тарифа (только при новой подписке)
- Один промокод на организацию — новый заменяет предыдущий
- Промокод с
recurringPeriods: 3действует 3 периода оплаты, потом автоматически снимается
Управление промокодами
Промокоды создаются и управляются через Server Actions (требуется роль
platformRole: "admin"). UI-панель для управления не предусмотрена — используйте консоль или скрипты.Создание промокода
import { createCouponAction } from "@/app/actions/admin/coupons";
// Промокод на 20% для всех тарифов, 100 использований, бессрочный
const result = await createCouponAction({
code: "WELCOME20",
description: "Приветственная скидка 20%",
mode: "percentage",
value: 0.2,
maxRedemptions: 100,
});
// Промокод на 500₽ скидку, только для Pro, действует 1 период
const result2 = await createCouponAction({
code: "PRO-500",
description: "Скидка 500₽ на первый месяц Pro",
mode: "fixed_amount",
value: 500,
applicablePlanIds: ["pro"],
recurringPeriods: 1,
maxRedemptions: 50,
expiresAt: "2026-06-30T23:59:59Z",
});
Просмотр всех промокодов
import { listCouponsAction } from "@/app/actions/admin/coupons";
const result = await listCouponsAction();
if (!("error" in result)) {
for (const coupon of result.coupons) {
console.log(
`${coupon.code}: ${coupon.redemptionCount}/${coupon.maxRedemptions ?? "∞"} использований, ` +
`активен: ${coupon.active}`
);
}
}
Деактивация промокода
import { deactivateCouponAction } from "@/app/actions/admin/coupons";
// Мягкое удаление — купон остаётся в базе, но перестаёт работать
await deactivateCouponAction("WELCOME20");
[!TIP] Деактивация — это мягкое удаление. Промокод остаётся в базе данных для аудита, но больше не принимается при оплате. У организаций, которые уже активировали этот купон, скидка продолжит действовать до истечения периодов.
Приветственные и другие скидки
Все скидки (приветственные, за лояльность и т.д.) управляются через купоны (см. раздел выше). Создайте купон с нужными параметрами (процент, количество периодов, ограничение по плану) и распространяйте код среди пользователей.
Расчёт итоговой цены
- Базовая цена = месячная цена × множитель интервала (годовая скидка 20% уже учтена)
- Купон — % от базовой цены или фиксированная сумма
Максимальная скидка — 50% от базовой цены. Минимальная итоговая цена — 1₽.
finalPrice = max(1, basePrice - скидкаКупона)