⏱ Політика Динамічної Тривалості Тарифів та Блокування Доступу
📌 Чому динамічна тривалість?
У ранніх версіях платформи терміни дії тарифів хардкодилися (або безкоштовний тариф вважався вічним). За вимогою бізнесу систему було переведено на повністю конфігуровану динамічну модель тривалості:
- Поле
durationDaysу сутностіTariffPlan(за замовчуванням:STARTER: 7,GROWTH: 30,PRO: 30,ENTERPRISE: 365). - Адміністратор у будь-який момент може змінити тривалість (наприклад, встановити Starter на 14 днів для маркетингової акції), і всі нові користувачі автоматично отримають оновлений термін.
🧮 Формули Розрахунку Термінів
sequenceDiagram participant User as Клієнт participant API as Backend API participant DB as PostgreSQL
Note over API,DB: При реєстрації нового акаунту або виборі плану API->>DB: Отримання dbPlan.durationDays (наприклад, 7 днів) API->>API: expiresAt = now() + durationDays * 86,400,000 мс API->>DB: Запис License { expiresAt, isActive: true }
Note over User,API: При кожному запиті GET /api/licenses/my API->>API: isExpired = (now() > expiresAt) API->>API: daysRemaining = Math.max(0, ceil((expiresAt - now()) / 86,400,000)) API-->>User: { planType, isExpired, daysRemaining, expiresAt }Формула обчислення expiresAt
const expiresAt = dbPlan?.durationDays ? new Date(Date.now() + dbPlan.durationDays * 24 * 60 * 60 * 1000) : null;Формули обчислення isExpired та daysRemaining
const isExpired = Boolean(license.expiresAt && new Date(license.expiresAt) < new Date());
const daysRemaining = license.expiresAt ? Math.max( 0, Math.ceil((new Date(license.expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24)), ) : null;🔒 Механізм Блокування Доступу (RequireActiveLicenseGuard)
Коли термін дії ліцензії користувача закінчується (isExpired === true або isActive === false):
-
На рівні Бекенду:
- Захищені ресурсні ендпоінти (наприклад, генерація S3 Presigned URL для збереження каталогів
POST /api/storage/presigned-url) захищені гардомRequireActiveLicenseGuard. - При спробі виклику повертається помилка
403 Forbiddenзі стандартизованим тілом:{"statusCode": 403,"error": "Forbidden","message": "LICENSE_EXPIRED","details": "Your current subscription plan has expired. Please renew or upgrade your plan."}
- Захищені ресурсні ендпоінти (наприклад, генерація S3 Presigned URL для збереження каталогів
-
На рівні Десктопного Клієнта:
- Сторінки робочого функціоналу (
/catalogs,/ai-enrichment,/cloud-sync) автоматично відображають інтерактивний блокувальник<ExpiredPlanBlocker />. - Всі операції блокуються, а користувачу показується чітке повідомлення з кнопкою “Обрати тариф”, яка спрямовує на
/plans. - Сторінки
/plans(вибір та поновлення тарифу) та/settings(профіль користувача) залишаються відкритими.
- Сторінки робочого функціоналу (
-
Миттєве Розблокування (Instant Unlock):
- Користувач переходить на
/plans, обирає бажаний тарифний план (наприклад,PROабоENTERPRISE), викликаєтьсяPOST /api/licenses/select-plan. - Бекенд генерує новий ключ ліцензії, встановлює новий
expiresAt(now + durationDays), скидаєisExpiredуfalse. - Десктопний додаток миттєво знімає блокувальник і повертає повний доступ до каталогів.
- Користувач переходить на