Business
Gifts, codes & referrals
src/routes/_authed.gifts.tsx, _authed.redeem-codes.tsx) for one-off promotions, and the referrals program (src/routes/_authed.referrals.tsx) for ongoing invite-a-friend rewards.Both Coupons (/_authed/gifts, rendered with mode="coupon") and Redeem codes (/_authed/redeem-codes, re-exports the same page with mode="redeem") share one backend table of "gift codes" (api.gifts.list() → { codes, redemptions, adminChatCount }), disambiguated by a kind field ("coupon" | "redeem") with a legacy fallback: codes matching rewardType === "points" and starting with GRVPN- are treated as redeem if kind is missing.
Concept differences
| Coupons | Redeem codes | |
|---|---|---|
| Distribution | Broadcast as a DM to all bot users | Posted into groups/channels where the bot is admin |
| Typical reward types | % off a plan (plan_discount), free plan (free_plan) | Points, free plan, or plan via points (plan_points) |
| Use limit | Up to maxUses total, per code | Each user can redeem a given code only once (server-enforced), even if maxUses allows more total uses across users |
Reward types (GiftRewardType)
points— flat points credit.free_plan— immediately provisions the chosen plan for the user (optionally plus extradurationDays/durationHourson top of the plan default).plan_points— grants exactly enough points to buy the specified plan at the user's local price.plan_discount— coupon-only; credits the coupon value in points equal todiscountPercent% of the plan price; the user pays the remainder.plan_days— "Add days to plan"; defined inREWARD_LABELbut not exposed in the current create dialog (a supported backend type not yet wired into the UI form).prepaid— "Prepaid top-up"; same status asplan_days, defined but not in the active create dialog.
Code fields
| Field | Notes |
|---|---|
| code | Auto-generates as GRVPN-XXXXXX if left blank; 3–40 chars, A-Z0-9_-. |
| rewardType, planId | Which reward, and the plan involved (when applicable). |
| points, discountPercent | Reward magnitude for points-based and discount-based rewards. |
| durationDays / durationHours | Extra time added on top of the plan default, for free_plan. |
| maxUses | Total redemption cap across all users. |
| expiresAt | Optional expiry date. |
| cooldownDays | 0 = use the global cooldown setting (anti-abuse). |
| note | Internal label, not shown to users. |
| active, kind | On/off toggle, and coupon vs redeem. |
| broadcast | Optional boolean: fire distribution immediately on creation. |
Endpoints (api.gifts)
list() codes + redemptions + adminChatCount
chats() bot's chats: { chatId, title, type, isAdmin }
create(payload) response includes code, optionally broadcastResult
update(code, { active }) toggle a code on/off
remove(code) delete a code
sendToChat(code, chatId) send/post the code to one specific admin chat
broadcast(code, { toChats, toUsers, chatIds?, userIds? }) full broadcast dialogUI features
- Stat tiles: total codes, active codes, total redemptions, total points paid out.
- Table columns: Code (kind badge + copy button), Reward (human summary per reward type), Uses (used/maxUses, plus cooldown), Expires, Note, Active toggle, Actions.
- Recent redemptions table: timestamp, code, redeemer (links to tg://user?id=), reward summary emoji line.
- Export CSV — client-side, dumps both codes and redemptions with columns: type, code, reward_type, plan, points, discount_percent, duration_days/hours, used, max_uses, active, expires_at, note, telegram_id, telegram_name, redeemed_at.
Bot-side redemption
In backend/agent/bot.py: /redeem and /coupon are registered commands (cmd_redeem, cmd_coupon) plus inline "🎁 Redeem" / "🎟 Coupon" prompts (user_gift_prompt, user_coupon_prompt) that ask the user to type a code. _handle_gift_redeem then grants points/plan/discount and confirms with a message ending "Tap /start to see your updated balance."
Issuing a redeem code (points, for a Telegram group)
- 1Add the bot as admin of the target group/channel (required — shown as a live "N chats where bot is admin" count).
- 2Go to Redeem codes → New code.
- 3Reward type: Points; set the point amount.
- 4Set Max uses (total redemptions allowed) and optionally an expiry date / cooldown.
- 5Optionally toggle Announce to groups & channels at creation, or leave off and send later via the Send icon (specific groups) or the megaphone icon (broadcast to all admin chats).
- 6Save — the bot posts the code (if broadcasting) with
/redeem <code>instructions; each user in the group can redeem once.
Issuing a discount coupon
- 1Go to Coupons → New code.
- 2Reward type: % off a plan; pick the plan and discount percentage.
- 3Set max total redemptions, optional expiry.
- 4Toggle Broadcast to all bot users (DM) to send immediately, or send later via the broadcast dialog (all users, specific chats, or specific Telegram IDs).
- 5Users apply it in the bot with
/coupon <code>(or the "🎟" menu button) when buying a plan; the discount is applied as points credit at checkout.
Route: /_authed/referrals. Configures and monitors the invite-a-friend rewards program, plus a full history log and top-referrer leaderboard.
Data (api.referrals.overview(limit))
- settings
enabled,pointsPerReferrer,pointsPerReferee,pointsOnTrial,pointsPerSignup,pointsPerUnit,maxPointsPerUser,maxReferralsPerUser,pkrPerUsd.- stats
totalUsers,totalReferred,totalReferrers,totalPointsPaid,logEntries.- logs[]
{ id, ts, referrer, referee?, kind, reason, points };kindis one ofpurchase | trial | referee-bonus | admin | other, each with a distinct colored badge/icon.- topReferrers[], referredBy
- Leaderboard, and a map keyed by referrer telegram id → list of referred users, shown in a dialog when a referrer row is opened.
How attribution works
Referral links use each user's referralCode. get_user_ctx in bot.py accepts a refCode on the /start deep-link and posts it to /bot/user/start, which the backend uses to attribute a new signup to a referrer. Attribution and payout rate enforcement happen server-side, not in the frontend. _referral_howto and user_ref in bot.py render the user-facing referral screen (their link, code, and stats), and _rewards_received_block summarizes rewards received so far.
How rewards and payouts work
pointsPerReferrer— points credited to the referrer when their referral converts.pointsPerReferee— points credited to the new user for having been referred.pointsPerSignup— points on plain signup (independent of referral).pointsOnTrial— points credited when a referred user takes a free trial.pointsPerUnitis the base conversion rate: points equal to 1 PKR. Displayed conversion:oneUsdPts = pointsPerUnit * pkrPerUsd(points equal to 1 USD). Example shown in the UI: "300 pts = 1 PKR · 84,000 pts = 1 USD (1 USD ≈ 280 PKR)".maxPointsPerUser(0 = unlimited) caps total referral earnings per user;maxReferralsPerUser(0 = unlimited) caps how many invites count per referrer.- All payouts land as points, which are then spendable toward plans exactly like any other points balance (see Wallet & tracks).
pointsOnTrial is not yet wired to Settings API
api.settings.save(...) yet — a code comment notes it "isn't exposed via settings API; skip until backend adds it."Setup — turning on referrals
- 1Go to Referrals.
- 2Flip the Program switch to Enabled.
- 3Set Points per PKR — this defines the whole points economy scale.
- 4Optionally cap Max points / user and Max referrals / user to control payout risk.
- 5Click Save settings (calls
api.settings.save({ referralEnabled, pointsPerReferrer, pointsPerReferee, pointsPerSignup, pointsPerUnit, maxPointsPerUser, maxReferralsPerUser })). - 6Configure the actual per-event payouts (
pointsPerReferrer,pointsPerReferee,pointsOnTrial,pointsPerSignup) in the Signup bonus & referral rewards card (BotRewardsEditor). - 7Users now see their referral link/code via the bot's referral menu and earn points automatically as referred users sign up, take a trial, or purchase.
Log and referrer tables are searchable by name/id/code; clicking a referrer filters the log to just their referrals, and a dialog lists everyone a specific referrer brought in.
