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.

Message me on Telegram