feat: group referral bonus display by tariff
This commit is contained in:
@@ -163,32 +163,66 @@ def _legacy_referral_bonus_periods(settings: Settings) -> List[int]:
|
|||||||
return sorted(int(months) for months in settings.subscription_options)
|
return sorted(int(months) for months in settings.subscription_options)
|
||||||
|
|
||||||
|
|
||||||
|
def _serialize_tariff_period_referral_bonus_details(
|
||||||
|
tariff: Any, lang: str
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
details: List[Dict[str, Any]] = []
|
||||||
|
for months in sorted(int(month) for month in tariff.enabled_periods):
|
||||||
|
inviter_days = tariff.referral_inviter_bonus_days(months)
|
||||||
|
friend_days = tariff.referral_referee_bonus_days(months)
|
||||||
|
if inviter_days is None and friend_days is None:
|
||||||
|
continue
|
||||||
|
details.append(
|
||||||
|
{
|
||||||
|
"id": f"{tariff.key}:{months}",
|
||||||
|
"tariff_key": tariff.key,
|
||||||
|
"tariff_name": tariff.name(lang),
|
||||||
|
"months": int(months),
|
||||||
|
"title": _format_months_title(int(months), lang),
|
||||||
|
"inviter_days": int(inviter_days or 0),
|
||||||
|
"friend_days": int(friend_days or 0),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return details
|
||||||
|
|
||||||
|
|
||||||
def _serialize_tariff_referral_bonus_details(settings: Settings, lang: str) -> List[Dict[str, Any]]:
|
def _serialize_tariff_referral_bonus_details(settings: Settings, lang: str) -> List[Dict[str, Any]]:
|
||||||
tariffs_config = settings.tariffs_config
|
tariffs_config = settings.tariffs_config
|
||||||
if not tariffs_config:
|
if not tariffs_config:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
details: List[Dict[str, Any]] = []
|
period_tariffs = [
|
||||||
for tariff in tariffs_config.enabled_tariffs:
|
tariff for tariff in tariffs_config.enabled_tariffs if tariff.billing_model == "period"
|
||||||
if tariff.billing_model != "period":
|
]
|
||||||
|
if len(period_tariffs) <= 1:
|
||||||
|
return (
|
||||||
|
_serialize_tariff_period_referral_bonus_details(period_tariffs[0], lang)
|
||||||
|
if period_tariffs
|
||||||
|
else []
|
||||||
|
)
|
||||||
|
|
||||||
|
summaries: List[Dict[str, Any]] = []
|
||||||
|
for tariff in period_tariffs:
|
||||||
|
details = _serialize_tariff_period_referral_bonus_details(tariff, lang)
|
||||||
|
if not details:
|
||||||
continue
|
continue
|
||||||
for months in sorted(int(month) for month in tariff.enabled_periods):
|
inviter_values = [int(item["inviter_days"]) for item in details]
|
||||||
inviter_days = tariff.referral_inviter_bonus_days(months)
|
friend_values = [int(item["friend_days"]) for item in details]
|
||||||
friend_days = tariff.referral_referee_bonus_days(months)
|
summaries.append(
|
||||||
if inviter_days is None and friend_days is None:
|
{
|
||||||
continue
|
"id": f"tariff:{tariff.key}",
|
||||||
details.append(
|
"type": "tariff_summary",
|
||||||
{
|
"tariff_key": tariff.key,
|
||||||
"id": f"{tariff.key}:{months}",
|
"tariff_name": tariff.name(lang),
|
||||||
"tariff_key": tariff.key,
|
"title": tariff.name(lang),
|
||||||
"tariff_name": tariff.name(lang),
|
"inviter_min_days": min(inviter_values),
|
||||||
"months": int(months),
|
"inviter_max_days": max(inviter_values),
|
||||||
"title": f"{tariff.name(lang)} - {_format_months_title(int(months), lang)}",
|
"friend_min_days": min(friend_values),
|
||||||
"inviter_days": int(inviter_days or 0),
|
"friend_max_days": max(friend_values),
|
||||||
"friend_days": int(friend_days or 0),
|
"details": details,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return details
|
return summaries
|
||||||
|
|
||||||
|
|
||||||
def _serialize_referral_bonus_details(settings: Settings, lang: str) -> List[Dict[str, Any]]:
|
def _serialize_referral_bonus_details(settings: Settings, lang: str) -> List[Dict[str, Any]]:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional, Union
|
from typing import Any, Callable, Optional, Union
|
||||||
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
||||||
|
|
||||||
from aiogram import Bot, F, Router, types
|
from aiogram import Bot, F, Router, types
|
||||||
@@ -76,31 +76,10 @@ async def referral_command_handler(
|
|||||||
await event.answer()
|
await event.answer()
|
||||||
return
|
return
|
||||||
|
|
||||||
bonus_info_parts = []
|
|
||||||
if getattr(settings, "traffic_sale_mode", False):
|
if getattr(settings, "traffic_sale_mode", False):
|
||||||
bonus_details_str = _("referral_not_available_for_traffic")
|
bonus_details_str = _("referral_not_available_for_traffic")
|
||||||
else:
|
else:
|
||||||
if settings.subscription_options:
|
bonus_details_str = _build_referral_bonus_details_text(settings, _, current_lang)
|
||||||
for months_period_key, _price in sorted(settings.subscription_options.items()):
|
|
||||||
inv_bonus = settings.referral_bonus_inviter.get(months_period_key)
|
|
||||||
ref_bonus = settings.referral_bonus_referee.get(months_period_key)
|
|
||||||
if inv_bonus is not None or ref_bonus is not None:
|
|
||||||
bonus_info_parts.append(
|
|
||||||
_(
|
|
||||||
"referral_bonus_per_period",
|
|
||||||
months=months_period_key,
|
|
||||||
inviter_bonus_days=inv_bonus
|
|
||||||
if inv_bonus is not None
|
|
||||||
else _("no_bonus_placeholder"),
|
|
||||||
referee_bonus_days=ref_bonus
|
|
||||||
if ref_bonus is not None
|
|
||||||
else _("no_bonus_placeholder"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
bonus_details_str = (
|
|
||||||
"\n".join(bonus_info_parts) if bonus_info_parts else _("referral_no_bonuses_configured")
|
|
||||||
)
|
|
||||||
|
|
||||||
referral_stats = await referral_service.get_referral_stats(session, inviter_user_id)
|
referral_stats = await referral_service.get_referral_stats(session, inviter_user_id)
|
||||||
|
|
||||||
@@ -208,6 +187,132 @@ async def referral_action_handler(
|
|||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
Translator = Callable[..., str]
|
||||||
|
|
||||||
|
|
||||||
|
def _period_bonus_text(
|
||||||
|
translator: Translator,
|
||||||
|
*,
|
||||||
|
months: int,
|
||||||
|
inviter_days: Optional[int],
|
||||||
|
referee_days: Optional[int],
|
||||||
|
) -> str:
|
||||||
|
return translator(
|
||||||
|
"referral_bonus_per_period",
|
||||||
|
months=months,
|
||||||
|
inviter_bonus_days=(
|
||||||
|
inviter_days if inviter_days is not None else translator("no_bonus_placeholder")
|
||||||
|
),
|
||||||
|
referee_bonus_days=(
|
||||||
|
referee_days if referee_days is not None else translator("no_bonus_placeholder")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _tariff_period_bonus_entries(tariff: Any) -> list[dict[str, Optional[int]]]:
|
||||||
|
entries: list[dict[str, Optional[int]]] = []
|
||||||
|
for months in sorted(int(month) for month in getattr(tariff, "enabled_periods", [])):
|
||||||
|
inviter_days = tariff.referral_inviter_bonus_days(months)
|
||||||
|
referee_days = tariff.referral_referee_bonus_days(months)
|
||||||
|
if inviter_days is None and referee_days is None:
|
||||||
|
continue
|
||||||
|
entries.append(
|
||||||
|
{
|
||||||
|
"months": months,
|
||||||
|
"inviter_days": inviter_days,
|
||||||
|
"referee_days": referee_days,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def _legacy_period_bonus_entries(settings: Settings) -> list[dict[str, Optional[int]]]:
|
||||||
|
entries: list[dict[str, Optional[int]]] = []
|
||||||
|
for months, _price in sorted(settings.subscription_options.items()):
|
||||||
|
inviter_days = settings.referral_bonus_inviter.get(months)
|
||||||
|
referee_days = settings.referral_bonus_referee.get(months)
|
||||||
|
if inviter_days is None and referee_days is None:
|
||||||
|
continue
|
||||||
|
entries.append(
|
||||||
|
{
|
||||||
|
"months": int(months),
|
||||||
|
"inviter_days": inviter_days,
|
||||||
|
"referee_days": referee_days,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return entries
|
||||||
|
|
||||||
|
|
||||||
|
def _bonus_days_range(translator: Translator, values: list[int]) -> str:
|
||||||
|
return translator(
|
||||||
|
"referral_bonus_days_range",
|
||||||
|
min_days=min(values),
|
||||||
|
max_days=max(values),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _build_referral_bonus_details_text(
|
||||||
|
settings: Settings, translator: Translator, current_lang: str
|
||||||
|
) -> str:
|
||||||
|
tariffs_config = settings.tariffs_config
|
||||||
|
if not tariffs_config:
|
||||||
|
bonus_info_parts = [
|
||||||
|
_period_bonus_text(
|
||||||
|
translator,
|
||||||
|
months=int(entry["months"] or 0),
|
||||||
|
inviter_days=entry["inviter_days"],
|
||||||
|
referee_days=entry["referee_days"],
|
||||||
|
)
|
||||||
|
for entry in _legacy_period_bonus_entries(settings)
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
"\n".join(bonus_info_parts)
|
||||||
|
if bonus_info_parts
|
||||||
|
else translator("referral_no_bonuses_configured")
|
||||||
|
)
|
||||||
|
|
||||||
|
period_tariffs = [
|
||||||
|
tariff for tariff in tariffs_config.enabled_tariffs if tariff.billing_model == "period"
|
||||||
|
]
|
||||||
|
if len(period_tariffs) <= 1:
|
||||||
|
entries = _tariff_period_bonus_entries(period_tariffs[0]) if period_tariffs else []
|
||||||
|
bonus_info_parts = [
|
||||||
|
_period_bonus_text(
|
||||||
|
translator,
|
||||||
|
months=int(entry["months"] or 0),
|
||||||
|
inviter_days=entry["inviter_days"],
|
||||||
|
referee_days=entry["referee_days"],
|
||||||
|
)
|
||||||
|
for entry in entries
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
"\n".join(bonus_info_parts)
|
||||||
|
if bonus_info_parts
|
||||||
|
else translator("referral_no_bonuses_configured")
|
||||||
|
)
|
||||||
|
|
||||||
|
bonus_info_parts = []
|
||||||
|
for tariff in period_tariffs:
|
||||||
|
entries = _tariff_period_bonus_entries(tariff)
|
||||||
|
if not entries:
|
||||||
|
continue
|
||||||
|
inviter_values = [int(entry["inviter_days"] or 0) for entry in entries]
|
||||||
|
referee_values = [int(entry["referee_days"] or 0) for entry in entries]
|
||||||
|
bonus_info_parts.append(
|
||||||
|
translator(
|
||||||
|
"referral_bonus_tariff_range",
|
||||||
|
tariff_name=tariff.name(current_lang),
|
||||||
|
inviter_bonus_range=_bonus_days_range(translator, inviter_values),
|
||||||
|
referee_bonus_range=_bonus_days_range(translator, referee_values),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
"\n".join(bonus_info_parts)
|
||||||
|
if bonus_info_parts
|
||||||
|
else translator("referral_no_bonuses_configured")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _build_webapp_referral_link(
|
def _build_webapp_referral_link(
|
||||||
base_url: Optional[str], referral_code: Optional[str]
|
base_url: Optional[str], referral_code: Optional[str]
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
|
|||||||
@@ -1537,6 +1537,89 @@ a {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.referral-tariff-dropdown {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--surface-sheen-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-summary {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 10px 11px;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-copy {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-copy strong {
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-copy small {
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-dropdown summary:hover .premium-server-help-icon,
|
||||||
|
.referral-tariff-dropdown[open] .premium-server-help-icon {
|
||||||
|
color: var(--accent);
|
||||||
|
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-details {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
padding: 0 8px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
transform-origin: top;
|
||||||
|
transition:
|
||||||
|
grid-template-rows 0.22s ease,
|
||||||
|
padding 0.22s ease,
|
||||||
|
opacity 0.18s ease,
|
||||||
|
transform 0.22s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-details > div {
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-detail-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-tariff-dropdown[open] .referral-tariff-details {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
padding: 0 8px 8px;
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.referral-bonus-row-nested {
|
||||||
|
background: var(--surface);
|
||||||
|
}
|
||||||
|
|
||||||
.settings-profile {
|
.settings-profile {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script>
|
<script>
|
||||||
import { Copy, Gift, Ticket, TriangleAlert } from "$components/ui/icons.js";
|
import { CircleQuestionMark, Copy, Gift, Ticket, TriangleAlert } from "$components/ui/icons.js";
|
||||||
import { Tooltip } from "$components/ui/primitives.js";
|
import { Tooltip } from "$components/ui/primitives.js";
|
||||||
|
|
||||||
import Button from "$components/ui/button.svelte";
|
import Button from "$components/ui/button.svelte";
|
||||||
@@ -21,6 +21,17 @@
|
|||||||
export let clearPromoFieldError = () => {};
|
export let clearPromoFieldError = () => {};
|
||||||
export let copyText = () => {};
|
export let copyText = () => {};
|
||||||
export let t = (key) => key;
|
export let t = (key) => key;
|
||||||
|
|
||||||
|
$: tariffBonusSummaries = referralBonusDetails.filter((bonus) => Array.isArray(bonus.details));
|
||||||
|
$: periodBonusDetails = referralBonusDetails.filter((bonus) => !Array.isArray(bonus.details));
|
||||||
|
$: usesTariffBonusSummaries = tariffBonusSummaries.length > 0;
|
||||||
|
|
||||||
|
function daysRange(minDays, maxDays) {
|
||||||
|
return t("wa_referral_bonus_range_days", {
|
||||||
|
min: Number(minDays || 0),
|
||||||
|
max: Number(maxDays || 0),
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main class="content with-nav">
|
<main class="content with-nav">
|
||||||
@@ -55,20 +66,68 @@
|
|||||||
<small>{t("wa_referral_bonus_friend_days", { days: referralWelcomeBonusDays })}</small>
|
<small>{t("wa_referral_bonus_friend_days", { days: referralWelcomeBonusDays })}</small>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if referralBonusDetails.length}
|
{#if usesTariffBonusSummaries}
|
||||||
|
<p class="referral-bonus-intro">{t("wa_referral_bonus_depends_on_tariff")}</p>
|
||||||
|
{:else if periodBonusDetails.length}
|
||||||
<p class="referral-bonus-intro">{t("wa_referral_bonus_paid_intro")}</p>
|
<p class="referral-bonus-intro">{t("wa_referral_bonus_paid_intro")}</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#each referralBonusDetails as bonus, index (bonus.id || `${bonus.tariff_key || "legacy"}:${bonus.months || index}`)}
|
{#if usesTariffBonusSummaries}
|
||||||
<div class="referral-bonus-row">
|
{#each tariffBonusSummaries as tariffBonus, index (tariffBonus.id || `tariff:${tariffBonus.tariff_key || index}`)}
|
||||||
<strong>{bonus.title || `${bonus.months || "?"}`}</strong>
|
<details class="referral-tariff-dropdown">
|
||||||
<small
|
<summary class="referral-tariff-summary">
|
||||||
>{t("wa_referral_bonus_you_days", { days: Number(bonus.inviter_days || 0) })}</small
|
<span class="referral-tariff-copy">
|
||||||
>
|
<strong>{tariffBonus.title || tariffBonus.tariff_name}</strong>
|
||||||
<small
|
<small>
|
||||||
>{t("wa_referral_bonus_friend_days", { days: Number(bonus.friend_days || 0) })}</small
|
{t("wa_referral_bonus_you_range", {
|
||||||
>
|
range: daysRange(tariffBonus.inviter_min_days, tariffBonus.inviter_max_days),
|
||||||
</div>
|
})}
|
||||||
{/each}
|
</small>
|
||||||
|
<small>
|
||||||
|
{t("wa_referral_bonus_friend_range", {
|
||||||
|
range: daysRange(tariffBonus.friend_min_days, tariffBonus.friend_max_days),
|
||||||
|
})}
|
||||||
|
</small>
|
||||||
|
</span>
|
||||||
|
<CircleQuestionMark class="premium-server-help-icon" size={16} />
|
||||||
|
</summary>
|
||||||
|
<div class="referral-tariff-details">
|
||||||
|
<div class="referral-tariff-detail-list">
|
||||||
|
{#each tariffBonus.details || [] as bonus, detailIndex (bonus.id || `${tariffBonus.tariff_key || index}:${bonus.months || detailIndex}`)}
|
||||||
|
<div class="referral-bonus-row referral-bonus-row-nested">
|
||||||
|
<strong>{bonus.title || `${bonus.months || "?"}`}</strong>
|
||||||
|
<small
|
||||||
|
>{t("wa_referral_bonus_you_days", {
|
||||||
|
days: Number(bonus.inviter_days || 0),
|
||||||
|
})}</small
|
||||||
|
>
|
||||||
|
<small
|
||||||
|
>{t("wa_referral_bonus_friend_days", {
|
||||||
|
days: Number(bonus.friend_days || 0),
|
||||||
|
})}</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{/each}
|
||||||
|
{:else}
|
||||||
|
{#each periodBonusDetails as bonus, index (bonus.id || `${bonus.tariff_key || "legacy"}:${bonus.months || index}`)}
|
||||||
|
<div class="referral-bonus-row">
|
||||||
|
<strong>{bonus.title || `${bonus.months || "?"}`}</strong>
|
||||||
|
<small
|
||||||
|
>{t("wa_referral_bonus_you_days", {
|
||||||
|
days: Number(bonus.inviter_days || 0),
|
||||||
|
})}</small
|
||||||
|
>
|
||||||
|
<small
|
||||||
|
>{t("wa_referral_bonus_friend_days", {
|
||||||
|
days: Number(bonus.friend_days || 0),
|
||||||
|
})}</small
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<StatusMessage>{t("wa_referral_bonus_not_configured")}</StatusMessage>
|
<StatusMessage>{t("wa_referral_bonus_not_configured")}</StatusMessage>
|
||||||
|
|||||||
@@ -97,6 +97,8 @@
|
|||||||
"no_button": "No",
|
"no_button": "No",
|
||||||
"referral_program_info_new": "🎁 <b>Referral Program</b>\n\n📊 <b>Your stats:</b>\n👥 Friends invited: <b>{invited_count}</b>\n💳 Purchased subscription: <b>{purchased_count}</b>\n\n🔗 Telegram link:\n<code>{referral_link}</code>{webapp_link_section}\n\n💰 <b>Invitation bonuses:</b>\n{bonus_details}\n\n📢 Share the link with friends and get bonuses!",
|
"referral_program_info_new": "🎁 <b>Referral Program</b>\n\n📊 <b>Your stats:</b>\n👥 Friends invited: <b>{invited_count}</b>\n💳 Purchased subscription: <b>{purchased_count}</b>\n\n🔗 Telegram link:\n<code>{referral_link}</code>{webapp_link_section}\n\n💰 <b>Invitation bonuses:</b>\n{bonus_details}\n\n📢 Share the link with friends and get bonuses!",
|
||||||
"referral_bonus_per_period": "\n\n🎁 For a friend's {months}-month subscription:\n ➢ You: <b>{inviter_bonus_days} days</b>\n ➢ Friend: <b>{referee_bonus_days} days</b>",
|
"referral_bonus_per_period": "\n\n🎁 For a friend's {months}-month subscription:\n ➢ You: <b>{inviter_bonus_days} days</b>\n ➢ Friend: <b>{referee_bonus_days} days</b>",
|
||||||
|
"referral_bonus_days_range": "from {min_days} to {max_days} days",
|
||||||
|
"referral_bonus_tariff_range": "\n\n🎁 {tariff_name}:\n ➢ You: <b>{inviter_bonus_range}</b>\n ➢ Friend: <b>{referee_bonus_range}</b>",
|
||||||
"referral_not_available_for_traffic": "Referral bonuses are not available for traffic packages.",
|
"referral_not_available_for_traffic": "Referral bonuses are not available for traffic packages.",
|
||||||
"referral_share_message_button": "📩 Message for friend",
|
"referral_share_message_button": "📩 Message for friend",
|
||||||
"referral_friend_message": "🚀 Hey! Try this service - it's fast, reliable and affordable!\n\n🎁 Use my link to get bonus days with your subscription!\n\n{referral_link}",
|
"referral_friend_message": "🚀 Hey! Try this service - it's fast, reliable and affordable!\n\n🎁 Use my link to get bonus days with your subscription!\n\n{referral_link}",
|
||||||
@@ -778,8 +780,12 @@
|
|||||||
"wa_referral_bonus_once_note": "Bonus is granted only once per invited user after their first payment.",
|
"wa_referral_bonus_once_note": "Bonus is granted only once per invited user after their first payment.",
|
||||||
"wa_referral_bonus_registration_title": "For registration via referral link",
|
"wa_referral_bonus_registration_title": "For registration via referral link",
|
||||||
"wa_referral_bonus_paid_intro": "If a friend pays for a subscription:",
|
"wa_referral_bonus_paid_intro": "If a friend pays for a subscription:",
|
||||||
|
"wa_referral_bonus_depends_on_tariff": "Bonus depends on the tariff and payment period your friend chooses",
|
||||||
|
"wa_referral_bonus_range_days": "from {min} to {max} days",
|
||||||
"wa_referral_bonus_you_days": "You: +{days} days",
|
"wa_referral_bonus_you_days": "You: +{days} days",
|
||||||
"wa_referral_bonus_friend_days": "Friend: +{days} days",
|
"wa_referral_bonus_friend_days": "Friend: +{days} days",
|
||||||
|
"wa_referral_bonus_you_range": "You: {range}",
|
||||||
|
"wa_referral_bonus_friend_range": "Friend: {range}",
|
||||||
"wa_referral_bonus_not_configured": "Referral bonuses are not configured",
|
"wa_referral_bonus_not_configured": "Referral bonuses are not configured",
|
||||||
"wa_promo_enter": "Enter promo code",
|
"wa_promo_enter": "Enter promo code",
|
||||||
"wa_promo_activated_until": "Promo code activated. Subscription until {date}",
|
"wa_promo_activated_until": "Promo code activated. Subscription until {date}",
|
||||||
|
|||||||
@@ -97,6 +97,8 @@
|
|||||||
"no_button": "Нет",
|
"no_button": "Нет",
|
||||||
"referral_program_info_new": "🎁 <b>Реферальная программа</b>\n\n📊 <b>Твоя статистика:</b>\n👥 Приглашено друзей: <b>{invited_count}</b>\n💳 Купили подписку: <b>{purchased_count}</b>\n\n🔗 Telegram ссылка:\n<code>{referral_link}</code>{webapp_link_section}\n\n💰 <b>Бонусы за приглашения:</b>\n{bonus_details}\n\n📢 Поделись ссылкой с друзьями и получай бонусы!",
|
"referral_program_info_new": "🎁 <b>Реферальная программа</b>\n\n📊 <b>Твоя статистика:</b>\n👥 Приглашено друзей: <b>{invited_count}</b>\n💳 Купили подписку: <b>{purchased_count}</b>\n\n🔗 Telegram ссылка:\n<code>{referral_link}</code>{webapp_link_section}\n\n💰 <b>Бонусы за приглашения:</b>\n{bonus_details}\n\n📢 Поделись ссылкой с друзьями и получай бонусы!",
|
||||||
"referral_bonus_per_period": "\n\n🎁 За {months}-мес. подписку друга:\n ➢ Вы: <b>{inviter_bonus_days} дн.</b>\n ➢ Друг: <b>{referee_bonus_days} дн.</b>",
|
"referral_bonus_per_period": "\n\n🎁 За {months}-мес. подписку друга:\n ➢ Вы: <b>{inviter_bonus_days} дн.</b>\n ➢ Друг: <b>{referee_bonus_days} дн.</b>",
|
||||||
|
"referral_bonus_days_range": "от {min_days} до {max_days} дн.",
|
||||||
|
"referral_bonus_tariff_range": "\n\n🎁 {tariff_name}:\n ➢ Вы: <b>{inviter_bonus_range}</b>\n ➢ Друг: <b>{referee_bonus_range}</b>",
|
||||||
"referral_not_available_for_traffic": "Для пакетов трафика реферальные бонусы не начисляются.",
|
"referral_not_available_for_traffic": "Для пакетов трафика реферальные бонусы не начисляются.",
|
||||||
"referral_share_message_button": "📩 Сообщение для друга",
|
"referral_share_message_button": "📩 Сообщение для друга",
|
||||||
"referral_friend_message": "🚀 Привет! Попробуй этот сервис - быстрый, надёжный и доступный!\n\n🎁 По моей ссылке тебе дадут бонусные дни к подписке!\n\n{referral_link}",
|
"referral_friend_message": "🚀 Привет! Попробуй этот сервис - быстрый, надёжный и доступный!\n\n🎁 По моей ссылке тебе дадут бонусные дни к подписке!\n\n{referral_link}",
|
||||||
@@ -778,8 +780,12 @@
|
|||||||
"wa_referral_bonus_once_note": "Бонус за приглашённого начисляется только один раз, после его первой оплаты.",
|
"wa_referral_bonus_once_note": "Бонус за приглашённого начисляется только один раз, после его первой оплаты.",
|
||||||
"wa_referral_bonus_registration_title": "За регистрацию по реферальной ссылке",
|
"wa_referral_bonus_registration_title": "За регистрацию по реферальной ссылке",
|
||||||
"wa_referral_bonus_paid_intro": "Если друг оплатит подписку:",
|
"wa_referral_bonus_paid_intro": "Если друг оплатит подписку:",
|
||||||
|
"wa_referral_bonus_depends_on_tariff": "Бонус зависит от тарифа и периода оплаты друга",
|
||||||
|
"wa_referral_bonus_range_days": "от {min} до {max} дней",
|
||||||
"wa_referral_bonus_you_days": "Вам: +{days} дней",
|
"wa_referral_bonus_you_days": "Вам: +{days} дней",
|
||||||
"wa_referral_bonus_friend_days": "Другу: +{days} дней",
|
"wa_referral_bonus_friend_days": "Другу: +{days} дней",
|
||||||
|
"wa_referral_bonus_you_range": "Вам: {range}",
|
||||||
|
"wa_referral_bonus_friend_range": "Другу: {range}",
|
||||||
"wa_referral_bonus_not_configured": "Реферальные бонусы не настроены",
|
"wa_referral_bonus_not_configured": "Реферальные бонусы не настроены",
|
||||||
"wa_promo_enter": "Введите промокод",
|
"wa_promo_enter": "Введите промокод",
|
||||||
"wa_promo_activated_until": "Промокод активирован. Подписка до {date}",
|
"wa_promo_activated_until": "Промокод активирован. Подписка до {date}",
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from bot.keyboards.inline.user_keyboards import (
|
|||||||
payment_methods_back_callback,
|
payment_methods_back_callback,
|
||||||
payment_options_back_callback,
|
payment_options_back_callback,
|
||||||
)
|
)
|
||||||
|
from config.tariffs_config import TariffsConfig
|
||||||
|
|
||||||
|
|
||||||
class JsonI18nStub:
|
class JsonI18nStub:
|
||||||
@@ -304,3 +305,95 @@ class UserBotMenuTests(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertLess(text.index("Telegram link:"), text.index("Web link:"))
|
self.assertLess(text.index("Telegram link:"), text.index("Web link:"))
|
||||||
self.assertLess(text.index("Web link:"), text.index("Invitation bonuses:"))
|
self.assertLess(text.index("Web link:"), text.index("Invitation bonuses:"))
|
||||||
|
|
||||||
|
def test_single_tariff_referral_text_uses_period_rows_without_tariff_name(self):
|
||||||
|
settings = SimpleNamespace(
|
||||||
|
tariffs_config=TariffsConfig.model_validate(
|
||||||
|
{
|
||||||
|
"default_tariff": "standard",
|
||||||
|
"tariffs": [
|
||||||
|
{
|
||||||
|
"key": "standard",
|
||||||
|
"names": {"en": "Standard"},
|
||||||
|
"descriptions": {},
|
||||||
|
"squad_uuids": ["standard"],
|
||||||
|
"billing_model": "period",
|
||||||
|
"monthly_gb": 100,
|
||||||
|
"prices_rub": {"2": 400, "4": 800},
|
||||||
|
"prices_stars": {},
|
||||||
|
"referral_bonus_days_inviter": {"2": 5, "4": 10},
|
||||||
|
"referral_bonus_days_referee": {"2": 1, "4": 2},
|
||||||
|
"enabled_periods": [2, 4],
|
||||||
|
"enabled": True,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
subscription_options={},
|
||||||
|
referral_bonus_inviter={},
|
||||||
|
referral_bonus_referee={},
|
||||||
|
)
|
||||||
|
|
||||||
|
text = referral._build_referral_bonus_details_text(
|
||||||
|
settings,
|
||||||
|
lambda key, **kwargs: self.i18n.gettext("en", key, **kwargs),
|
||||||
|
"en",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn("For a friend's 2-month subscription", text)
|
||||||
|
self.assertIn("For a friend's 4-month subscription", text)
|
||||||
|
self.assertNotIn("Standard", text)
|
||||||
|
|
||||||
|
def test_multiple_tariff_referral_text_uses_tariff_ranges(self):
|
||||||
|
settings = SimpleNamespace(
|
||||||
|
tariffs_config=TariffsConfig.model_validate(
|
||||||
|
{
|
||||||
|
"default_tariff": "standard",
|
||||||
|
"tariffs": [
|
||||||
|
{
|
||||||
|
"key": "standard",
|
||||||
|
"names": {"en": "Standard"},
|
||||||
|
"descriptions": {},
|
||||||
|
"squad_uuids": ["standard"],
|
||||||
|
"billing_model": "period",
|
||||||
|
"monthly_gb": 100,
|
||||||
|
"prices_rub": {"2": 400, "4": 800},
|
||||||
|
"prices_stars": {},
|
||||||
|
"referral_bonus_days_inviter": {"2": 5, "4": 10},
|
||||||
|
"referral_bonus_days_referee": {"2": 1, "4": 2},
|
||||||
|
"enabled_periods": [2, 4],
|
||||||
|
"enabled": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "premium",
|
||||||
|
"names": {"en": "Premium"},
|
||||||
|
"descriptions": {},
|
||||||
|
"squad_uuids": ["premium"],
|
||||||
|
"billing_model": "period",
|
||||||
|
"monthly_gb": 500,
|
||||||
|
"prices_rub": {"1": 700, "3": 1800},
|
||||||
|
"prices_stars": {},
|
||||||
|
"referral_bonus_days_inviter": {"1": 8, "3": 24},
|
||||||
|
"referral_bonus_days_referee": {"1": 3, "3": 9},
|
||||||
|
"enabled_periods": [1, 3],
|
||||||
|
"enabled": True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
subscription_options={},
|
||||||
|
referral_bonus_inviter={},
|
||||||
|
referral_bonus_referee={},
|
||||||
|
)
|
||||||
|
|
||||||
|
text = referral._build_referral_bonus_details_text(
|
||||||
|
settings,
|
||||||
|
lambda key, **kwargs: self.i18n.gettext("en", key, **kwargs),
|
||||||
|
"en",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertIn("Standard", text)
|
||||||
|
self.assertIn("Premium", text)
|
||||||
|
self.assertIn("from 5 to 10 days", text)
|
||||||
|
self.assertIn("from 8 to 24 days", text)
|
||||||
|
self.assertNotIn("2-month subscription", text)
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"tariff_key": "standard",
|
"tariff_key": "standard",
|
||||||
"tariff_name": "Standard",
|
"tariff_name": "Standard",
|
||||||
"months": 2,
|
"months": 2,
|
||||||
"title": "Standard - 2 months",
|
"title": "2 months",
|
||||||
"inviter_days": 5,
|
"inviter_days": 5,
|
||||||
"friend_days": 1,
|
"friend_days": 1,
|
||||||
},
|
},
|
||||||
@@ -150,7 +150,7 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"tariff_key": "standard",
|
"tariff_key": "standard",
|
||||||
"tariff_name": "Standard",
|
"tariff_name": "Standard",
|
||||||
"months": 4,
|
"months": 4,
|
||||||
"title": "Standard - 4 months",
|
"title": "4 months",
|
||||||
"inviter_days": 10,
|
"inviter_days": 10,
|
||||||
"friend_days": 2,
|
"friend_days": 2,
|
||||||
},
|
},
|
||||||
@@ -159,7 +159,7 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"tariff_key": "standard",
|
"tariff_key": "standard",
|
||||||
"tariff_name": "Standard",
|
"tariff_name": "Standard",
|
||||||
"months": 8,
|
"months": 8,
|
||||||
"title": "Standard - 8 months",
|
"title": "8 months",
|
||||||
"inviter_days": 0,
|
"inviter_days": 0,
|
||||||
"friend_days": 4,
|
"friend_days": 4,
|
||||||
},
|
},
|
||||||
@@ -168,13 +168,78 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"tariff_key": "standard",
|
"tariff_key": "standard",
|
||||||
"tariff_name": "Standard",
|
"tariff_name": "Standard",
|
||||||
"months": 16,
|
"months": 16,
|
||||||
"title": "Standard - 16 months",
|
"title": "16 months",
|
||||||
"inviter_days": 40,
|
"inviter_days": 40,
|
||||||
"friend_days": 0,
|
"friend_days": 0,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_referral_bonus_details_group_multiple_tariffs(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
path = Path(tmpdir) / "tariffs.json"
|
||||||
|
path.write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"default_tariff": "standard",
|
||||||
|
"tariffs": [
|
||||||
|
{
|
||||||
|
"key": "standard",
|
||||||
|
"names": {"en": "Standard"},
|
||||||
|
"descriptions": {},
|
||||||
|
"squad_uuids": ["standard"],
|
||||||
|
"billing_model": "period",
|
||||||
|
"monthly_gb": 100,
|
||||||
|
"prices_rub": {"2": 400, "4": 800},
|
||||||
|
"prices_stars": {},
|
||||||
|
"referral_bonus_days_inviter": {"2": 5, "4": 10},
|
||||||
|
"referral_bonus_days_referee": {"2": 1, "4": 2},
|
||||||
|
"enabled_periods": [2, 4],
|
||||||
|
"enabled": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "premium",
|
||||||
|
"names": {"en": "Premium"},
|
||||||
|
"descriptions": {},
|
||||||
|
"squad_uuids": ["premium"],
|
||||||
|
"billing_model": "period",
|
||||||
|
"monthly_gb": 500,
|
||||||
|
"prices_rub": {"1": 700, "3": 1800},
|
||||||
|
"prices_stars": {},
|
||||||
|
"referral_bonus_days_inviter": {"1": 8, "3": 24},
|
||||||
|
"referral_bonus_days_referee": {"1": 3, "3": 9},
|
||||||
|
"enabled_periods": [1, 3],
|
||||||
|
"enabled": True,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
settings = Settings(
|
||||||
|
_env_file=None,
|
||||||
|
BOT_TOKEN="token",
|
||||||
|
POSTGRES_USER="app_user",
|
||||||
|
POSTGRES_PASSWORD="app_password",
|
||||||
|
TARIFFS_CONFIG_PATH=str(path),
|
||||||
|
)
|
||||||
|
|
||||||
|
details = subscription_webapp._serialize_referral_bonus_details(settings, "en")
|
||||||
|
|
||||||
|
self.assertEqual([item["type"] for item in details], ["tariff_summary", "tariff_summary"])
|
||||||
|
self.assertEqual(details[0]["title"], "Standard")
|
||||||
|
self.assertEqual(details[0]["inviter_min_days"], 5)
|
||||||
|
self.assertEqual(details[0]["inviter_max_days"], 10)
|
||||||
|
self.assertEqual(details[0]["friend_min_days"], 1)
|
||||||
|
self.assertEqual(details[0]["friend_max_days"], 2)
|
||||||
|
self.assertEqual(
|
||||||
|
[item["title"] for item in details[0]["details"]],
|
||||||
|
["2 months", "4 months"],
|
||||||
|
)
|
||||||
|
self.assertEqual(details[1]["title"], "Premium")
|
||||||
|
self.assertEqual(details[1]["inviter_min_days"], 8)
|
||||||
|
self.assertEqual(details[1]["inviter_max_days"], 24)
|
||||||
|
|
||||||
def test_subscription_template_does_not_block_on_telegram_sdk(self):
|
def test_subscription_template_does_not_block_on_telegram_sdk(self):
|
||||||
html = subscription_webapp.TEMPLATE_PATH.read_text(encoding="utf-8")
|
html = subscription_webapp.TEMPLATE_PATH.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user