Punch2027: Ex-Sokoto dep gov urges North to back TinubuThe Jerusalem PostLiberman calls for Netanyahu treason probe over alleged attempt to start war to delay electionsCNN TürkAsrın inşası ABD'nin gündeminde: "Üretim, saatte 23 konut hızına eşdeğer"InquirerITCZ to bring rain to parts of Mindanao until WednesdayESPNSuffocating defense, JSN lift Seahawks to victoryBollywood HungamaDaler Mehndi controversy ends as Uttarakhand Minister Subodh Uniyal apologises; Mika Singh’s team reactsהידעןליקוי ירח: למה הירח מאדים, מה הוא לימד את המדע ומתי הליקוי הבא בישראלWirtualna PolskaPrezydent Częstochowy odwołany. Jest stanowczy głos z KonfederacjiVanguardBrazil’s Lula clinches poll victory, Bolsonaro seeks to annul electionheise onlineAngriffe auf Arista VeloCloud und Collabora/Nextcloud beobachtetHet Laatste Nieuws“Anderlecht is één grote carrousel waardoor spelers geen houvast meer hebben”: Marc Degryse snapt op de duur niets meer van Vítor BrunoCNN بالعربيةصورة لقطرات من الحبر داخل طابعة تفوز بمسابقة تصوير مرموقة
The Daily Newsstand · Free, Always
Monday, October 26, 2026

Микро-SaaS на 1 ГБ RAM: Telegram-мини-апп, СБП и один очень дорогой дефис

Translate

Как «Паспорт вещей» (Next.js + Prisma + PostgreSQL) живёт на самом дешёвом VDS, зачем MAX нужен мост и собственный корень Минцифры, и почему платежи не работали с первого дня — а никто не замечал.

Дисклеймер

Это не туториал, а полевой отчёт соло-разработчика. Продукт — «Паспорт вещей» (паспорт-вещей.рф): цифровой паспорт покупок с напоминаниями о гарантии в Telegram/MAX. Бесплатно — до 5 вещей и все функции для авто (пробег, ТО, история); подписка «Открытый» (59 ₽/мес, СБП) — безлимит, OCR чеков, CSV и документы. Стек: Next.js 16, Prisma + PostgreSQL, grammy, ЮKassa. Всё это — на VDS с 1 vCPU и 1 ГБ RAM.

Урок 1: сборка на слабом сервере — это OOM

Первая же docker compose build на 1-гигабайтном сервере уронила машину: sshd и контейнеры были убиты OOM-киллером, помогла перезагрузка через панель хостера. С тех пор правило железное: сборка — на локальной машине, на сервер — docker save | ssh docker load. Бонус: чиним место на диске заранее (образы плодятся).

Урок 2: Idempotence ≠ Idempotency

Самая дорогая буква в проекте. Платежи через ЮKassa у нас создаются POST-ом с идемпотентным ключом. Стандартное имя заголовка — Idempotency-Key? У ЮKassa — нет: контрактное имя Idempotence-Key. Мы отправляли «правильное» имя — ЮKassa молча отвечала 400 «Idempotence key is missing», а пользователю показывалось «Не получилось создать платёж».

Прелесть в том, что дефект жил с первого дня: ни одного платежа не прошло никогда, но и никто не платил — поэтому узнавали мы об этом только через месяц с лишним, когда прогнали полный цикл на тестовом магазине. Морали три:

  1. Юнит-тест на моке закреплял неверное имя — тестируйте контракт, а не свою интерпретацию контракта.

  2. Полный платёжный e2e на тестовом окружении — до релиза, а не после.

  3. Ошибка создания платежа обязана логироваться со статусом и телом ответа.

Кстати о тестовом магазине ЮKassa: в нём нет СБП — только тестовые карты. Поэтому в тестовом режиме создаём платёж без payment_method_data и способ выбирается на стороне ЮKassa.

Урок 3: вебхукам нельзя верить, а return_url — можно (немного)

Тело вебхука мы не доверяем: по id платежа дергаем живой GET к API ЮKassa, сверяем сумму с прайсом, и в одной транзакции клеймим Payment.subscriptionId + выдаём подписку. Повторный вебхук видит клейм и молчит. А ещё возврат с оплаты на ?paid=1 сам запускает перепроверку — страховка от «вебхук опоздал».

Отдельная история — куда возвращать пользователя. Оплата из мини-аппа открывается в браузерном контейнере мессенджера, и жёсткий return_url=/price приводил к странице тарифов с кнопкой «Войти» (cookie-сессии в контексте возврата могло не оказаться). Решение: возвращать на /app — точку, которая при отсутствии сессии сама тихо логинит через мост мессенджера.

Урок 4: MAX — это Telegram-совместимый протокол, но

Мини-аппы MAX валидируются как Telegram-initData (без signature), но initData реально приходит только через самописный мост — hash-фолбэк не работает. API платформы требует российского корня TLS (добывали по цепочке AIA от госУЦ). А кнопка мини-приложения в боте ставится только через бизнес-кабинет — API для неё нет.

Урок 5: словарь на 4000 строк из CSV

Для авто-раздела нужен был словарь марок/моделей. Ничего экзотического: две таблицы (VehicleMake/VehicleModel), сид из CSV, автокомплит. Интересны грабли:

  • SQLite (тесты/локал) и PostgreSQL (прод) расходятся семантикой LIKE: PG крутит %/_ как wildcards, SQLite не склоняет кириллицу по регистру. Ветвление «по провайдеру» дало расхождение дев/прод — «ваз» находил «ВАЗ (Lada)» в деве, но не наоборот. Выход: при ~400 строках поиск дешевле делать одним JS-фильтром на обеих БД.

  • В автокомплите активный индекс — самая хрупкая вещь: после Escape список скрыт, но Enter всё ещё «выбирал» невидимую подсказку. Лечится гейтом «open» и сбросом индекса при каждом обновлении списка.

Урок 6: тарифный ревамп — отдать платное бесплатно

Стартовали с аддоном «Авто» за 99 ₽/мес (пробег и ТО). По итогам беты пересобрали модель: авто-функции ушли в бесплатный тариф целиком, а подписка «Открытый» (59 ₽/мес · 599 ₽/год) продаёт безлимит вещей, OCR чеков, CSV и документы. Логика: резать то, что уже было бесплатным, нельзя — а вот отдать бесплатным самый громкий вход в продукт можно, если платным остаётся то, что экономит время. Слишком рано судить, но ревамп дал повод наконец раздать бесплатный авто-раздел прессе и каналам — как раз то, что эта статья и делает.

Что дальше

Продукт живёт, платит за себя хостинг. Семейный доступ уже выкачен. Ближайшие планы — публичный API, каталог регламентов по моделям. Если тема микро-SaaS на слабом железе интересна — расскажу отдельно про тюнинг PostgreSQL под 1 ГБ и cron-оптимизацию под напоминания.

Бот: @VeshiPassportBot, сайт: паспорт-вещей.рф. Критика кода и продуктовых решений приветствуется в комментариях.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.