commit 7b143e6372771566c0cfc5bfc8557fd72d8f7a2e Author: Ga1maz Date: Sun Feb 1 02:34:25 2026 +0500 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1e2d696 --- /dev/null +++ b/.env.example @@ -0,0 +1,17 @@ +# === Данные ФНС === +INN=123456789012 # ИНН самозанятого (12 цифр) +PASSWORD=your_fns_password # Пароль от приложения «Мой налог» + +# === Настройки приложения === +APPNAME=Моя компания # Название, отображаемое в чеке ФНС + +# === SMTP настройки === +SMTP_HOST=smtp.gmail.com # SMTP сервер +SMTP_PORT=587 # SMTP порт (обычно 587 или 465) +SMTP_USER=noreply@example.com # Email для отправки +SMTP_PASS=app_password # Пароль приложения для email +SMTP_MAIL_FROM=noreply@example.com # Email отправителя + +# === Безопасность === +API_PASS=your_secure_password_here # Пароль для доступа к API (используйте сложный!) +PORT=4000 # Порт, на котором будет работать сервер \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13b7380 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Node modules + +node_modules/ + +# Environment variables (ignore real .env, keep example) + +.env +!.env.example + +# Logs + +logs/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# OS files + +.DS_Store +Thumbs.db + +# Optional npm cache directory + +.npm + +# Temporary files + +*.tmp +*.temp + +# Build outputs + +dist/ +build/ + +# Ignore everything by default except these files + +* + +!.gitignore +!.env.example +!package.json +!README.md +!server.js diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..139fe06 --- /dev/null +++ b/README.MD @@ -0,0 +1,296 @@ +# 🧾 FNS Receipt Service + +> Автоматизированный сервис для создания чеков самозанятого через ФНС (Мой налог) с отправкой детализированных чеков клиентам по email + +--- + +## 📋 Содержание + +* [Описание](#-описание) +* [Возможности](#-возможности) +* [Установка](#-установка) +* [Настройка](#️-настройка) +* [Использование](#-использование) +* [API Reference](#-api-reference) +* [Переменные окружения](#-переменные-окружения-env) +* [Ограничения и особенности](#-ограничения-и-особенности) +* [FAQ](#-faq) +* [Лицензия](#-лицензия) + +--- + +## 🎯 Описание + +**FNS Receipt Service** — Node.js сервис для автоматического создания чеков самозанятого через API ФНС («Мой налог») и отправки клиентам детализированных чеков по email. + +Сервис учитывает ограничения официального API ФНС и использует практику, применяемую платёжными системами. + +### Ключевая особенность + +⚠️ **Ограничение API ФНС**: официальный API самозанятых **не поддерживает несколько позиций в одном чеке**. + +Используется гибридный подход: + +* **В ФНС** → отправляется **одна агрегированная позиция** с общей суммой дохода +* **Клиенту по email** → отправляется **детализированный HTML‑чек** со всеми товарами/услугами + +Это полностью соответствует требованиям ФНС: налоговый учёт ведётся по сумме дохода, а не по товарным позициям. + +Если вы обнаружили баг или есть идея для улучшения сервиса, пожалуйста, создайте [Issue](https://github.com/ga1maz/fns-receipt-service/issues) в репозитории. + +--- + +## ✨ Возможности + +* ✅ Создание чеков самозанятого через официальный API ФНС +* ✅ Retry‑механизм при временных ошибках ФНС (до 3 попыток) +* ✅ Детализированные HTML‑чеки для клиентов +* ✅ Отправка официальной ссылки на чек ФНС +* ✅ Защита API паролем +* ✅ Простая HTTP‑интеграция с любыми сервисами + +--- + +## 🚀 Установка + +### Требования + +* Node.js **16.x+** +* npm или yarn +* Активный статус самозанятого в «Мой налог» + +### Установка + +```bash +git clone https://github.com/ga1maz/fns-receipt-service.git +cd fns-receipt-service +npm install +``` + +### Основные зависимости + +```json +{ + "express": "^4.18.0", + "lknpd-nalog-api": "^1.0.0", + "nodemailer": "^6.9.0", + "dotenv": "^16.0.0" +} +``` + +--- + +## ⚙️ Настройка + +### 1. Создание `.env` + +```bash +cp .env.example .env +``` + +### 2. Переменные окружения + +```env +# === ФНС (Мой налог) === +INN=123456789012 +PASSWORD=your_fns_password + +# === Приложение === +APPNAME=Моя компания + +# === SMTP === +SMTP_HOST=smtp.gmail.com +SMTP_PORT=587 +SMTP_USER=noreply@example.com +SMTP_PASS=app_password +SMTP_MAIL_FROM=noreply@example.com + +# === Безопасность === +API_PASS=your_secure_password_here +PORT=4000 +``` + +> Поддерживаются любые SMTP‑провайдеры (Gmail, Яндекс, Mail.ru и др.). Для Gmail рекомендуется использовать **пароль приложения**. + +--- + +## 🎮 Использование + +### Запуск сервера + +```bash +node index.js +``` + +Сервер запускается на: + +``` +http://localhost:4000 +``` + +### Проверка состояния сервиса + +```bash +GET /health +``` + +Сервис возвращает состояние подключения и возможные ошибки: + +```json +{ + "status": "ok", // общий статус сервера (ok, degraded, error) + "connect_to_fns": "ok", // соединение с API ФНС + "smtp": "ok" // статус SMTP +} +``` + +Примеры возможных ошибок `health`: + +* `connect_to_fns: error` — проблемы с авторизацией или доступностью API ФНС +* `smtp: error` — проблемы с подключением к SMTP серверу (неверный логин/пароль, блокировка сервера) +* `status: degraded` — сервис работает, но есть неполадки с одним из сервисов + +Если вы что-то не заметили или есть идеи для доработки, создайте [Issue](https://github.com/ga1maz/fns-receipt-service/issues) в репозитории. + +--- + +## 📡 API Reference + +### POST `/api/v1/create-receipt` + +Создаёт **один чек дохода в ФНС** на общую сумму всех позиций и отправляет клиенту **детализированный HTML‑чек по email**. + +### 🔐 Авторизация + +Пароль передаётся **в теле запроса**: + +```json +{ + "api_pass": "..." +} +``` + +Значение сравнивается с `API_PASS` из `.env`. + +### 📥 Request Body + +```json +{ + "api_pass": "your_secure_password_here", + "email": "client@example.com", + "items": [ + { + "id": "sku-001", + "name": "Консультация по налогам", + "price": 5000, + "quantity": 1 + } + ] +} +``` + +### ⚙️ Логика обработки + +* Общая сумма: + +``` +Σ (price × quantity) +``` + +* В ФНС отправляется: + + * `name` → `APPNAME` + * `amount` → общая сумма + * `quantity` → `1` + +* При ошибке ФНС: + + * до **3 попыток** + * задержка **2 секунды** между попытками + +### 📤 Response (Success) + +**HTTP 200 OK** + +```json +{ + "success": true, + "receiptId": "200uhagtun", + "printLink": "https://lknpd.nalog.ru/api/v1/receipt/INN/receiptId/print" +} +``` + +> Поля `totalAmount` и `email` **не возвращаются** в ответе. + +### ❌ Ошибки + +#### 400 — Неверные данные + +```json +{ "error": "Неверные данные" } +``` + +#### 401 — Unauthorized + +```json +{ "error": "Unauthorized" } +``` + +#### 500 — Internal Server Error + +```json +{ "error": "Не удалось создать чек" } +``` + +--- + +## 🧩 Переменные окружения (.env) + +| Переменная | Назначение | +| ---------------- | -------------------- | +| `API_PASS` | Пароль доступа к API | +| `INN` | ИНН самозанятого | +| `PASSWORD` | Пароль «Мой налог» | +| `APPNAME` | Название в чеке ФНС | +| `SMTP_HOST` | SMTP сервер | +| `SMTP_PORT` | SMTP порт | +| `SMTP_USER` | SMTP логин | +| `SMTP_PASS` | SMTP пароль | +| `SMTP_MAIL_FROM` | Email отправителя | +| `PORT` | Порт сервера | + +--- + +## 📝 Ограничения и особенности + +* ❗ Один запрос = **один чек дохода** +* ❗ Детализация **не передаётся в ФНС**, только в email +* ✔️ Поддержка дробных количеств +* ✔️ HTML‑чек с таблицей позиций +* ✔️ Официальная ссылка ФНС + +--- + +## ❓ FAQ + +**Это законно?** +Да. ФНС учитывает доход суммарно, детализация — дополнительный сервис. + +**Можно ли создать несколько чеков на один платёж?** +Нет. Один платёж = один чек. + +**Нужно ли хранить `receiptId`?** +Рекомендуется для поддержки клиентов и учёта. + +**Нужен ли IP РФ?** +Да, API ФНС работает только с IP РФ. + +--- + +## 📄 Лицензия + +MIT License © 2026 + +--- + +**Сделано с ❤️ для самозанятых** \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..ed50c42 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "checks", + "version": "1.0.0", + "description": "Checks for the self-employed", + "license": "ISC", + "author": "Ga1maz", + "type": "module", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "dependencies": { + "body-parser": "^2.2.2", + "dotenv": "^17.2.3", + "express": "^5.2.1", + "lknpd-nalog-api": "^1.0.1", + "nodemailer": "^7.0.13" + } +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..50a736e --- /dev/null +++ b/server.js @@ -0,0 +1,176 @@ +import express from "express"; +import bodyParser from "body-parser"; +import nodemailer from "nodemailer"; +import dotenv from "dotenv"; +import pkg from "lknpd-nalog-api"; + +dotenv.config(); + +const { NalogApi } = pkg; + +const app = express(); +app.use(bodyParser.json()); + +const PORT = process.env.PORT || 4000; +const MAX_RETRIES = 3; + +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_HOST, + port: Number(process.env.SMTP_PORT), + secure: false, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASS + } +}); + +const nalogApi = new NalogApi({ + inn: process.env.INN, + password: process.env.PASSWORD +}); + +async function createReceiptWithRetry(income, retries = MAX_RETRIES) { + for (let attempt = 1; attempt <= retries; attempt++) { + try { + return await nalogApi.addIncome(income); + } catch (err) { + console.error(`Попытка ${attempt} не удалась`, err.message || err); + if (attempt === retries) throw err; + await new Promise(r => setTimeout(r, 2000)); + } + } +} + +app.get("/health", async (req, res) => { + const result = { + status: "ok", + connect_to_fns: "ok", + smtp: "ok", + }; + + try { + await transporter.verify(); + } catch (err) { + result.smtp = "error"; + result.status = "degraded"; + } + + try { + await nalogApi.getUserInfo(); + } catch (err) { + console.error("FNS health error:", err.message || err); + result.connect_to_fns = "error"; + result.status = "degraded"; + } + + res.json(result); +}); + +app.post("/api/v1/create-receipt", async (req, res) => { + try { + const { api_pass, email, items } = req.body; + + if (api_pass !== process.env.API_PASS) { + return res.status(401).json({ error: "Unauthorized" }); + } + + if (!email || !Array.isArray(items) || items.length === 0) { + return res.status(400).json({ error: "Неверные данные" }); + } + + const total = items.reduce( + (sum, i) => sum + i.price * (i.quantity || 1), + 0 + ); + + const income = { + name: `${process.env.APPNAME}`, + amount: Number(total.toFixed(2)), + quantity: 1 + }; + + const receiptId = await createReceiptWithRetry(income); + + const printLink = `https://lknpd.nalog.ru/api/v1/receipt/${process.env.INN}/${receiptId}/print`; + + const rows = items.map(i => { + const qty = i.quantity || 1; + return ` + + ${i.id} + ${i.name} + ${i.price.toFixed(2)} + ${qty} + ${(i.price * qty).toFixed(2)} + + `; + }).join(""); + + const html = ` + + + + +Чек + + + + + + + +
+ + + + +
+ +

Ваш чек

+

Чек сформирован в ФНС (Мой налог)

+ + + + + +${rows} +
IDНазваниеЦенаКол-воСумма
+ +

Итого: ${total.toFixed(2)} ₽

+ + +Посмотреть чек + + +

+${process.env.APPNAME} +

+
+
+ + +`; + + await transporter.sendMail({ + from: process.env.SMTP_MAIL_FROM, + to: email, + subject: `Чек ${process.env.APPNAME}`, + html + }); + + res.json({ + success: true, + receiptId, + printLink + }); + + } catch (err) { + console.error("Ошибка:", err); + res.status(500).json({ error: "Не удалось создать чек" }); + } +}); + +app.listen(PORT, () => { + console.log(`✅ Сервер запущен: http://localhost:${PORT}`); +});