tmc-v2/main.js

278 lines
8.2 KiB
JavaScript

const express = require('express');
const path = require('path');
const fs = require('fs');
const base64url = require('base64url');
const multer = require('multer');
const { log } = require('console');
const bodyParser = require('body-parser');
const { convertSvgToPng } = require('./utils/imageConverter');
const websocket = require('./websocket');
const translations = require('./translations');
const app = express();
const upload = multer({ storage: multer.memoryStorage() });
// Configure EJS
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(express.urlencoded({ extended: true }));
app.use(bodyParser.json({ limit: "50mb" }));
// Serve static files
app.use(express.static(path.join(__dirname, 'public')));
// Initialize WebSocket server
const server = app.listen(80, () => {
console.log('Server running on port 80');
});
websocket.initialize(server);
// Static images mapping
const staticImages = {
mulberry: 'am_mulberry_logo_wide_color.png' ,
home: 'payment.png',
scan: 'Scanner.png',
printer: 'Check.png',
settings: 'Settings.png',
pinpad: 'pinpad.png',
cashier: 'cashier.png',
menu: 'Menu.png',
};
// Topics list
const topics = [
{ deviceName: 'M50F_Second', deviceID: '01534090202502210057' },
{ deviceName: 'M50F_Igor', deviceID: '01534090202502210142' },
{ deviceName: 'M60_Igor', deviceID: '01620013202312220735' },
{ deviceName: 'M70_Igor', deviceID: '01723060202412010280' },
{ deviceName: 'M30_Igor', deviceID: '01364100202503060196' },
{ deviceName: 'M80F_Ahmed', deviceID: '01804042025072100015' },
{ deviceName: 'M80K_Ahmed', deviceID: '78040132025082600001' },
{ deviceName: 'M50F_Ahmed', deviceID: '01534090202502210065' },
{ deviceName: 'M50F_Ahmed', deviceID: '01534090202502210065' },
{ deviceName: 'M60_Ahmed', deviceID: '01620013202312221500' },
{ deviceName: 'M30_Ahmed', deviceID: '01364100202503060115' },
{ deviceName: 'M20_Ahmed', deviceID: '01220110202305150514' },
{ deviceName: 'M70_Ahmed', deviceID: '01723060202412010160' },
{ deviceName: 'M60F-NOSN', deviceID: 'm60f_pos_no_sn' }
];
// Sample configuration
const defaultConfig = {
type: "configuration",
navItems: [
{
navPage: "NAV_HOME",
navName: "Оплата",
icon: { type: "resource", value: "home" },
position: 0,
enabled: true
},
{
navPage: "NAV_SCAN",
navName: "Сканнер",
icon: { type: "resource", value: "scan" },
position: 1,
enabled: true
},
{
navPage: "NAV_PRINTER",
navName: "Принтер",
icon: { type: "resource", value: "printer" },
position: 2,
enabled: true
},
{
navPage: "NAV_SETTINGS",
navName: "Настройки",
icon: { type: "resource", value: "settings" },
position: 3,
enabled: true
}
],
mainLogo: {
type: "base64",
value: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABlUAAAE "
},
footerLogo: {
type: "base64",
value: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaQAAAWY"
},
printLogo: {
type: "base64",
value: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABaQAAAWY"
},
printURL: "www.mulberrypos.ru",
footerLogoVisibility: "true",
bankMode: false,
footerGreetingText: "Привет Игорь! ))",
footerText: "Mulberry, OOO Demo ©2025",
primaryColor: "#002233",
secondaryColor: "#67777eff",
};
// Routes
app.get('/', (req, res) => {
res.render('index', {
staticImages: Object.keys(staticImages),
topics,
defaultConfig,
connectedDevices: websocket.getConnectedDevices()
});
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
websocketConnections: websocket.connectedDevices.size
});
});
// Graceful shutdown
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);
function gracefulShutdown() {
console.log('Shutting down gracefully...');
websocket.cleanup();
server.close(() => {
console.log('Server closed');
process.exit(0);
});
}
app.post('/sendToDevice', (req, res) => {
const { config, topic } = req.body;
console.log(`Sending config to device ${topic}`);
const success = websocket.sendToDevice(topic, config);
res.status(success ? 200 : 404).send(success ? 'Message sent' : 'Device not connected');
});
// Payment endpoints
// Payment endpoint
app.get('/pay', (req, res) => {
try {
const paymentData = JSON.parse(base64url.decode(req.query.data));
if (!paymentData.sn || !paymentData.amount) {
return res.redirect(`/error?message=${encodeURIComponent('Invalid payment data')}`);
}
console.log(paymentData);
// Convert the amount from cents to dollars
const amountInDollars = parseFloat(paymentData.amount) / 100;
// Format the amount to two decimal places
const formattedAmount = amountInDollars.toFixed(2);
const language = paymentData.lang;
// Get translations for the selected language
const t = translations[language] || translations.en;
// const event = new Date(paymentData.time);
// const russianDate = event.toLocaleString("ru-RU", { timeZone: "Etc/GMT-3" })
const event = new Date(paymentData.time);
const russianDate = event.toLocaleString("ru-RU", {
timeZone: "Europe/Moscow",
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
}).replace(',', '');
// Format data for the receipt
const receiptData = {
deviceId: paymentData.sn,
t:t,
appLogo: paymentData.appLogo,
appColor: paymentData.appColor,
currencyS: paymentData.currencyS,
paymentMethod: "QR Code",
amount: formattedAmount,
timestamp: russianDate
,
transactionId: `TXN-${Math.random().toString(36).substr(2, 9).toUpperCase()}`
};
// Render EJS template with data
res.render('receipt', {
title: 'DatexPay Virtual Bank - Receipt',
...receiptData
});
} catch (error) {
res.redirect(`/error?message=${encodeURIComponent(error.message)}`);
}
});
app.post('/pay-command', (req, res) => {
console.log("pay command");
const id = req.query.id;
websocket.sendToDevice(id, {
type: 'payment',
action: 'PRINT_RECEIPT',
status : "success" });
res.send({ message: 'Receipt print command sent!' });
});
app.post('/cancel-command', (req, res) => {
const id = req.query.id;
console.log("cancel command id :" + id);
websocket.sendToDevice(id, { type: 'payment', action: 'REJECT_RECEIPT',
status : "failed" });
res.send({ message: 'Receipt rejection command sent!' });
});
// Error page
app.get('/error', (req, res) => {
res.render('error', {
title: 'Payment Error',
message: req.query.message || 'An unknown error occurred'
});
});
// File upload
app.post('/upload', upload.single('image'), async (req, res) => {
try {
if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' });
}
let base64Data;
const isSvg = req.body.isSvg === 'true';
if (isSvg) {
const svgContent = req.file.buffer.toString('utf-8');
const pngBuffer = await convertSvgToPng(svgContent, 'LARGE');
base64Data = pngBuffer.toString('base64');
} else {
base64Data = req.file.buffer.toString('base64');
}
res.json({
success: true,
base64: `data:image/${isSvg ? 'png' : req.file.mimetype.split('/')[1]};base64,${base64Data}`,
fileName: req.file.originalname,
inputName: req.body.inputName
});
} catch (error) {
console.error('Upload error:', error);
res.status(500).json({ error: 'Image processing failed' });
}
});