<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Doday</title>
    <description>The latest articles on DEV Community by Doday (@swairit).</description>
    <link>https://dev.to/swairit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3949371%2F08fb764a-1f1e-46a7-8626-e0dc93c26b2b.jpg</url>
      <title>DEV Community: Doday</title>
      <link>https://dev.to/swairit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/swairit"/>
    <language>en</language>
    <item>
      <title>I'm 15 and I built a todo app with Telegram Stars payments — only legal way for me to monetize before turning 18</title>
      <dc:creator>Doday</dc:creator>
      <pubDate>Sun, 24 May 2026 17:45:16 +0000</pubDate>
      <link>https://dev.to/swairit/im-15-and-i-built-a-todo-app-with-telegram-stars-payments-only-legal-way-for-me-to-monetize-1nmd</link>
      <guid>https://dev.to/swairit/im-15-and-i-built-a-todo-app-with-telegram-stars-payments-only-legal-way-for-me-to-monetize-1nmd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkb3z7qxhfioq3xxug9o3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkb3z7qxhfioq3xxug9o3.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey dev.to,&lt;/p&gt;

&lt;p&gt;So this is a weird story and I wanted to share it. Stick with me, the payment-processor part is genuinely funny.&lt;/p&gt;

&lt;p&gt;I'm 15, live in Russia, and over the past &lt;strong&gt;three weeks of intense solo coding&lt;/strong&gt; I built &lt;strong&gt;Doday&lt;/strong&gt; — a todo app that runs as a web app, a Telegram Mini App, and a Telegram bot, all sharing one backend. Basically a Todoist alternative with Pomodoro built in, kanban boards, school portal sync (for Russian students — this is the moat tbh), and team collaboration.&lt;/p&gt;

&lt;p&gt;You can try it here: &lt;strong&gt;&lt;a href="https://getdoday.ru" rel="noopener noreferrer"&gt;getdoday.ru&lt;/a&gt;&lt;/strong&gt; · code: &lt;strong&gt;&lt;a href="https://github.com/SwairIt/doday" rel="noopener noreferrer"&gt;github.com/SwairIt/doday&lt;/a&gt;&lt;/strong&gt; · bot: &lt;strong&gt;&lt;a href="https://t.me/DodayTaskBot" rel="noopener noreferrer"&gt;DodayTaskBot on Telegram&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I want to talk about the monetization part because I think it might be useful for other minors who are trying to ship products.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;I'd been building Doday since May 2nd. By the time I wanted to add paid plans, the app already had a small group of users (mostly friends and people from my school plus a few random folks). I was running on &lt;code&gt;BETA_FREE_FOR_ALL=true&lt;/code&gt; env flag, meaning everyone got Pro features for free. This was sustainable for the launch period but I wanted to actually validate that people would pay for the thing.&lt;/p&gt;

&lt;p&gt;Plan was simple: register a Russian payment processor (YooKassa, formerly Yandex Kassa — pretty much the local Stripe equivalent), implement their checkout, done.&lt;/p&gt;

&lt;p&gt;I'm registered as "self-employed" (самозанятый — a tax regime in Russia available from 14 with parental consent), so I have a tax ID and a real bank account. I figured this was enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three rejections in two days
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;YooKassa&lt;/strong&gt; — login via gov-ID (Госуслуги, our equivalent of e-gov / single sign-on for government services). Instant rejection: &lt;em&gt;"Users with a child account on Госуслуги cannot sign in."&lt;/em&gt; My Госуслуги account is &lt;strong&gt;fully verified&lt;/strong&gt; — I pay taxes through it, do everything an adult does. But YooKassa checks date of birth and if you're under 18, no appeal. Hard wall.&lt;/p&gt;

&lt;p&gt;OK, &lt;strong&gt;manual entry&lt;/strong&gt;. I open the manual form — they want a scanned color passport upload. Their OCR reads the date of birth and rejects it. I tried with strategically blurred dates — got "could not verify identity" after an hour. Same wall.&lt;/p&gt;

&lt;p&gt;OK, &lt;strong&gt;T-Bank acquiring&lt;/strong&gt; (another major Russian processor). Sign-up flow gets to "you need to be a registered IP (individual entrepreneur) or LLC". Self-employed is allowed but only from 18+. To register as IP you also need to be 18 (16 with parental consent and notarized statement, but banks don't accept these for acquiring).&lt;/p&gt;

&lt;p&gt;OK, &lt;strong&gt;register under a parent's name&lt;/strong&gt;. Now the money legally belongs to them. If there's a dispute with a customer, my parent has to argue it. The tax office considers the income theirs. If they decide they don't want to be involved tomorrow, I lose my entire payment infrastructure overnight. Not happening.&lt;/p&gt;

&lt;p&gt;OK, &lt;strong&gt;wait until 18&lt;/strong&gt;. That's three more years. I can't justify investing that much more time into a product without seeing some financial validation.&lt;/p&gt;

&lt;p&gt;I was honestly about to give up on monetization. Then I remembered Telegram Stars existed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Telegram Stars (XTR)
&lt;/h2&gt;

&lt;p&gt;If you don't know — Telegram has its own in-app currency called Stars (currency code &lt;code&gt;XTR&lt;/code&gt; in their Bot API). Users buy them through the Telegram client (Apple/Google in-app purchase). Bots can accept payments in Stars via &lt;code&gt;createInvoiceLink&lt;/code&gt;. Developers get ~70% of Stars value, withdraw through TON crypto or via Fragment.&lt;/p&gt;

&lt;p&gt;Key thing: &lt;strong&gt;BotFather (Telegram's bot-management bot) is available from age 13&lt;/strong&gt;. No documents. No tax IDs. No banks. Your contract is with Telegram, not with a bank.&lt;/p&gt;

&lt;p&gt;For me at 15, this is the only legitimate payment path that exists.&lt;/p&gt;

&lt;p&gt;I sat down and wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built (technical)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Schema migration&lt;/strong&gt; (&lt;code&gt;alembic 0039&lt;/code&gt;): &lt;code&gt;users.pro_until&lt;/code&gt; (timestamp), &lt;code&gt;star_payments&lt;/code&gt; table with &lt;code&gt;UNIQUE(telegram_payment_charge_id)&lt;/code&gt; for idempotency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Product catalog&lt;/strong&gt; (&lt;code&gt;app/billing/products.py&lt;/code&gt;) — single source of truth for pricing. Pro 1m = 250⭐, Pro 12m = 2500⭐, Pro Lifetime = 12500⭐. Edit this file, that's it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HMAC-signed invoice payloads&lt;/strong&gt; — &lt;code&gt;v1:{product}:{user_id_hex}:{nonce}:{sig}&lt;/code&gt;. Without this, anyone could intercept the invoice URL and swap the product code to "pro_forever" before paying. With it, the signature breaks and Bot API rejects. Payload ≤80 bytes (Telegram cap is 128).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent payment application&lt;/strong&gt; — Telegram retries webhook on failure. I insert into &lt;code&gt;star_payments&lt;/code&gt; with &lt;code&gt;UNIQUE&lt;/code&gt; on charge_id; second insert raises &lt;code&gt;IntegrityError&lt;/code&gt; → I catch and return existing row. No double-credit ever.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;effective_tier()&lt;/code&gt; honors &lt;code&gt;pro_until&lt;/code&gt;&lt;/strong&gt; — paid users with expired &lt;code&gt;pro_until&lt;/code&gt; auto-revert to free with zero cron jobs. Lazy eval on every request. Lifetime purchases set &lt;code&gt;pro_until&lt;/code&gt; to year 2099 as a sentinel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Renewal extends from existing &lt;code&gt;pro_until&lt;/code&gt;&lt;/strong&gt; — buying Pro 1 month when you still have 10 days left doesn't reset to 30 days. It extends to &lt;code&gt;current_pro_until + 30 days&lt;/code&gt;. (Todoist does this wrong, FYI.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refund flow&lt;/strong&gt; — admin endpoint calls &lt;code&gt;refundStarPayment&lt;/code&gt; Bot API + rolls back &lt;code&gt;pro_until&lt;/code&gt;. Telegram allows refunds within 21 days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bot handlers&lt;/strong&gt; — &lt;code&gt;PreCheckoutQueryHandler&lt;/code&gt; (must answer in 10 sec, verifies signature + amount), &lt;code&gt;MessageHandler(filters.SUCCESSFUL_PAYMENT)&lt;/code&gt; (calls apply_successful_payment).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tests: 24 unit tests covering signing, tampering rejection, amount mismatch, idempotency, renewal math, lifetime sentinel, expired→free fallback.&lt;/p&gt;

&lt;p&gt;The Mini App UI uses &lt;code&gt;Telegram.WebApp.openInvoice(url, callback)&lt;/code&gt; for in-app payment. The web UI opens the invoice URL in a new tab as a fallback for desktop.&lt;/p&gt;

&lt;p&gt;I also closed a security hole I'd been sitting on — &lt;code&gt;POST /api/billing/change-tier&lt;/code&gt; used to let any authenticated user upgrade themselves to Pro for free by POSTing &lt;code&gt;{"tier": "pro"}&lt;/code&gt;. Closed it the same day I shipped Stars: upgrades now require 402 Payment Required, only downgrade to free is self-service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Important caveat about monetization right now
&lt;/h2&gt;

&lt;p&gt;I just shipped Stars, but &lt;strong&gt;the service is currently in beta and Pro features are free for everyone&lt;/strong&gt;. Nobody is paying anything yet, and I'm not pushing it. When I exit beta, I'll switch on paid mode, and existing users (anyone registered before that flip) will stay free with whatever they've already created. In the Mini App there's a founder-style &lt;strong&gt;"Pro Forever" offer for 12500 ⭐&lt;/strong&gt; — for people who want to lock in Pro before the paid mode returns. Not mandatory. If you don't buy it, you'll be on Free after the flip.&lt;/p&gt;

&lt;p&gt;So monetization is wired up but optional-by-design. It'll go live for real when the product stabilizes and starts growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack (in case you care)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI 0.115 + async SQLAlchemy 2.0 + Pydantic v2&lt;/li&gt;
&lt;li&gt;PostgreSQL 16 (asyncpg)&lt;/li&gt;
&lt;li&gt;Jinja2 + HTMX + Alpine.js + Tailwind via CDN — no React, no build step&lt;/li&gt;
&lt;li&gt;python-telegram-bot v21 for the bot worker&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mypy --strict&lt;/code&gt; enforced via pre-commit on every commit&lt;/li&gt;
&lt;li&gt;~20k lines of Python, 850+ pytest tests, 39 Alembic migrations&lt;/li&gt;
&lt;li&gt;One-line deploy: &lt;code&gt;git push&lt;/code&gt; → cron-poll on prod VPS pulls every minute, applies migrations, restarts uvicorn. Live in ~60 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About using Claude Code
&lt;/h2&gt;

&lt;p&gt;I wrote Doday in pair with Claude Code (Anthropic's AI coding assistant). I'm not hiding it — the architecture decisions are mine but Claude was a fast typist with good memory of the codebase. Every commit was reviewed before push. Lines I didn't understand got rewritten until I could explain them out loud.&lt;/p&gt;

&lt;p&gt;The reason the codebase doesn't smell like AI slop is the rails: &lt;code&gt;mypy --strict&lt;/code&gt; fails the build on type errors, every behavior change ships with a test, the Jinja linter catches bad Alpine patterns Claude tends to generate. With those guardrails, an AI assistant is just a fast pair-programmer.&lt;/p&gt;

&lt;p&gt;Whether you consider that "real" coding depends on your definition. I think it does. I wouldn't have shipped this much in three weeks alone, but I also wouldn't have shipped it well without the taste for architecture and review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest stats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;First commit: May 2nd, 2026&lt;/li&gt;
&lt;li&gt;Total commits: 511&lt;/li&gt;
&lt;li&gt;Tests: 850&lt;/li&gt;
&lt;li&gt;~20k lines of Python&lt;/li&gt;
&lt;li&gt;Active users: small, mostly from my school + friends-of-friends. I haven't really tried marketing yet — this Reddit post is one of the first attempts.&lt;/li&gt;
&lt;li&gt;Paying users: 0 — beta mode, Pro is free for everyone, lifetime offer is optional&lt;/li&gt;
&lt;li&gt;Revenue so far: $0&lt;/li&gt;
&lt;li&gt;Roadmap: parent dashboard for Family tier, public API tokens, native iOS via Capacitor wrapping the Mini App&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'm asking
&lt;/h2&gt;

&lt;p&gt;If you got curious, the live demo is at &lt;a href="https://getdoday.ru" rel="noopener noreferrer"&gt;getdoday.ru&lt;/a&gt; — UI is Russian but English-readers can navigate with browser translation. The codebase on &lt;a href="https://github.com/SwairIt/doday" rel="noopener noreferrer"&gt;github.com/SwairIt/doday&lt;/a&gt; is more interesting if you want to see how all the pieces fit (Mini App auth, Stars payment flow, team sharing, opt-in feature flags).&lt;/p&gt;

&lt;p&gt;If you have feedback on the architecture, the payment flow, or you've also dealt with the "underage and trying to ship a SaaS" problem — I'd love to hear it.&lt;/p&gt;

&lt;p&gt;If you've shipped your own Stars integration — there's one part I'm uncertain about: I'm currently keeping &lt;code&gt;provider_payment_charge_id&lt;/code&gt; as nullable because I'm not sure when Telegram actually populates it. Anyone know?&lt;/p&gt;

&lt;p&gt;And if you star the repo — it genuinely helps. I'm not running this for fun, I'd love to make a living from it eventually.&lt;/p&gt;

&lt;p&gt;Cheers from a 15-year-old in Russia.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; if you're wondering why a Russian teenager is posting on Reddit — Doday is open source MIT and I want it to grow beyond Russian users. The codebase is English (docstrings, variable names, commit messages aside — those are Russian past-tense), so contributions are welcome. The Telegram Stars integration in particular is generic — works for anyone selling anything to anyone with Telegram.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
