How I Migrated a Telegram Store Bot to a Self-Hosted VPS & Fixed a Broken Payment System

Case Study ยท Telegram Commerce

Migrating a Telegram Store Bot to a Self-Hosted VPS โ€” and Fixing a Payment System That Rejected Every Order

A live digital-goods store was running on Replit with a checkout that silently failed on every purchase. I moved it onto a hardened VPS, root-caused and fixed the payment-verification failure, completed a half-built anti-fraud system, and shipped new features and branded documentation โ€” turning a fragile prototype into a production business that runs itself.

TypeScriptNode 20PostgreSQL 16Telegram Bot APIBinance PayPM2 + systemd
The Problem

A live store that couldn’t take payments

The store sold digital goods through a Telegram bot, but it was running on Replit โ€” convenient for a prototype, unsuitable for a real business moving money around the clock. There was no proper process management, no hardened database, and the whole thing leaned on a third-party runtime that could sleep, reset, or disappear at any moment.

The deeper issue was the checkout itself. Binance auto-verification had never actually worked. Every legitimate payment was silently rejected, so customers paid and received nothing until someone stepped in by hand โ€” a slow, error-prone process that quietly bled away sales and trust. On top of that, the anti-fraud layer meant to protect deliveries was only half-built and effectively dead code, leaving the store wide open.

The brief was simple to state and hard to deliver: make it production-grade, fully self-hosted, and able to verify payments and deliver automatically โ€” without ever getting defrauded.

What I Did

End to end: infrastructure, data, fixes & features

๐Ÿ–ฅ๏ธ

Deployment & Infrastructure

Migrated the TypeScript/Node monorepo to Ubuntu 24.04 and provisioned the full stack โ€” Node 20, PostgreSQL 16, and PM2 running under systemd so the bot starts automatically on boot. Then I hardened it: a non-root service user, a database bound to localhost only, SSH-key access, and locked-down secrets.

๐Ÿ—„๏ธ

Data Migration

Recreated the schema and migrated all the real data โ€” products, licence-key stock, images, orders and users. The tricky part was an ID collision between the old and new databases, which I solved with name-based remapping, fully idempotent imports, and backups at every step so nothing could be lost or double-counted.

๐Ÿ›ก๏ธ

Anti-Fraud Completion

The fraud logic depended on a “payer binding” that turned out to be dead code โ€” it never ran. I implemented trust-on-first-use auto-delivery while preserving the guarantees that matter: cross-user uniqueness, per-user binding, and replay protection, so a payment can’t be reused or hijacked.

โœจ

New Features

Built an animated chat experience โ€” a storefront reveal and live verification progress bars โ€” plus an in-Telegram, paginated sales dashboard. I also corrected the revenue accounting so it only recognises orders once payment is actually confirmed.

๐Ÿ“ฆ

Tooling & Documentation

Set up a reproducible typecheck → build → deploy pipeline with pnpm and esbuild, so shipping a change is one predictable command. Delivered branded PDFs too: a client manual for day-to-day use and a developer maintenance guide for the future.

โš›๏ธ

24/7 Reliability

PM2 and systemd keep the bot alive around the clock, restarting it automatically on a crash or server reboot. The store is now fully self-hosted with no dependency on any third-party runtime that could pause or pull the plug.

The Critical Fix

Why every valid payment was being rejected

This was the headline issue โ€” and the most satisfying to crack. Binance auto-verification had never once worked in production, and the root cause turned out to be two bugs stacked on top of each other:

  • It matched the wrong identifier. Verification compared the bot’s internal transactionId against Binance’s records instead of the customer’s actual Order ID โ€” so it was hunting for something that could never match.
  • A silent number-vs-string mismatch. Even when the right values lined up, a type mismatch made every comparison fail quietly, with nothing logged โ€” so valid payments were thrown out and no one could see why.

I fixed both, then hardened the flow with canonical-ID de-duplication so a single payment can never be redeemed twice. The result: payments now verify the moment they confirm, and delivery happens automatically โ€” no manual checking, no lost sales.

Results & Impact

From broken checkout to a store that runs itself

A payment flow that used to reject every order now verifies and auto-delivers in real time, with fraud protection fully intact. The store runs 24/7 on its own VPS โ€” no third-party runtime, no manual babysitting โ€” and the owner can see accurate sales and revenue right inside Telegram.

100%of valid payments now verify
Real-timeauto-delivery on confirmation
24/7self-hosted uptime
Zerodouble-redemptions or replays
Tech Stack

What it’s built with

Language & Runtime
TypeScriptNode 20Expressesbuildpnpm
Data
PostgreSQL 16Drizzle ORM
Payments & Bot
Telegram Bot APIBinance Pay API
Infrastructure & Ops
LinuxPM2systemdSSH
Work With Me

Need something like this built โ€” or fixed?

Telegram bots, payment integrations, server migrations, anti-fraud systems and automation. I take projects from broken or half-built to production-ready. Let’s talk.

Get in touch