Skip to the content

IT&SOFT4 min readDevelopment

Building a Telegram bot that takes payments in 6 days: an architecture breakdown

What actually takes six days, where the day spent on approvals hides, and why the payment scheme is decided before the first line of code is written.

Six working days is not a record and it is not marketing. It is the timeline you get when the scope is known in advance and the decisions about money are taken before the start. Below is a breakdown of what those six days are made of, using a pre-order system for a chain of coffee shops as the example.

Day zero: the decisions you cannot postpone

Three questions have to be closed before any code. Putting any of them off costs days of waiting.

Who receives the money. If the payment goes straight to the client’s account, you need their acquiring contract and their keys. Connecting YooKassa takes anywhere from one day to a week, and that week passes on the bank’s side. Starting development without having begun that procedure means running into it on day five.

How the receipt is issued. 54-FZ (Russia’s cash-register law) requires fiscalisation, and that is not something you add at the end. Either the payment provider generates the receipt, or the client has a cloud till, or a separate service is needed. The choice affects how the order is structured in the database.

What happens when something fails. Refund, partial refund, cancelled order, double charge. Refund logic is half the code of a payment module, and it is designed together with the main flow, not after it.

Days 1–2: the storefront

A Mini App is a web application inside the messenger. The phrase “bot with buttons” misses it, and two things follow. First: you have a real interface, so a catalogue with modifiers does not turn into twenty messages in a row. Second: it is ordinary frontend, which has to be adapted to a small screen and to the theme the user chose in Telegram.

User data arrives in initData—a signed string that must be verified on the server. This is the first place bots break: the parameters are taken from the request as they are, which hands anyone the ability to place an order in someone else’s name.

lib/telegram/init-data.ts
import { createHmac } from 'node:crypto'
 
/** Проверка подписи initData. Без неё любой параметр можно подделать. */
export function verifyInitData(initData: string, botToken: string): boolean {
  const params = new URLSearchParams(initData)
  const hash = params.get('hash')
  if (!hash) return false
 
  params.delete('hash')
  const dataCheckString = [...params.entries()]
    .map(([key, value]) => `${key}=${value}`)
    .sort()
    .join('\n')
 
  const secret = createHmac('sha256', 'WebAppData').update(botToken).digest()
  const computed = createHmac('sha256', secret).update(dataCheckString).digest('hex')
 
  return computed === hash
}

Day 3: payment

The scheme is simple, but it has one non-obvious node. The customer taps “pay”, the application creates an order with status pending and receives a link from the provider. From there the user goes off to the bank’s page—and may not come back. Closed the tab, lost the network, switched to another app.

So the source of truth about payment status is the provider’s webhook, not the user’s redirect. The redirect shows a “thank you” screen; the webhook moves the order to paid. If you rely on the redirect alone, some paid orders will sit in pending and some unpaid ones will go into production.

The webhook has to be idempotent: the provider is entitled to send the same notification twice. The idempotency key is the payment id. Using the order id here breaks on the first partial refund.

Day 4: the operator screen

The guest sees a status, but someone has to press “ready”. For a coffee shop that is a tablet at the counter: the order queue, time in the queue, one large button. No password login—there is one tablet and it stands behind the counter, so access is by a one-time link with a long token.

This is also where the first performance requirement appears, the one people usually forget: the screen has to refresh itself. Polling every five seconds is more honest and more reliable than a websocket when orders come in tens rather than thousands. Less code, fewer states, and it survives a dropped connection without reconnecting.

Day 5: reports and small things

A nightly export of sales into the manager’s spreadsheet. A notification to the guest when the order is ready. Handling the case where the guest never turns up. Separate wording for when the location is closed.

Small things are half the time. And that is normal: they are exactly what separates a product from a prototype.

Day 6: a pilot at one location

Not across the whole chain. One location, one day, a live barista standing next to it. That day usually surfaces two or three phrasings the guest does not understand, and one scenario nobody planned for—a takeaway order for three people with different ready times, say.

What can really move the deadline

  • Acquiring. Waiting for the connection is the most common reason. Start with it.
  • Content. A menu with descriptions, photos, modifiers. Without it, development stalls and waits.
  • One person in charge. Not a committee. What slips a project is usually the approvals rather than the code.

Six days is what you get when everything above is ready. If it is not, count honestly: development time plus waiting time. We try to name both.

ShareTelegramVK
Author

IT&SOFT

A small team of engineers. We write about the work we do by hand, and about what breaks while we do it. If you have something similar on your plate, write to us and we will go through your case.

Discuss your task
Subscribe to new breakdowns
Next

Got a similar
task?

Describe it in the brief. In working hours we come back with an estimate of time and cost within two hours.