feat: two-sided referral program
Each user gets a short ref_code (backfilled lazily for pre-existing accounts too) and a shareable t.me/<bot>?start=ref_<code> link, new "Пригласить друга" menu item shows it plus how many referrals actually converted and any bonus days waiting to be applied. Reward fires once, on the referred user's first subscription of any kind (free, gift, or paid) — not on signup, so an unconverted click never pays out. Both sides get REFERRAL_BONUS_DAYS (config.py/.env, default 3): the referrer's day count comes from settings.py's live-read pattern, same as prices/HWID, so it's tunable without a restart even before a panel UI exists for it. Bonus extends an active subscription directly if the recipient has one, otherwise accumulates in bonus_days_pending and gets folded into whichever subscription they create next (redeemed automatically inside create_subscription, one choke point regardless of which of bot.py's several call sites created it — free trial, gift code, paid, or admin grant). Guards: no self-referral, referrer must exist, first-touch attribution only (a second ?start=ref_ link never overwrites it), and only takes for genuinely new accounts (no existing subscriptions) — attaching a referrer to an already-active user was never the intent. Tested two ways, matching this repo's usual db.py-can-be-imported- standalone / bot.py-needs-a-workaround split: 16 checks against a real isolated sqlite db for the db.py logic (attribution, both reward paths, double-reward guard, pending-bonus fold-in), then 9 more through an actual `import bot` — aiogram/fastapi now have Python 3.14 wheels so this imported for real rather than needing AST-extraction, modulo one old blocker (xray_manager still imports the Unix-only fcntl for its file lock) worked around with a tiny fake fcntl module in sys.modules, same spirit as the fcntl shim already used elsewhere in this project's history. Real start_deeplink and cb_referral calls, get_me() mocked to avoid a live Telegram API call. Not done: admin-panel UI toggle for REFERRAL_ENABLED/REFERRAL_BONUS_DAYS (currently .env-only, like several other business tunables were before they got a settings-page treatment) and a docs-tab writeup — happy to add both if wanted, scoped this pass to the mechanic itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
695771c79a
commit
6546d5bed1
4 changed files with 156 additions and 3 deletions
14
settings.py
14
settings.py
|
|
@ -87,3 +87,17 @@ def get_hwid_settings() -> dict:
|
|||
"enabled": _bool(raw.get("HWID_LIMIT_ENABLED"), config.HWID_LIMIT_ENABLED),
|
||||
"fallback_limit": limit if limit > 0 else config.HWID_FALLBACK_LIMIT,
|
||||
}
|
||||
|
||||
|
||||
def get_referral_settings() -> dict:
|
||||
raw = legal.read_env_vars(["REFERRAL_ENABLED", "REFERRAL_BONUS_DAYS"])
|
||||
days = _positive_int(raw.get("REFERRAL_BONUS_DAYS"), config.REFERRAL_BONUS_DAYS)
|
||||
return {
|
||||
"enabled": _bool(raw.get("REFERRAL_ENABLED"), config.REFERRAL_ENABLED),
|
||||
"bonus_days": days if days > 0 else config.REFERRAL_BONUS_DAYS,
|
||||
}
|
||||
|
||||
|
||||
def set_referral_settings(enabled: bool, bonus_days: int):
|
||||
legal.update_env_var("REFERRAL_ENABLED", "true" if enabled else "false")
|
||||
legal.update_env_var("REFERRAL_BONUS_DAYS", str(int(bonus_days)))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue