Business
Wallet & tracks
Wallet and Wallet Tracks, present the same underlying ledger from different angles.Each track is independently topped-up, spent, and giftable, and each has its own ledger of transactions fetched via api.wallet.txns(track) for "points" | "money" | "prepaid".
| Track | Field | What it represents |
|---|---|---|
| Points | points | Loyalty/reward currency, mainly earned via referrals, gift/redeem codes, or admin grants; spendable toward plans. |
| Money / Top-up | moneyCents | Real cash balance in cents; topped up by the user (admin-approved) or gifted by an admin; spent buying plans/subscriptions directly. |
| Prepaid | prepaidCount | A count of prepaid plan-units/subscriptions credited to the user (not cash), redeemable against actual VPN accounts. |
Transaction record
Every mutation of a track is a row in one append-only ledger table, the WalletTxn shape:
- id, ts
- Row id and timestamp.
- telegram_id, track
- Which user, which of points|money|prepaid.
- delta
- Signed change to the balance.
- balance_after
- Running balance after this transaction.
- kind
- Freeform, e.g.
topup,gift,spend. - ref
- Often
order:<id>, linking back to a specific purchase/invoice. - note, admin_actor
- Free text, and which staff member (if any) initiated it.
Real vs gifted vs coupon classification
The Wallet dashboard classifies money/prepaid inflows into buckets by inspecting tx metadata:
- Real — genuine cash top-ups / plan purchases paid in cash.
- Gifted — admin gifts or gift-code redemptions (
isGift/isRedeemCodehelpers). - Coupon — coupon-code discounted purchases (
isCoupon).
A FIFO algorithm (moneyAttr) walks each user's money ledger chronologically, draining the “gifted” bucket first on spends, so the dashboard can correctly split “real spent” vs “gift spent” and “real held” vs “gift held”. This is purely informational reporting — it is not enforced by the backend, since the ledger has no dedicated column for it; the split is derived heuristically from kind/note/ref.
Money is displayed with formatMoney(cents) (Intl.NumberFormat USD). PKR conversion uses pkrPerUsd from Settings (default 280), purely for display on the Referrals page.
§4.2 — /_authed/wallet
Wallet page#
The newer “business dashboard”, sectioned by cash source. A SectionRail switches between seven sections, each backed by a dedicated component driven by the same underlying data:
| Section | Component |
|---|---|
| Top-up | TopUpSection |
| Prepaid | PrepaidSection |
| Points | PointsSection |
| Top-up gifted | TopUpGiftSection |
| Prepaid gifted | PrepaidGiftSection |
| Coupon top-up | CouponTopUpSection |
| Coupon prepaid | CouponPrepaidSection |
A hero card at the top shows all-time added/spent/held tiles for whichever section is selected, plus overall Users and Total transactions counts. Data is fetched via api.wallet.summary() (per-user aggregate balances + pending counts), api.users.list(), api.invoices.list(), api.wallet.txns("money"|"prepaid"|"points"), api.accounts.list(), and api.referrals.overview(500).
§4.3 — /_authed/wallet-tracks
Wallet Tracks page & crypto tracking#
A tabbed alternative UI: Overview, Points, Top-up, Prepaid, Approvals (with a live badge count of pending top-up + prepaid requests). It is linked from Wallet via a Legacy view button.
Layout
- Hero row — three
BalanceCards (Points / Top-up / Prepaid totals across all users) plus pending counts fromsummary.pending.topup/summary.pending.prepaid. - Actions — Export XLSX (
api.wallet.exportUrl()), Send monthly (api.wallet.monthlyClose()— generates and sends monthly wallet statements to users, toast reports sent/failed/users counts), and Gift wallet (opens the gift dialog). - Overview tab — top wallet holders table (sorted by combined weighted balance) and a recent-activity feed of the last 40 wallet transactions across all tracks.
- Per-track tab (Points/Top-up/Prepaid) — Credited/Spent/Net summary cards plus a full paginated ledger table (up to 300 rows) with per-row XLSX export.
- Approvals tab — pending top-up and prepaid requests submitted by users self-service in the bot, distinct from the Payments queue (which is for direct plan purchases). This is where crypto top-up confirmations typically get manually verified and approved before the balance lands on the user's Top-up track.
Endpoints (api.wallet)
summary() per-user aggregate balances + pending counts
requests() pending approvals
txns(track) "points" | "money" | "prepaid"
gift(telegramId, track, amount, reason, planId?) instant admin credit
monthlyClose() generate + send monthly wallet statements
exportUrl() XLSX export
decideTopup(id, approve, note) approve/reject a self-service top-up request
decidePrepaid(id, approve, note) approve/reject a self-service prepaid requestGifting a wallet balance
- 1Go to Wallet Tracks (or Users → a specific user → grant dialog).
- 2Click Gift wallet.
- 3Enter the recipient's numeric Telegram ID.
- 4Choose the track: Points, Top-up (money, in cents), or Prepaid (pick a plan + quantity).
- 5Enter the amount and a reason (shown in the ledger/audit trail).
- 6Click Send gift — this is instant and does not require the Payments approval workflow. It is the same primitive the Users page's grant/deduct dialog uses, calling
api.wallet.gift(telegramId, track, amount, reason, planId?).
Approving a self-service top-up/prepaid request
- 1Go to Wallet Tracks → Approvals.
- 2Review the request (user, track, amount/plan).
- 3Optionally type a note.
- 4Click Approve or Reject — calls
api.wallet.decideTopup(id, approve, note)or the parallel prepaid decision call. The wallet summary and requests list are invalidated/refetched afterward.
Two separate approval layers
api.payments.decide vs api.wallet.decideTopup/decidePrepaid — but both notify the user in Telegram on decision.- All money amounts are integers in cents; always divide by 100 for display —
formatMoney()does this and formats as USD. - Points are a separate integer currency; the conversion rate is configurable in Referrals → Points per PKR (
pointsPerUnit). PKR/USD (pkrPerUsd, default 280) is a Settings-driven display rate, not live FX. - Prepaid is a count of plan-units, not currency, credited from admin gifts, coupons, or real cash top-up-to-prepaid conversion.
- Every wallet mutation — top-up, spend, gift, coupon-discount, referral payout, admin grant/deduct — is a row in one append-only ledger table; there is no separate table per track, only a track field distinguishing them.
