import express from "express"; import bodyParser from "body-parser"; import nodemailer from "nodemailer"; import dotenv from "dotenv"; import pkg from "lknpd-nalog-api"; import fs from "fs/promises"; import path from "path"; import { fileURLToPath } from "url"; dotenv.config(); const { NalogApi } = pkg; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); app.use(bodyParser.json()); const PORT = process.env.PORT || 4000; const MAX_RETRIES = 3; const ERROR_FILE = path.join(__dirname, "error.json"); const ADMIN_EMAIL = process.env.ADMIN_EMAIL; 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)); } } } async function saveToErrorFile(errorData) { try { let errors = []; try { const data = await fs.readFile(ERROR_FILE, "utf8"); const parsedData = JSON.parse(data); if (Array.isArray(parsedData)) { errors = parsedData; } } catch (err) { } errors.push({ ...errorData, timestamp: new Date().toISOString(), retryAttempt: 0 }); await fs.writeFile(ERROR_FILE, JSON.stringify(errors, null, 2)); console.log(`Ошибка сохранена в ${ERROR_FILE}`); } catch (err) { console.error("Не удалось сохранить ошибку в файл:", err); } } async function notifyAdmin(errorData) { try { const html = `
Время: ${new Date().toLocaleString()}
Email клиента: ${errorData.email}
Сумма: ${errorData.amount} ₽
Ошибка: ${errorData.error}
Данные заказа:
${JSON.stringify(errorData.items, null, 2)}
Ошибка сохранена в error.json для последующей обработки.
Пробейте чек вручную через приложение Мой налог и вручную отправте клиенту чек по email.
`; await transporter.sendMail({ from: process.env.SMTP_MAIL_FROM, to: ADMIN_EMAIL, subject: `Ошибка создания чека ${process.env.APPNAME}`, html }); console.log(`Администратор ${ADMIN_EMAIL} уведомлен об ошибке`); } catch (err) { console.error("Не удалось отправить уведомление администратору:", err); } } 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 `
|