Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions botspot/components/middlewares/i18n_strings.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,16 @@
"en": "Global free trial budget reached for today (hard cost cap). Try tomorrow or add credits in /account.",
"ru": "Глобальный бюджет бесплатного периода на сегодня достигнут (лимит затрат). Попробуйте завтра или пополните баланс через /account.",
},
"trial_daily_cap": {
"en": (
"Daily free limit reached. You have {minutes_left} minutes left today. "
"Resets at {resets_at}."
),
"ru": (
"Дневной бесплатный лимит исчерпан. Сегодня осталось {minutes_left} мин. "
"Сброс в {resets_at}."
),
},
"subscription_picker": {
"en": "Choose a plan. Pay with Telegram Stars. Credit packs stay available as pay-as-you-go.",
"ru": "Выберите план. Оплата — Telegram Stars. Пакеты кредитов остаются как оплата по факту.",
Expand Down
8 changes: 7 additions & 1 deletion botspot/components/new/DEV_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,10 @@ chat_binder.py -> components/features/chat_binder.py

subscription_manager stays in `components/new/` (package). Public re-export:
`botspot/subscription_manager.py`. Enable with `BOTSPOT_SUBSCRIPTION_MANAGER_ENABLED`
and MongoDB. Friends/admins bypass via `is_friend` / `is_admin`.
and MongoDB. Friends/admins bypass via `is_friend` / `is_admin`.

Per-user daily trial caps (0 = off):
`BOTSPOT_SUBSCRIPTION_MANAGER_TRIAL_USER_AUDIO_MINUTES_PER_DAY`,
`BOTSPOT_SUBSCRIPTION_MANAGER_TRIAL_USER_AUDIO_REQUESTS_PER_DAY`,
`BOTSPOT_SUBSCRIPTION_MANAGER_TRIAL_USER_CHAT_REQUESTS_PER_DAY`.
`BOTSPOT_SUBSCRIPTION_MANAGER_TRIAL_DURATION_DAYS=0` means no expiry.
6 changes: 6 additions & 0 deletions botspot/components/new/subscription_manager/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
Credits ledger, Telegram Stars one-off invoices and native subscriptions,
Stripe/YooKassa/TON adapters (Stripe customers + Customer Portal), trial caps,
and friend/admin bypass.

Per-user daily trial caps (env prefix ``BOTSPOT_SUBSCRIPTION_MANAGER_``, 0 = off):
``TRIAL_USER_AUDIO_MINUTES_PER_DAY``, ``TRIAL_USER_AUDIO_REQUESTS_PER_DAY``,
``TRIAL_USER_CHAT_REQUESTS_PER_DAY``. ``TRIAL_DURATION_DAYS=0`` means no expiry
(public free tier). Hitting a per-user daily cap sets ``Decision.reason`` to
``trial_daily_cap``.
"""

from __future__ import annotations
Expand Down
23 changes: 22 additions & 1 deletion botspot/components/new/subscription_manager/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@
from botspot.components.new.subscription_manager import stripe as stripe_adapter
from botspot.components.new.subscription_manager import ton as ton_adapter
from botspot.components.new.subscription_manager import yookassa as yookassa_adapter
from botspot.components.new.subscription_manager.trial import TrialLimiter
from botspot.components.new.subscription_manager.trial import (
REASON_TRIAL_DAILY_CAP,
TrialLimiter,
)
from botspot.core.errors import SubscriptionValidationError
from botspot.utils.internal import get_logger

Expand Down Expand Up @@ -438,7 +441,25 @@ async def authorize(
)
if trial.allowed:
return Decision(allowed=True, source=SOURCE_TRIAL, reason="trial")
return self._deny_authorize(trial, sub, credits, needed_minutes)

def _deny_authorize(self, trial, sub, credits: int, needed_minutes: float) -> Decision:
if trial.message_key == REASON_TRIAL_DAILY_CAP:
minutes_left = (
0.0 if trial.minutes_left_today is None else float(trial.minutes_left_today)
)
resets_at = trial.resets_at or ""
return Decision(
allowed=False,
source=SOURCE_NONE,
reason=REASON_TRIAL_DAILY_CAP,
message_key=REASON_TRIAL_DAILY_CAP,
user_message=t(
REASON_TRIAL_DAILY_CAP,
minutes_left=f"{minutes_left:.1f}",
resets_at=resets_at,
),
)
if sub and needed_minutes > 0:
message_key = "subscription_minutes_exhausted"
elif credits:
Expand Down
1 change: 1 addition & 0 deletions botspot/components/new/subscription_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ class Decision(BaseModel):
hold_id: str | None = None
reason: str = ""
message_key: str = ""
user_message: str | None = None


class Balance(BaseModel):
Expand Down
13 changes: 11 additions & 2 deletions botspot/components/new/subscription_manager/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,13 @@


class SubscriptionManagerSettings(BaseSettings):
"""Env prefix ``BOTSPOT_SUBSCRIPTION_MANAGER_``."""
"""Env prefix ``BOTSPOT_SUBSCRIPTION_MANAGER_``.

Per-user daily trial caps (0 = off): ``TRIAL_USER_AUDIO_MINUTES_PER_DAY``,
``TRIAL_USER_AUDIO_REQUESTS_PER_DAY``, ``TRIAL_USER_CHAT_REQUESTS_PER_DAY``.
``TRIAL_DURATION_DAYS=0`` means no expiry (public free tier); default 7 expires
after a week.
"""

enabled: bool = False
register_commands: bool = True
Expand All @@ -88,11 +94,14 @@ class SubscriptionManagerSettings(BaseSettings):
plans_json: str = ""
packs_json: str = ""
trial_enabled: bool = True
trial_duration_days: int = 7
trial_duration_days: int = 7 # 0 = no expiry (public free tier)
trial_user_audio_requests_total: int = 20
trial_user_audio_minutes_total: float = 90.0
trial_user_chat_requests_total: int = 200
trial_user_chat_tokens_total: int = 60000
trial_user_audio_minutes_per_day: float = 0 # 0 = off
trial_user_audio_requests_per_day: int = 0 # 0 = off
trial_user_chat_requests_per_day: int = 0 # 0 = off
trial_global_audio_requests_per_day: int = 100
trial_global_audio_minutes_per_day: float = 480.0
trial_global_chat_requests_per_day: int = 1000
Expand Down
Loading
Loading