Промокоды и скидки

Поптимайзер поддерживает три вида скидок на подписку: промокоды (купоны), вводная скидка для новых подписчиков и скидка за лояльность. Все скидки складываются и считаются от базовой цены тарифа.

Как работают скидки

Итоговая цена рассчитывается так:
  1. Берётся базовая цена тарифа (с учётом годовой скидки, если выбран годовой интервал)
  2. Каждая скидка рассчитывается от базовой цены независимо
  3. Все скидки складываются и вычитаются из базовой цены
  4. Минимальная цена — 1 ₽ (ЮKassa не принимает платёж на 0 ₽)
Пример: тариф 990 ₽/мес, вводная скидка 20% и промокод 10%:
  • Вводная: 990 × 20% = 198 ₽
  • Промокод: 990 × 10% = 99 ₽
  • Итого скидка: 297 ₽
  • К оплате: 693 ₽/мес

Промокоды (купоны)

Промокод — это уникальный код, который даёт скидку при оформлении подписки. Пользователь вводит код на странице биллинга перед оплатой.

Параметры промокода

| Параметр | Описание | |----------|----------| | Код | Уникальный, латинские буквы, цифры, дефис, подчёркивание (мин. 3 символа) | | Тип скидки | percentage — процент (0.2 = 20%) или fixed_amount — фиксированная сумма в рублях | | Срок действия | Дата истечения (необязательно, без срока = бессрочный) | | Лимит использований | Сколько раз можно применить (необязательно, без лимита = безлимит) | | Периоды действия | На сколько периодов оплаты действует скидка (1 = только первый платёж, без значения = постоянный) | | Ограничение по тарифу | Применим только к конкретным тарифам (необязательно, без ограничения = все тарифы) |

Жизненный цикл промокода

  1. Админ создаёт промокод через Server Action
  2. Пользователь вводит код на странице биллинга → видит предпросмотр скидки
  3. Пользователь нажимает «Оформить» → переходит на оплату в ЮKassa
  4. После подтверждения оплаты (webhook payment.succeeded) промокод погашается
  5. Если пользователь не завершает оплату — промокод не расходуется
[!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] Деактивация — это мягкое удаление. Промокод остаётся в базе данных для аудита, но больше не принимается при оплате. У организаций, которые уже активировали этот купон, скидка продолжит действовать до истечения периодов.

Приветственные и другие скидки

Все скидки (приветственные, за лояльность и т.д.) управляются через купоны (см. раздел выше). Создайте купон с нужными параметрами (процент, количество периодов, ограничение по плану) и распространяйте код среди пользователей.

Расчёт итоговой цены

  1. Базовая цена = месячная цена × множитель интервала (годовая скидка 20% уже учтена)
  2. Купон — % от базовой цены или фиксированная сумма
Максимальная скидка — 50% от базовой цены. Минимальная итоговая цена — 1₽.
finalPrice = max(1, basePrice - скидкаКупона)