🇬🇧 English · 🇷🇺 Русский
A full-featured e-commerce bot for Telegram — catalog, cart, Telegram Stars & USDT payments, Stars subscriptions, reviews & ratings, promo codes, wishlist, working loyalty & referral programs, a built-in Mini App, and an admin panel. 5 languages out of the box. Ships as a single binary. One guided command, two answers, no config-file editing.
|
🛍️ Buyer
|
🔧 Admin
⚙️ Infrastructure
|
For a read-only Telegram Stars ledger check, run
make reconcile-stars. It prints aggregate counts only. Exit1means configuration/API/DB failure, a truncated or unstable window, provider/local-only rows, amount mismatch, or unresolved review items. Model: ADR 0001. Recovery stays preview-first and explicit; see the payment operations runbook.
Get a token from @BotFather, then choose a release binary or build from source.
Download the archive for your system from GitHub Releases: Windows (.zip, available from v3.0.1) or Linux (.tar.gz). Choose amd64 for Intel/AMD processors or arm64 for ARM processors. Prebuilt binaries need no Go, Make, or Docker.
Download checksums.txt from the same release and verify the archive's SHA-256 before extracting or running it. This checks file integrity; it does not provide an author signature.
Windows: extract the entire ZIP, including the locales folder. Open PowerShell in the extracted directory containing telegram-shop-bot.exe, then run:
.\telegram-shop-bot.exe quickstartAfter stopping the bot with Ctrl+C, use these commands in the same directory:
.\telegram-shop-bot.exe doctor # check configuration and services
.\telegram-shop-bot.exe version # show the installed version
.\telegram-shop-bot.exe run # start without the wizardLinux: extract the entire archive, keep locales beside the executable, open a terminal in that directory, and run ./telegram-shop-bot quickstart. Later, use ./telegram-shop-bot doctor, ./telegram-shop-bot version, or ./telegram-shop-bot run.
Requires Go 1.24+, Git, and Make:
git clone https://github.com/JumpCodeFrog/telegram-shop-bot.git && cd telegram-shop-bot
make quickstartThe built-in setup asks only for the BotFather token and your numeric Telegram user ID. It validates the bot through Telegram, derives its username, writes .env without overwriting an existing file (0600 on Unix), checks SQLite and optional Redis, then starts the shop. The token is hidden in an interactive terminal and never printed.
Need your numeric ID? Send any message to @userinfobot; it is not your phone number or @username.
Optional: enable Inline Mode with /setinline in @BotFather to share products from the catalog in any chat. doctor reports when it is off.
make doctor # actionable setup report
make run # start without the wizard
make payment-review PROVIDER=stars # redacted local review inboxRedis is optional; without it the bot uses its in-memory fallback. See Getting Started for more binary commands.
Only Docker is required. The source checkout is built locally.
git clone https://github.com/JumpCodeFrog/telegram-shop-bot.git
cd telegram-shop-bot
cp .env.example .env
# Set BOT_TOKEN and ADMIN_IDS in .env, then:
docker compose up -d --build
curl http://127.0.0.1:8080/health
docker compose logs -f bot- Open Telegram → search @BotFather
- Send
/newbot - Choose a name and username
- Copy the token:
1234567890:ABCdefGHIjklMNOpqrsTUVwxyz - Paste it when the
quickstartwizard asks (or setBOT_TOKENin.envfor Docker)
| Variable | Required | Default | Description |
|---|---|---|---|
BOT_TOKEN |
yes | — | Token from @BotFather |
BOT_USERNAME |
no | — | Bot @username (without @), for referral links |
ADMIN_IDS |
no | — | Comma-separated Telegram IDs of admins |
CRYPTOBOT_TOKEN |
no | — | CryptoBot token for USDT payments |
DB_PATH |
no | data/shop.db |
SQLite database path |
LOG_LEVEL |
no | info |
debug / info / warn / error |
APP_ENV |
no | development |
production for JSON logs |
REDIS_ADDR |
no | localhost:6379 |
Redis address |
REDIS_PASSWORD |
no | — | Redis password |
USD_TO_STARS_RATE |
no | 50 |
Telegram Stars per 1 USD |
WEBHOOK_URL |
no | — | Public HTTPS URL for webhook mode |
TELEGRAM_WEBHOOK_SECRET |
with WEBHOOK_URL |
— | Strong webhook verification secret (generate with openssl rand -hex 32) |
LOCALES_DIR |
no | locales |
Path to translations folder |
WEBAPP_URL |
no | — | Public HTTPS URL of the Mini App; empty = Mini App disabled |
ADMIN_GROUP_ID |
no | — | Supergroup ID for admin order notifications (instead of DMs) |
TOPIC_ORDERS_NEW |
no | — | Forum topic ID for new-order notifications |
TOPIC_ORDERS_PAID |
no | — | Forum topic ID for paid-order notifications |
TOPIC_ORDERS_DELIVERED |
no | — | Forum topic ID for delivered-order notifications |
OUTBOUND_WEBHOOK_URL |
no | — | External URL receiving order.paid / order.delivered events |
OUTBOUND_WEBHOOK_SECRET |
no | — | X-Webhook-Secret header value for outbound webhooks |
💡 No
WEBHOOK_URL→ polling mode (great for development).
No Redis → automatic fallback to in-memory state.
| Command | Description |
|---|---|
/start |
Main menu |
/catalog |
Browse products |
/search <query> |
Search products |
/cart |
Your cart |
/orders |
Order history |
/mysubs |
Your Stars subscriptions (with cancel buttons) |
/profile |
Your profile & loyalty status |
/referral |
Your referral link, invited count & earned points |
/wishlist |
Your wishlist |
/support |
Contact support |
/paysupport |
Payment help |
/terms |
Terms of service |
/help |
List of commands |
/cancel |
Cancel current action |
Admin commands (require your ID in ADMIN_IDS):
| Command | Description |
|---|---|
/admin |
Admin panel |
/addproduct |
Add a product (step-by-step wizard, photos & subscriptions supported) |
/editproduct <id> <field> <value> |
Edit a product |
/deleteproduct <id> |
Delete a product |
/addcategory <name> |
Add a category |
/editcategory / /deletecategory / /listcategories |
Manage categories |
/addpromo / /listpromos / /deletepromo |
Manage promo codes |
/orders_all |
All orders |
/order <id> |
Order card: buyer, items, totals, status, payment method & id |
/setdelivered <id> |
Mark an order delivered (triggers review request) |
/payreview |
Payment review queue — quarantined payments, two-tap settle/refund/dismiss |
/paystatus |
Payment provider status per rail (config, rates, webhook URLs) |
/refund <order_id> [amount] |
Refund a paid order — two-tap confirm, full or partial (Stars full-only) |
/setbalance <user_id> <±amount> [reason] |
Adjust a user's USD balance |
/reviews |
Latest reviews with delete buttons |
/analytics |
Revenue chart, top buyers, promo report |
/export_orders [from] [to] |
CSV export, optional date range |
/btnstyle |
Customize button colors |
Telegram Stars — built-in, works out of the box. Rate is configurable via USD_TO_STARS_RATE (default: 50 Stars = $1).
CryptoBot (USDT) — optional. Set CRYPTOBOT_TOKEN to enable.
Background worker polls payment status every 30 seconds. Signatures verified via HMAC-SHA256.
YooKassa (RUB) — optional. Card payments: the buyer is redirected to the YooKassa checkout page, the order settles when YooKassa notifies the bot.
Set YOOKASSA_SHOP_ID, YOOKASSA_SECRET_KEY, YOOKASSA_RETURN_URL (public HTTPS) and USD_TO_RUB_RATE (RUB per 1 USD, e.g. 92.5) — the first three must be set together, and with USD_TO_RUB_RATE at the default 0 the "Pay by card" button stays hidden (in the bot and the Mini App alike). USD prices convert at the configured rate; the RUB total is snapshotted on the order at checkout. Register <WEBHOOK_URL>/yookassa-webhook as the notification URL in the YooKassa merchant cabinet. YooKassa notifications are unsigned — the body only identifies the payment, and settlement happens strictly after the bot re-fetches the authoritative payment state from the YooKassa API. As a backup for lost notifications (e.g. bot downtime), a background poller re-scans the last 24 hours of succeeded payments every 60 seconds and settles whatever the webhook missed — no action needed from the operator. Subscriptions remain Stars-only.
Stripe (USD) — optional. Card payments in USD: the buyer is redirected to Stripe's hosted Checkout page, the order settles when Stripe notifies the bot.
Set STRIPE_SECRET_KEY (must start with sk_live_ or sk_test_), STRIPE_WEBHOOK_SECRET (must start with whsec_) and STRIPE_RETURN_URL (public HTTPS) — all three must be set together. In the Stripe dashboard (Developers → Webhooks) register <WEBHOOK_URL>/stripe-webhook for checkout.session.completed and copy the endpoint's signing secret into STRIPE_WEBHOOK_SECRET. Stripe notifications are HMAC-signed: once the signature verifies, the signed body itself settles the order — no API refetch needed (unlike the unsigned YooKassa flow, which must re-fetch the payment). Orders below Stripe's $0.50 minimum are refused when the card button is tapped. Subscriptions remain Stars-only.
TON (on-chain) — optional. Direct TON transfers to the shop's own wallet: the buyer taps the TON button and gets the wallet address, the exact amount and a mandatory order-<id> memo — the ton://transfer deeplink button prefills all three — then sends the transfer from any TON wallet. There is no webhook on-chain: a background worker polls the wallet's latest transactions via toncenter every ~30 seconds and settles the order once a transfer with the exact memo covers the frozen amount.
Set TON_WALLET_ADDRESS and USD_PER_TON (USD per 1 TON, e.g. 5.25) together — with USD_PER_TON at the default 0 the TON button stays hidden (in the bot and the Mini App alike); TON_API_KEY (toncenter) is optional and only lifts rate limits. The nanoton total is snapshotted on the order at checkout from the USD_PER_TON in effect at that moment, so a later rate change never reprices an existing order, and an order created while TON was disabled can never be paid in TON. Settlement is overpay-tolerant: a transfer of at least the snapshot settles and the ledger records the actual received amount; an underpay is quarantined for make payment-review PROVIDER=ton. A transfer without the exact memo can never be matched to an order automatically — identify the sender and the intended order on-chain (tonviewer), then either credit the transfer to the order with payment-review ingest-provider --provider ton or refund it manually from the wallet (details: docs/payment-operations.md §7). Subscriptions remain Stars-only.
NOWPayments (300+ coins) — optional. Hosted crypto invoices: the buyer is redirected to a NOWPayments invoice page offering 300+ coins (BTC, ETH, USDT, …), and the order settles when NOWPayments sends a signed IPN callback.
Set NOWPAYMENTS_API_KEY, NOWPAYMENTS_IPN_SECRET and NOWPAYMENTS_RETURN_URL (public HTTPS) — all three must be set together — and register <WEBHOOK_URL>/nowpayments-webhook as the IPN callback URL in the NOWPayments dashboard. IPN callbacks are HMAC-SHA512-signed over the canonicalized (recursively key-sorted) JSON body: once the signature verifies, the signed body itself settles the order — no API refetch (the CryptoBot/Stripe pattern). Only payment_status: finished settles; every other status is acknowledged without side effects. Invoices charge the order's own USD total, so the signed amount must match exactly — unlike TON there is no overpay tolerance.
Balance — internal, no configuration. Buyers with a positive USD balance (granted by an admin via /setbalance) see a "Pay (balance)" button at checkout for non-subscription carts; the tap settles the order synchronously and debits the balance — no provider round-trip. Subscriptions remain Stars-only.
Stars subscriptions — a product created as a "30-day subscription" is sold as a recurring Stars payment (subscription_period=2592000). Subscriptions are Stars-only; users manage them via /mysubs.
A lightweight web shop (vanilla JS, embedded in the binary — no build step) that opens right inside Telegram: catalog, product cards with photos and ratings, cart, and checkout via openInvoice (Stars) or CryptoBot.
- Serve the bot's port 8080 behind a public HTTPS domain (Telegram requires HTTPS for Web Apps).
- Set
WEBAPP_URL=https://shop.example.com/appin.env. - Restart — the bot's menu button becomes the Mini App, static files are served at
/app/, and the REST API at/api/*(authorized via TelegraminitData, HMAC-verified).
Without
WEBAPP_URLthe Mini App and its API are not mounted at all — the bot works exactly as before.
go run ./cmd/seedCreates categories (Clothing, Shoes, Accessories), 6 products, and promo codes WELCOME10 (−10%) and SALE20 (−20%). Safe to run multiple times.
# 1. Set real BOT_TOKEN and ADMIN_IDS in .env (or run `make init`)
# 2. Run unified checks: config, DB, Redis, Telegram and webhook
make doctor
# 3. Start
docker compose up -d --build
curl http://127.0.0.1:8080/health
curl http://127.0.0.1:8080/metricsWebhook + nginx config
server {
listen 443 ssl;
server_name shop.example.com;
ssl_certificate /etc/letsencrypt/live/shop.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/shop.example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}WEBHOOK_URL=https://shop.example.com
TELEGRAM_WEBHOOK_SECRET=random-secret-stringTelegram posts to https://shop.example.com/telegram-webhook. If CryptoBot is enabled, configure its webhook as https://shop.example.com/cryptobot-webhook. If YooKassa is enabled, register https://shop.example.com/yookassa-webhook as the notification URL in the YooKassa merchant cabinet. If Stripe is enabled, register https://shop.example.com/stripe-webhook in the Stripe dashboard (Developers → Webhooks). If NOWPayments is enabled, register https://shop.example.com/nowpayments-webhook as the IPN callback URL in the NOWPayments dashboard. TON needs no webhook at all — settlement is polled from the chain.
telegram-shop-bot/
├── cmd/
│ ├── bot/ # Entrypoint — start the bot
│ ├── preflight/ # Pre-launch env check
│ ├── seed/ # Load demo data
│ ├── telegram-smoke/ # Smoke test via Telegram API
│ └── usability-smoke/ # Buyer flow smoke test (no token)
├── internal/
│ ├── bot/ # Handlers, keyboards, notifications, webhook
│ ├── config/ # Configuration loading
│ ├── payment/ # Stars (incl. subscriptions) & CryptoBot adapters
│ ├── service/ # Cross-cutting services (i18n, loyalty, metrics)
│ ├── shop/ # Catalog, cart, orders (PaymentOutcome pipeline)
│ ├── storage/ # SQLite stores, Redis cache/FSM, migrations
│ └── webapi/ # Mini App REST API (initData auth)
├── web/app/ # Mini App frontend (embedded, vanilla JS)
├── locales/ # Translations (ru, en, es, de, zh)
├── worker/ # Background workers
├── monitoring/ # Grafana dashboard JSON
├── Dockerfile
├── docker-compose.yml
├── Makefile
└── .env.example
make build # Build all binaries
make test # Run tests
make lint # go vet
make quickstart # Guided init + doctor + run
make init # Create .env with two guided answers
make doctor # Unified environment and service checks
make run # Start the bot
make seed # Load demo data
make preflight # Legacy local/offline checks (doctor is the online check)
make reconcile-stars # Read-only Telegram Stars ledger comparison
make payment-review # List the Stars review inbox (PROVIDER=crypto is supported)- Webhook signature verification (HMAC-SHA256) for CryptoBot
- Secret token support for Telegram webhooks
- Admin access restricted to explicit Telegram ID list
- Docker container runs as non-root user
- Tokens and secrets never appear in logs
Found a vulnerability? See SECURITY.md.
- Fork the repo
- Create a branch:
git checkout -b feature/my-feature - Run tests:
go test ./... - Run linter:
go vet ./... - Open a Pull Request
See CONTRIBUTING.md for details.
MIT — do whatever you want. See LICENSE.
Русская версия: README.ru.md.