尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Backend (Node.js/TypeScript/Express/Prisma) + встроенный веб-дашборд для расчёта юнит-экономики магазина, продающего на Kaspi.kz (Магазин), Ozon и Wildberries. Разворачивается на Vercel бесплатно.
Сервер автоматически подтягивает заказы через официальные API площадок, считает себестоимость по вашей карточке товара, вычитает комиссии/логистику/ рекламу и показывает выручку, прибыль, маржу и ROI — по товарам, по площадкам и в динамике. Также умеет автоматически снижать цену на Kaspi, не опускаясь ниже заданного минимума (см. раздел про автобот ниже).
Node.js + TypeScript + Express + Prisma ORM + PostgreSQL (Neon). Дашборд — статический HTML/CSS/JS (без сборки), график на Chart.js. Хостинг — Vercel (serverless-функции + статика), без Docker.
Понадобится: аккаунт на vercel.com (можно войти через GitHub) и аккаунт на neon.tech (тоже можно через GitHub). Оба — бесплатно, без карты.
Vercel разворачивает проекты из Git-репозитория, поэтому сначала код нужно загрузить на GitHub (если ещё не там).
kaspi-ozon-unit-economics → Create repository.kaspi-unit-economics), регион выбери
ближайший к себе (например Europe), нажми Create project.DATABASE_URL.DIRECT_URL.postgresql://user:[email protected]/neondb?sslmode=require
(у "pooled" в адресе будет ещё -pooler перед .region).Сохрани обе строки куда-нибудь рядом — они понадобятся через минуту.
kaspi-ozon-unit-economics
(если не видно — нажми Adjust GitHub App Permissions и разреши доступ
к репозиторию).vercel.json внутри проекта..env.example,
заполненный своими значениями):| Имя переменной | Что вписать |
|---|---|
DATABASE_URL | "pooled" строка из Neon (шаг 2) |
DIRECT_URL | строка без пулинга из Neon (шаг 2) |
KASPI_API_TOKEN | токен из кабинета Kaspi (см. Часть 2 ниже) |
KASPI_MERCHANT_UID | ваш merchant ID Kaspi |
OZON_CLIENT_ID | Client-Id из кабинета Ozon |
OZON_API_KEY | Api-Key из кабинета Ozon |
WB_API_TOKEN | токен категории "Статистика" из кабинета Wildberries |
PRICE_FEED_SECRET | придумайте любую длинную случайную строку |
CRON_SECRET | придумайте ещё одну длинную случайную строку (другую) |
KASPI_DEFAULT_DELIVERY_ZONE | kazakhstan (или city/express) |
Площадки, которых у вас пока нет (например, ещё не продаёте на Ozon) — можно оставить пустыми или не добавлять вовсе, сервер просто пропустит их синхронизацию с предупреждением в логах, ничего не сломается.
prisma generate и prisma migrate deploy, это уже прописано
в vercel.json, ничего вручную запускать не нужно).https://kaspi-ozon-unit-economics-xxxx.vercel.app.
Открой её — должен открыться дашборд.https://ваш-проект.vercel.app/api/health — должно показать {"ok":true,...}.Если что-то не открылось — зайди в Vercel → твой проект → вкладка Deployments → открой последний деплой → Logs, там будет видна причина.
🛠 Если некоторые адреса (например
/api/orders/meta,/api/sync/status,/api/margin-calculator/categories) отвечают404, а другие (например/api/products) работают: это значит, что на Vercel сейчас лежит не полностью актуальная версия кода — скорее всего, в один из прошлых разов на GitHub были заменены не все файлы (например, только один-два файла точечно, а не весь проект целиком). Самое надёжное решение — не точечные правки, а полная замена всех файлов разом: удали всё содержимое репозитория на GitHub и загрузи архив заново целиком (см. Шаг 1 выше). Смешивание старых и новых файлов между собой — самая частая причина таких "частичных" поломок.
🛠 Если конкретно
/api/cron/warm(или/api/cron/sync,/api/cron/reprice) отвечает404, хотя остальной дашборд работает: в этой версии проекта у каждого cron-эндпоинта теперь свой отдельный файл (api/cron/warm.ts,api/cron/sync.ts,api/cron/reprice.ts) вместо одного общего catch-all файла[...slug].ts— у именных файлов в Vercel нет неоднозначности с маршрутизацией вложенных путей. Если после обновления 404 всё ещё появляется — почти наверняка на GitHub опять не дошли какие-то файлы: убедись, что в папкеapi/на GitHub есть все четыре файла:[...slug].ts,cron/sync.ts,cron/reprice.ts,cron/warm.ts, и папка_lib/cronAuth.ts.
🛠 Если увидишь ошибку
prisma: command not found(exit code 127) в Build Logs: с июля 2026 npm по умолчанию блокирует установочные скрипты пакетов (новая защита от вредоносных зависимостей). В этом проекте это уже исправлено — вpackage.jsonесть полеallowScripts, разрешающее нужные пакеты (prisma,@prisma/client,@prisma/engines,esbuild), а команда сборки вvercel.jsonиспользуетnpx prisma ...вместо гологоprisma .... Если ошибка всё равно появится с другим пакетом — в логах будет фраза видаN packages have install scripts not yet covered by allowScriptsсо списком имён; добавь их вallowScriptsвpackage.json("имя-пакета": true) и задеплой заново.
🛠 Если увидишь ошибку
No entrypoint found which imports express: Vercel иногда сам решает, что у вас «голое» Express-приложение (просто увидевexpressв зависимостях), и пытается сам найти файл-точку входа по стандартным именам — вместо того чтобы использовать нашу serverless- функциюapi/[...slug].ts. В этом проекте это уже исправлено: вvercel.jsonявно прописано"framework": null, что отключает это авто-определение. Если ошибка всё же повторится — зайди в Vercel → твой проект → Settings → General → Framework Preset и вручную выбери Other, затем передеплой (Deployments → ⋯ → Redeploy).
🛠 Про алиасы путей (
@/...) и ошибкуCannot find module: в исходном коде проекта больше вообще нет алиасов@/...— все импорты переписаны на обычные относительные пути (./utils/logger,../services/sync.serviceи т.п.). Раньше алиасы использовались для удобства чтения кода, но Vercel не умеет надёжно разворачивать их при сборке TypeScript-функции "на лету", что приводило к падению всего API с ошибкойCannot find module '@/...'. Теперь эта проблема невозможна структурно — убирать нечего. Плюсapi/[...slug].tsпо-прежнему подключает не сырые исходники изsrc/, а уже собранный JavaScript изdist/(создаётся командойnpm run buildвbuildCommand) — двойная защита от подобных ошибок.
🛠 Если увидишь ошибку
Ошибка конфигурации .envв Runtime Logs: значит какая-то переменная окружения задана некорректно (лишний пробел, кавычки, опечатка). Для большинства настроек (типаKASPI_DEFAULT_DELIVERY_ZONE) сервер теперь сам аккуратно подставит значение по умолчанию вместо того, чтобы падать целиком — но для по-настоящему обязательных переменных (DATABASE_URL) ошибка всё равно покажется, это осознанно: без базы данных сервер работать не может. Текст ошибки в логах точно укажет, какая именно переменная не в порядке — проверь её в Vercel → Settings → Environment Variables.
🛠 Если запросы к
/api/productsи другим эндпоинтам, работающим с базой, зависают и возвращают504 Gateway Timeout: классическая проблема связки Vercel (serverless) + Prisma + Neon — подключения к базе не переиспользовались между запросами и быстро исчерпывали маленький бесплатный лимит одновременных подключений Neon, из-за чего новые запросы зависали в ожидании свободного подключения. В этой версии проекта это уже исправлено вsrc/db/prisma.ts(подключение теперь всегда кэшируется, а не только в режиме разработки) — плюс сервер сам добавляет параметрыpgbouncer=true&connection_limit=1кDATABASE_URL, даже если их нет в самой переменной в Vercel. Ничего вручную менять не нужно. Если ошибка всё же повторится — проверь в Neon Dashboard, что используешь именно "pooled" строку подключения дляDATABASE_URL(в адресе должно быть-poolerперед.region), а не "direct".
Это самая важная часть, про которую нужно знать честно.
У Vercel на бесплатном тарифе встроенный Cron работает максимум 1 раз в сутки.
Это ограничение Vercel, а не наше — на платном тарифе (Pro, $20/мес) можно
чаще. В vercel.json уже настроены два ежедневных запуска "по умолчанию":
/api/cron/sync — синхронизация заказов, раз в сутки в 03:00 UTC/api/cron/reprice — автобот цены на Kaspi, раз в сутки в 04:00 UTCЭто будет работать "из коробки" без каких-либо действий с твоей стороны — но раз в сутки для автобота цены маловато, если хочется, чтобы цена подстраивалась в течение дня. Чтобы это работало чаще — бесплатно, без перехода на платный тариф Vercel — используй внешний планировщик:
Kaspi/Ozon/WB synchttps://ваш-проект.vercel.app/api/cron/sync?secret=ЗНАЧЕНИЕ_CRON_SECRET
(подставь то же значение, что вписал в CRON_SECRET в Vercel)Kaspi repricerhttps://ваш-проект.vercel.app/api/cron/reprice?secret=ЗНАЧЕНИЕ_CRON_SECRETDB warmuphttps://ваш-проект.vercel.app/api/cron/warm?secret=ЗНАЧЕНИЕ_CRON_SECRET504 Gateway Timeout на обычных страницах сайта (например, при
добавлении товара). Этот cronjob не даёт базе "засыпать", регулярно
её "трогая" лёгким запросом, который ничего не делает — просто держит
её в рабочем состоянии.Готово — теперь синхронизация, автобот цены и "подогрев" базы данных будут запускаться сами по расписанию, полностью бесплатно, без Docker и без постоянно работающего сервера.
⚠️ Даже если настроить проверку каждую минуту — сам Kaspi обновляет цены на сайте по XML-прайс-листу примерно раз в час (это ограничение самого Kaspi, см. раздел про автобот ниже). Более частая проверка ускорит реакцию на изменение цен конкурентов, но не ускорит появление новой цены на сайте Kaspi быстрее, чем позволяет сам Kaspi.
🛠 Если запросы (например, добавление товара) всё равно иногда падают с
504 Gateway Timeoutдаже после настройки "подогрева": у Vercel Hobby (бесплатного) есть фактическое ограничение на время выполнения функции около 10 секунд — это ограничение платформы, не нашего кода, и оно не снимается настройками. Сочетание "холодный старт функции + Neon просыпается от простоя" иногда всё равно может упереться в это время при первом запросе после долгого простоя сайта. Если такое случается часто, а не изредка — единственный полный "снятие" ограничения — платный тариф Vercel Pro ($20/мес, лимит времени вырастает до 60 сек по умолчанию). Для личного использования с редкими заходами это обычно не требуется — "подогрев" из шага 4 закрывает проблему в большинстве случаев.
🛠 Если 504 продолжается даже после "подогрева" — как диагностировать по-настоящему (а не гадать):
- Открой в браузере напрямую:
https://ваш-проект.vercel.app/api/cron/warm?secret=ЗНАЧЕНИЕ_CRON_SECRET— должно быстро (за секунды) вернуть{"ok":true,"ms":...}. Если и этот простейший запрос висит или падает — проблема не в "холодном старте", а в самом подключении к базе (неверныйDATABASE_URL, база на паузе/удалена, регион и т.п.).- Зайди в cron-job.org → открой созданный cronjob
DB warmup→ посмотри историю выполнений. Если там красные крестики (ошибки), а не зелёные галочки — "подогрев" не работает, и нужно чинить его настройку (неверный URL или секрет), а не сам сервер.- Zайди в Neon Dashboard → убедись, что проект не приостановлен/не удалён, и что используемая в
DATABASE_URLстрока — это именно "pooled" вариант (с-poolerв адресе).- После следующего деплоя открой Vercel → Logs и найди строки, начинающиеся с
🐢 [DB],❌ [DB]или⏱ ТАЙМАУТ— теперь сервер сам пишет в лог, сколько именно миллисекунд занял каждый запрос к базе, и что именно зависло. Эта информация — самая полезная для дальнейшей диагностики, если проблема не решится.
Способ 1 (рекомендуется) — через интерфейс дашборда:
X-Auth-Token).Способ 2 — через переменные окружения (как раньше): впишите токен в KASPI_API_TOKEN в Environment Variables Vercel. Используется, только если магазин не добавлен через дашборд.
Подробная документация партнёра: https://guide.kaspi.kz/partner/ru/shop/api
✅ Комиссия и логистика Kaspi считаются автоматически. У Kaspi нет эндпоинта, который отдаёт размер удержанной комиссии по заказу напрямую — поэтому сервер считает её сам по официальной тарифной таблице Kaspi (
src/integrations/kaspi.categories.ts, ставки по всем категориям 1-го уровня + точечные исключения для Аптеки/Аксессуаров/Телефонов/Товаров для животных) и тарифам Kaspi Доставки (src/integrations/kaspi.delivery.ts). Для точного расчёта укажите у каждого товара в разделе «Товары» его категорию Kaspi и вес — без этого комиссия по такому товару будет считаться нулевой, чтобы не искажать отчёт неверной ставкой.
⚠️ Честно про «синхронизацию каталога». У официального API Kaspi нет метода «отдай список всех моих товаров» — раздел «Товары» в Kaspi Гид содержит только методы добавления новых товаров, не чтения уже существующих. Поэтому кнопка «Синхронизировать каталог» на самом деле запускает синхронизацию заказов — а товары, которых ещё нет в базе, создаются автоматически по факту продажи (себестоимость по умолчанию 0, обязательно укажите её вручную). Это единственный честный способ получить каталог через официальный API, без логина в личный кабинет.
Client-Id и Api-Key.OZON_CLIENT_ID и OZON_API_KEY.Сервер получает заказы через /v3/posting/fbs/list и реальные удержания
(комиссия, логистика, доп. услуги) через /v3/finance/transaction/list —
это даёт фактические суммы, а не оценочные.
WB_API_TOKEN.⚠️ Устройство API у Wildberries заметно отличается от Kaspi/Ozon:
- Один заказ = одна строка = один товар (WB не группирует несколько товаров в один "заказ", как это делают Kaspi и Ozon).
- Статус заказа у WB здесь — упрощённый (
NEWилиCANCELLED): их Statistics API не отдаёт полный жизненный цикл заказа, в отличие от Kaspi.- Комиссия и логистика берутся из отдельного отчёта о реализации (
reportDetailByPeriod) и сопоставляются с заказами по полюsrid. Свежие заказы могут попасть в этот отчёт не сразу — тогда комиссия по ним будет временно 0 и подтянется сама при следующей синхронизации.
Юнит-экономика считается только для товаров, у которых указана себестоимость.
Для ~1000 товаров — массовая загрузка (быстрее всего):
sku — внутренний SKU (обязательно)name — название товара (обязательно)kaspiSku — артикул на Kaspi (для сопоставления с заказами)costPrice — себестоимостьkaspiTopCategory — категория Kaspi, должна дословно совпадать
с одним из значений выпадающего списка в форме добавления товара
(например Электроника, а не электроника или Электроника )SKU, Sku, sku — не важно).Для единичных товаров:
Если товар ещё не заведён вручную — при синхронизации заказов Kaspi он создастся автоматически (себестоимость 0 по умолчанию — обязательно проставь её позже, иначе юнит-экономика будет считать нулевую себестоимость для таких товаров).
Сервер умеет автоматически следить за ценами конкурентов на карточке товара и подстраивать твою цену, чтобы быть первым в списке продавцов — но не дешевле заданной тобой минимальной цены. Работает только для Kaspi — для Ozon и Wildberries автобота нет и не планируется.
Как это работает:
/api/cron/reprice (см. Часть 1, шаг 5) сервер читает
публичную страницу товара на Kaspi и находит самую низкую цену конкурента.repriceStep).
Если эта цена ниже твоей minPrice — бот останавливается на minPrice
и не уходит в убыток./api/kaspi/price-feed.xml.Как настроить:
https://ваш-проект.vercel.app/api/kaspi/price-feed.xml?token=ЗНАЧЕНИЕ_PRICE_FEED_SECRET
и один раз укажи её в кабинете Kaspi: Товары → Загрузить прайс-лист →
автоматическая загрузка.Честное предупреждение: чтение цен конкурентов идёт через обычную
публичную страницу товара (так же, как её видит покупатель в браузере) —
у Kaspi нет официального API "цены конкурентов" для продавцов, и хранение
пароля от кабинета Kaspi нигде не используется. Разметка страницы может
со временем измениться, тогда парсер в src/integrations/kaspi.scraper.ts
нужно будет поправить.
kaspiProductUrl.В списке «Последние заказы» есть действия для заказов Kaspi:
ACCEPTED_BY_MERCHANT.ASSEMBLE, указав количество мест.Печатную накладную для курьера всё равно нужно скачать и распечатать в личном кабинете Kaspi — API отдаёт только смену статуса, не готовый PDF.
🛠 Если запросы к товарам/заказам падают с
500(не504!), а/api/cron/warmпри этом отвечает быстро и успешно ({"ok":true}): дело не в скорости подключения к базе, а в том, что в самой базе данных физически нет нужных таблиц. Так бывает, если ни разу не была применена ни одна миграция —prisma migrate deployпросто нечего было применять (файлов миграций не было). В этой версии проекта такой файл уже есть (prisma/migrations/20260101000000_init/) — при следующем деплое он создаст все таблицы автоматически. Важно на будущее: если сам будешь менятьschema.prisma(добавлять поля и т.п.) — для каждого такого изменения нужно создавать новый файл миграции, иначе Vercel не узнает, что нужно поменять в реальной базе. Локально это делается командойnpm run prisma:migrate(см. Часть 4 ниже) — она подключается к базе, сравнивает её сschema.prismaи сама создаёт нужный SQL-файл; после этого файл нужно закоммитить вместе с остальными изменениями.
Если хочешь дорабатывать сервер у себя на компьютере перед тем, как заливать изменения на GitHub/Vercel:
npm install
cp .env.example .env
# впиши в .env те же DATABASE_URL/DIRECT_URL от Neon (можно тот же проект,
# что и для продакшена — Neon поддерживает несколько "веток" базы бесплатно)
npm run prisma:migrate
npm run dev
Дашборд откроется на http://localhost:3000. Фоновые задачи (синхронизация,
автобот цены) в режиме npm run dev по-прежнему работают через встроенный
планировщик (node-cron) — это удобно для локальных тестов. На самом Vercel
эта часть не используется, там всё через /api/cron/*, см. Часть 1.
kaspi-ozon-unit-economics/
vercel.json — конфигурация деплоя (cron, сборка, статика)
api/
[...slug].ts — catch-all: весь дашборд (товары, заказы и т.д.) через Express
cron/
sync.ts — отдельный файл-маршрут: POST /api/cron/sync
reprice.ts — отдельный файл-маршрут: POST /api/cron/reprice
warm.ts — отдельный файл-маршрут: POST /api/cron/warm
_lib/
cronAuth.ts — общая проверка секрета (папка с "_" — не маршрут, служебный код)
src/
expressApp.ts — Express-приложение (общее для локалки и Vercel)
server.ts — точка входа ТОЛЬКО для локальной разработки
cron/handlers.ts — логика фоновых задач (синхронизация, репрайсер, прогрев БД)
config/env.ts — загрузка и валидация .env
db/prisma.ts — подключение к БД
integrations/
kaspi.client.ts — клиент Kaspi Shop API (заказы, статусы, накладные)
kaspi.categories.ts — справочник комиссий Kaspi по категориям
kaspi.delivery.ts — тарифы Kaspi Доставки
kaspi.scraper.ts — чтение цен конкурентов / рейтинга с публичной страницы
ozon.client.ts — клиент Ozon Seller API
wb.client.ts — клиент Wildberries Statistics API
services/
sync.service.ts — сохранение заказов в БД + расчёт комиссий/логистики
analytics.service.ts — расчёт юнит-экономики
repricer.service.ts — логика автобота снижения цены
routes/ — REST API
jobs/scheduler.ts — node-cron планировщик (только для локальной разработки)
prisma/schema.prisma — модель данных (PostgreSQL)
public/ — дашборд (index.html, styles.css, dashboard.js)
| Метод | Путь | Описание |
|---|---|---|
| GET | /api/analytics/summary?from=&to=&marketplace= | Сводка юнит-экономики |
| GET | /api/analytics/by-product?from=&to= | Разбивка по товарам |
| GET | /api/analytics/timeseries?from=&to=&groupBy=day|week|month | Динамика по периодам |
| GET/POST/PUT/DELETE | /api/products | Товары и себестоимость |
| GET | /api/orders?from=&to=&marketplace= | Список заказов |
| GET/POST/DELETE | /api/expenses/ad-spend, /api/expenses/manual | Расходы на рекламу / прочее |
| POST | /api/sync/kaspi?days=7 | Запустить синхронизацию Kaspi вручную |
| POST | /api/sync/ozon?days=7 | Запустить синхронизацию Ozon вручную |
| POST | /api/sync/wb?days=7 | Запустить синхронизацию Wildberries вручную |
| GET | /api/sync/logs | Журнал синхронизаций |
| GET | /api/kaspi/price-feed.xml?token=... | XML-прайс-лист для автообновления цен в кабинете Kaspi |
| POST | /api/cron/sync?secret=... | Фоновая синхронизация всех площадок (для внешнего планировщика) |
| POST | /api/cron/reprice?secret=... | Фоновый цикл автобота цены Kaspi (для внешнего планировщика) |