diff --git a/app.js b/app.js
new file mode 100644
index 0000000..c310399
--- /dev/null
+++ b/app.js
@@ -0,0 +1,264 @@
+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 = {
+ home: 'payment.png',
+ scan: 'Scanner.png',
+ printer: 'Check.png',
+ settings: 'Settings.png',
+ pinpad: 'pinpad.png',
+ cashier: 'cashier.png',
+};
+
+// Topics list
+const topics = [
+ { deviceName: 'M50F_Igor', deviceID: '01534090202502210142' },
+ { deviceName: 'M60_Igor', deviceID: '01620013202312220735' },
+ { deviceName: 'M70_Igor', deviceID: '01723060202412010280' },
+ { deviceName: 'M30_Igor', deviceID: '01364100202503060196' },
+
+ { 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"
+ },
+ footerLogoVisibility: "true",
+ footerGreetingText: "Привет Игорь! ))",
+ footerText: "Mulberry, OOO Demo ©2025",
+ primaryColor: "#002233"
+};
+
+// 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' });
+ }
+});
\ No newline at end of file
diff --git a/main copy.js b/main copy.js
deleted file mode 100644
index ffa35df..0000000
--- a/main copy.js
+++ /dev/null
@@ -1,71 +0,0 @@
-const express = require('express');
-const mysql = require('mysql2/promise');
-const path = require('path');
-const app = express();
-const config = require("./config/db.config.js");
-const port = 8080;
-
-const dbConfig = {
- host: config.host,
- user: config.user,
- password: config.password,
- database: config.database,
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0
-};
-
-app.use(express.json({ limit: '50mb' }));
-app.use(express.static(path.join(__dirname, 'public'))); // Serve static files
-
-async function uploadPriomToMySQL(jsonRows, tableName) {
- const conn = await mysql.createConnection(dbConfig);
-
- // This SQL will be used to check for existing records
- const checkSql = `SELECT COUNT(*) as count FROM ${tableName} WHERE SerialNumber = ? AND filename = ?`;
- // This SQL will be used to insert new records
- const insertSql = `INSERT INTO ${tableName} (PriomID, Model, SerialNumber, Problem, filename) VALUES (?, ?, ?, ?, ?)`;
-
- try {
- await Promise.all(jsonRows.map(async (jsonRow) => {
- const values = [
- jsonRow['№ п/п'] || null,
- jsonRow['Оборудование'] || null,
- jsonRow['Серийный номер'] || null,
- jsonRow['Неисправность'] || null,
- jsonRow['filename'] || null
- ];
-
- // Check if the combination of SerialNumber and filename exists
- const [rows] = await conn.execute(checkSql, [values[2], values[4]]);
- if (rows[0].count === 0) {
- // Insert only if the combination doesn't exist
- await conn.execute(insertSql, values);
- }
- }));
- } catch (err) {
- throw new Error(err.message);
- } finally {
- await conn.end();
- }
-}
-
-app.post('/api/upload', async (req, res) => {
- // console.log("post upload works")
- const jsonRows = req.body;
-
- if (!Array.isArray(jsonRows) ) {
- return res.status(400).json({ error: 'Invalid request data' });
- }
-
- try {
- await uploadPriomToMySQL(jsonRows , 'priom');
- res.status(200).json({ message: 'Data uploaded successfully' });
- } catch (error) {
- res.status(500).json({ error: 'Internal server error: ' + error.message });
- }
-});
-
-app.listen(port, () => {
- console.log(`Server running at http://localhost:${port}`);
-});
diff --git a/main.js b/main.js
deleted file mode 100644
index dc5e4b7..0000000
--- a/main.js
+++ /dev/null
@@ -1,227 +0,0 @@
-const express = require('express');
-const path = require('path');
-const fs = require('fs');
-const base64url = require('base64url');
- const multer = require('multer');
-const mqtt = require('mqtt');
-const { log } = require('console');
- const bodyParser = require('body-parser');
-
- const { convertSvgToPng } = require('./utils/imageConverter');
-
-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')));
-
-// Static images mapping (matches Android resources)
-const staticImages = {
- home: 'am_card_pay_black.png',
- scan: 'am_barcode_black.png',
- printer: 'am_receipt_cart_black.png',
- settings: 'am_settings_black.png',
- mulberry: 'am_mulberry_logo_wide_color.png',
- // ... add other images
-};
-
-// MQTT Client setup
-const client = mqtt.connect('mqtt://192.168.1.199', {
- username: 'ahkad',
- password: 'Ksenia11241124m'
-});
-
-// Topics list
-const topics = ['pos/update', 'pos/update2', 'pos/update3'];
-
-// Sample configuration data structure
-const defaultConfig = {
- type: "configuration",
- navItems: [
- {
- navPage: "NAV_HOME",
- navName: "Payment",
- icon: { type: "resource", value: "home" },
- position: 0,
- enabled: true
- },
- {
- navPage: "NAV_SCAN",
- navName: "Scanner",
- icon: { type: "resource", value: "scan" },
- position: 1,
- enabled: true
- },
- {
- navPage: "NAV_PRINTER",
- navName: "Printer",
- icon: { type: "resource", value: "printer" },
- position: 2,
- enabled: true
- },
- {
- navPage: "NAV_SETTINGS",
- navName: "Settings",
- 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"
- },
- footerLogoVisibility: "ture",
- footerGreetingText: "Привет Игорь! ))",
- footerText: "Mulberry, OOO Demo ©2025",
- primaryColor: "#002233"
-};
-
-
-client.on('connect', () => {
- console.log('Connected to MQTT broker');
-});
-
-client.on('error', (err) => {
- console.error('MQTT error:', err);
-});
-
-// Payment endpoint
-app.get('/pay', (req, res) => {
- try {
- const paymentData = JSON.parse(base64url.decode(req.query.data));
-
- if (!paymentData.deviceId || !paymentData.paymentMethod || !paymentData.amount) {
- return res.redirect(`/error?message=${encodeURIComponent('Invalid payment data')}`);
- }
- console.log(paymentData);
-
- // Format data for the receipt
- const receiptData = {
- deviceId: paymentData.deviceId,
- appLogo: paymentData.appLogo,
- appColor: paymentData.appColor,
- paymentMethod: paymentData.paymentMethod,
- amount: paymentData.amount.toFixed(2),
- timestamp: new Date(paymentData.timestamp).toLocaleString(),
- 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)}`);
- }
-});
-
-// ANSI escape code for grey text
-const grey = '\x1b[90m';
-const reset = '\x1b[0m'; // Reset to default color
-
-// Handle Pay command
-app.post('/pay-command', (req, res) => {
- const id = req.query.id; // Get the id from the query parameter
- const timestamp = new Date().toLocaleString(); // Get the current date and time
- console.log(`${grey}[${timestamp}] pay command on id: ${id}${reset}`);
-
- client.publish(id, 'PRINT_RECEIPT');
- res.send({ message: 'Receipt printed successfully!' });
-});
-
-// Handle Cancel command
-app.post('/cancel-command', (req, res) => {
- const id = req.query.id; // Get the id from the query parameter
- const timestamp = new Date().toLocaleString(); // Get the current date and time
- console.log(`${grey}[${timestamp}] cancel command on id: ${id}${reset}`);
-
- client.publish(id, 'REJECT_RECEIPT');
- res.send({ message: 'Receipt rejected!' });
-});
-
-// Error page
-app.get('/error', (req, res) => {
- res.render('error', {
- title: 'Payment Error',
- message: req.query.message || 'An unknown error occurred'
- });
-});
-
-
-// app.post('/upload', upload.single('image'), (req, res) => {
-// const base64 = fs.readFileSync(req.file.path, 'base64');
-// fs.unlinkSync(req.file.path); // Clean up
-// res.json({ base64: `data:image/png;base64,${base64}` });
-// });
-
-// In your upload route handler
-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) {
- // Convert SVG to PNG
- const svgContent = req.file.buffer.toString('utf-8');
- const pngBuffer = await convertSvgToPng(svgContent, 'LARGE');
- base64Data = pngBuffer.toString('base64');
- } else {
- // Use original image for non-SVG
- 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' });
- }
-});
-
-// Routes
-app.get('/', (req, res) => {
- res.render('index', {
- staticImages: Object.keys(staticImages),
- topics,
- defaultConfig
- });
-
-});
-app.post('/publish', (req, res) => {
-
- const { config, topic } = req.body;
- console.log("published config on topic" + topic);
- client.publish(topic, JSON.stringify(config));
- res.sendStatus(200);
-});
-
-
-app.listen(80, () => {
- console.log('Server running on port 80');
-});
\ No newline at end of file
diff --git a/main_test.js b/main_test.js
deleted file mode 100644
index 4ca8067..0000000
--- a/main_test.js
+++ /dev/null
@@ -1,174 +0,0 @@
-const express = require('express');
-const crypto = require('crypto');
-const app = express();
-const port = 8080;
-
-
-const base64url = require('base64url'); // npm install base64url
-
-app.get('/pay', (req, res) => {
- try {
- // Decode and parse the payment data
- const paymentData = JSON.parse(base64url.decode(req.query.data));
-
- // Validate required fields
- if (!paymentData.deviceId || !paymentData.paymentMethod || !paymentData.amount) {
- return res.status(400).send(invalidRequestResponse());
- }
-
- // Generate a beautiful receipt
- res.send(generateReceiptHTML(
- paymentData.deviceId,
- paymentData.paymentMethod,
- paymentData.amount,
- paymentData.timestamp
- ));
-
- } catch (error) {
- res.status(400).send(errorResponse(error));
- }
-});
-
-// Helper function to generate receipt HTML
-function generateReceiptHTML(deviceId, paymentMethod, amount, timestamp) {
- const paymentDate = new Date(timestamp).toLocaleString();
- return `
-
-
-
- DatexPay Virtual Bank - Receipt
-
-
-
-
-
-
-
-
-
-
-
-
- Device ID:
- ${deviceId}
-
-
- Payment Method:
- ${paymentMethod}
-
-
- Amount:
- $${amount.toFixed(2)}
-
-
- Date:
- ${paymentDate}
-
-
-
-
-
-
-
-
-
-`;
-}
-
-// Helper function to generate error response
-function errorResponse(error) {
- return `
-
-
-
- Error - DatexPay
-
-
-
-
-
-
-
Payment Processing Error
-
${error.message}
-
Please try again or contact support.
-
-
-
-`;
-}
-
-// Generate a random transaction ID
-function generateTransactionId() {
- return 'TXN-' + Math.random().toString(36).substr(2, 9).toUpperCase();
-}
-
-
-app.listen(port, () => {
- console.log(`Test server running at http://localhost:${port}/`);
-});
\ No newline at end of file
diff --git a/package.json b/package.json
index 50f5f84..059a75d 100644
--- a/package.json
+++ b/package.json
@@ -26,7 +26,8 @@
"mysql2": "^3.11.0",
"sharp": "^0.34.3",
"svg2img": "^1.0.0-beta.2",
- "tmp": "^0.2.3"
+ "tmp": "^0.2.3",
+ "ws": "^8.18.3"
},
"devDependencies": {
"nodemon": "^3.1.0"
diff --git a/public/css/receipt.css b/public/css/receipt.css
index 060a740..8d47a3d 100644
--- a/public/css/receipt.css
+++ b/public/css/receipt.css
@@ -13,7 +13,7 @@ body {
width: 300px;
background: white;
padding: 25px;
- box-shadow: 0 0 10px rgba(0,0,0,0.1);
+ /* box-shadow: 0 0 10px rgba(0,0,0,0.1); */
border-top: 5px solid #00b8d7;
}
.header {
diff --git a/public/images/Card.png b/public/images/Card.png
new file mode 100644
index 0000000..f8ef77f
Binary files /dev/null and b/public/images/Card.png differ
diff --git a/public/images/Check.png b/public/images/Check.png
new file mode 100644
index 0000000..aa315da
Binary files /dev/null and b/public/images/Check.png differ
diff --git a/public/images/Discard.png b/public/images/Discard.png
new file mode 100644
index 0000000..20a3f81
Binary files /dev/null and b/public/images/Discard.png differ
diff --git a/public/images/Menu.png b/public/images/Menu.png
new file mode 100644
index 0000000..00e80b1
Binary files /dev/null and b/public/images/Menu.png differ
diff --git a/public/images/Refund.png b/public/images/Refund.png
new file mode 100644
index 0000000..b29dede
Binary files /dev/null and b/public/images/Refund.png differ
diff --git a/public/images/Scaner.png b/public/images/Scaner.png
new file mode 100644
index 0000000..cb3dbc0
Binary files /dev/null and b/public/images/Scaner.png differ
diff --git a/public/images/Settings.png b/public/images/Settings.png
new file mode 100644
index 0000000..b17768b
Binary files /dev/null and b/public/images/Settings.png differ
diff --git a/public/images/am_mulberry_logo_wide_color.png b/public/images/am_mulberry_logo_wide_color.png
new file mode 100644
index 0000000..d0ffb80
Binary files /dev/null and b/public/images/am_mulberry_logo_wide_color.png differ
diff --git a/public/images/cashier.png b/public/images/cashier.png
new file mode 100644
index 0000000..26c173f
Binary files /dev/null and b/public/images/cashier.png differ
diff --git a/public/images/drawable/Card.png b/public/images/drawable/Card.png
new file mode 100644
index 0000000..f8ef77f
Binary files /dev/null and b/public/images/drawable/Card.png differ
diff --git a/public/images/drawable/Check.png b/public/images/drawable/Check.png
new file mode 100644
index 0000000..aa315da
Binary files /dev/null and b/public/images/drawable/Check.png differ
diff --git a/public/images/drawable/Discard.png b/public/images/drawable/Discard.png
new file mode 100644
index 0000000..20a3f81
Binary files /dev/null and b/public/images/drawable/Discard.png differ
diff --git a/public/images/drawable/Menu.png b/public/images/drawable/Menu.png
new file mode 100644
index 0000000..00e80b1
Binary files /dev/null and b/public/images/drawable/Menu.png differ
diff --git a/public/images/drawable/Refund.png b/public/images/drawable/Refund.png
new file mode 100644
index 0000000..b29dede
Binary files /dev/null and b/public/images/drawable/Refund.png differ
diff --git a/public/images/drawable/Scaner.png b/public/images/drawable/Scaner.png
new file mode 100644
index 0000000..cb3dbc0
Binary files /dev/null and b/public/images/drawable/Scaner.png differ
diff --git a/public/images/drawable/Settings.png b/public/images/drawable/Settings.png
new file mode 100644
index 0000000..b17768b
Binary files /dev/null and b/public/images/drawable/Settings.png differ
diff --git a/public/images/drawable/cashier.png b/public/images/drawable/cashier.png
new file mode 100644
index 0000000..26c173f
Binary files /dev/null and b/public/images/drawable/cashier.png differ
diff --git a/public/images/drawable/payment.png b/public/images/drawable/payment.png
new file mode 100644
index 0000000..99ddf17
Binary files /dev/null and b/public/images/drawable/payment.png differ
diff --git a/public/images/drawable/pinpad.png b/public/images/drawable/pinpad.png
new file mode 100644
index 0000000..8fe9c84
Binary files /dev/null and b/public/images/drawable/pinpad.png differ
diff --git a/public/images/payment.png b/public/images/payment.png
new file mode 100644
index 0000000..99ddf17
Binary files /dev/null and b/public/images/payment.png differ
diff --git a/public/images/pinpad.png b/public/images/pinpad.png
new file mode 100644
index 0000000..8fe9c84
Binary files /dev/null and b/public/images/pinpad.png differ
diff --git a/public/js/script.js b/public/js/script.js
index ae36f9a..efb90e2 100644
--- a/public/js/script.js
+++ b/public/js/script.js
@@ -5,10 +5,12 @@ const imageStore = {
// Static image resources
staticImages: {
- home: 'am_card_pay_black.png',
- scan: 'am_barcode_black.png',
- printer: 'am_receipt_cart_black.png',
- settings: 'am_settings_black.png',
+ home: 'payment.png',
+ scan: 'Scaner.png',
+ printer: 'Check.png',
+ settings: 'Settings.png',
+ pinpad: 'pinpad.png',
+ cashier: 'cashier.png',
mulberry: 'am_mulberry_logo_wide_color.png',
overtec: 'am_overtec_logo_wide_color.png',
datexpay: 'am_datexpay_logo_wide_color.png',
@@ -70,7 +72,7 @@ document.addEventListener('DOMContentLoaded', function () {
select.dispatchEvent(new Event('change'));
});
-
+
// Handle image uploads (updated for SVG support)
@@ -130,12 +132,12 @@ document.addEventListener('DOMContentLoaded', function () {
} catch (error) {
console.error('[ERROR] Upload failed:', error);
- alert('Error uploading image. Please try again.');
+
}
});
});
- // Handle image uploads
+ // Handle image uploads
document.querySelectorAll('.icon-upload').forEach(input => {
input.addEventListener('change', async function () {
if (!this.files[0]) {
@@ -166,35 +168,35 @@ document.addEventListener('DOMContentLoaded', function () {
const data = await response.json();
// Generate a unique key
- const index = this.closest('.nav-item-config').dataset.index;
- const imageKey = `nav-icon-${index}-${Date.now()}`;
-
- // Store the image
- imageStore.storeBase64Nav(imageKey, data.base64);
-
+ const index = this.closest('.nav-item-config').dataset.index;
+ const imageKey = `nav-icon-${index}-${Date.now()}`;
+
+ // Store the image
+ imageStore.storeBase64Nav(imageKey, data.base64);
+
// Update preview dynamically
-
+
const previewImageElement = document.getElementById(`nav-icon${index}`);
if (previewImageElement) {
previewImageElement.src = data.base64; // Set the src to Base64 data
}
-// Update the hidden input
- const hiddenInput = document.querySelector(`input[name="navIconValue${index}"]`);
- if (hiddenInput) {
- hiddenInput.value = imageKey;
- console.log('Stored nav icon:', {
- index: index,
- key: imageKey,
- preview: data.base64.substring(0, 20) + '...'
- });
- }
+ // Update the hidden input
+ const hiddenInput = document.querySelector(`input[name="navIconValue${index}"]`);
+ if (hiddenInput) {
+ hiddenInput.value = imageKey;
+ console.log('Stored nav icon:', {
+ index: index,
+ key: imageKey,
+ preview: data.base64.substring(0, 20) + '...'
+ });
+ }
- // Update preview
- const previewImg = document.querySelector(`.nav-item:nth-child(${parseInt(index)+1}) .nav-icon`);
- if (previewImg) {
- previewImg.src = data.base64;
- }
+ // Update preview
+ const previewImg = document.querySelector(`.nav-item:nth-child(${parseInt(index) + 1}) .nav-icon`);
+ if (previewImg) {
+ previewImg.src = data.base64;
+ }
// Trigger any additional updates required
updatePreview();
@@ -227,7 +229,7 @@ document.addEventListener('DOMContentLoaded', function () {
// .then(response => response.json())
// .then(data => console.log('Success:', data))
// .catch(error => console.error('Error:', error));
- fetch('/publish', {
+ fetch('/sendToDevice', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -237,7 +239,7 @@ document.addEventListener('DOMContentLoaded', function () {
})
.then(() => {
console.log('[DEBUG] Configuration published successfully');
- alert('Configuration published successfully!');
+
})
.catch(err => {
console.error('[ERROR] Publish failed:', err);
@@ -305,36 +307,36 @@ document.addEventListener('DOMContentLoaded', function () {
}
function getNavItemConfig(formData, prefix) {
- const index = prefix.match(/\[(\d+)\]/)[1]; // Extract the index (0, 1, 2, etc.)
- const iconType = formData.get(`${prefix}[icon][type]`);
- let iconValue = formData.get(`${prefix}[icon][value]`);
+ const index = prefix.match(/\[(\d+)\]/)[1]; // Extract the index (0, 1, 2, etc.)
+ const iconType = formData.get(`${prefix}[icon][type]`);
+ let iconValue = formData.get(`${prefix}[icon][value]`);
// For base64 icons, get the value from the hidden input
- if (iconType === 'base64') {
- const storageKey = formData.get(`navIconValue${index}`);
- if (storageKey) {
- // iconValue = storageKey; // Use the storage key as the value
- iconValue = imageStore.getBase64(storageKey)
+ if (iconType === 'base64') {
+ const storageKey = formData.get(`navIconValue${index}`);
+ if (storageKey) {
+ // iconValue = storageKey; // Use the storage key as the value
+ iconValue = imageStore.getBase64(storageKey)
+ }
}
- }
console.log("Nav item config:", {
- prefix: prefix,
- index: index,
- iconType: iconType,
- iconValue: iconValue
- });
+ prefix: prefix,
+ index: index,
+ iconType: iconType,
+ iconValue: iconValue
+ });
- return {
- navPage: formData.get(`${prefix}[navPage]`),
- navName: formData.get(`${prefix}[navName]`),
- icon: {
- type: iconType,
- value: iconValue
- },
- position: parseInt(formData.get(`${prefix}[position]`)) || 0,
- enabled: formData.get(`${prefix}[enabled]`) === 'on'
- };
+ return {
+ navPage: formData.get(`${prefix}[navPage]`),
+ navName: formData.get(`${prefix}[navName]`),
+ icon: {
+ type: iconType,
+ value: iconValue
+ },
+ position: parseInt(formData.get(`${prefix}[position]`)) || 0,
+ enabled: formData.get(`${prefix}[enabled]`) === 'on'
+ };
}
function logStoredBase64Images() {
@@ -396,27 +398,27 @@ document.addEventListener('DOMContentLoaded', function () {
console.log(`[DEBUG] Set resource icon: ${iconValue}`);
}
else if (iconType === 'base64') {
- // Get the index from the prefix
- const index = prefix.match(/\[(\d+)\]/)[1];
- const storageKey = formData.get(`navIconValue${index}`);
-
- console.log('Retrieving nav icon:', {
- index: index,
- key: storageKey,
- data: imageStore.getBase64(storageKey)
- });
-
- const base64Data = imageStore.getBase64(storageKey);
- console.log("",);
-
- if (base64Data) {
- iconElement.src = base64Data;
- iconElement.style.display = 'block';
- } else {
- console.error('Missing nav icon for:', storageKey);
- iconElement.style.display = 'none';
- }
-}
+ // Get the index from the prefix
+ const index = prefix.match(/\[(\d+)\]/)[1];
+ const storageKey = formData.get(`navIconValue${index}`);
+
+ console.log('Retrieving nav icon:', {
+ index: index,
+ key: storageKey,
+ data: imageStore.getBase64(storageKey)
+ });
+
+ const base64Data = imageStore.getBase64(storageKey);
+ console.log("",);
+
+ if (base64Data) {
+ iconElement.src = base64Data;
+ iconElement.style.display = 'block';
+ } else {
+ console.error('Missing nav icon for:', storageKey);
+ iconElement.style.display = 'none';
+ }
+ }
else {
iconElement.style.display = 'none';
console.log('[DEBUG] No valid icon to display');
diff --git a/translations.js b/translations.js
new file mode 100644
index 0000000..c5a3849
--- /dev/null
+++ b/translations.js
@@ -0,0 +1,41 @@
+// translations.js
+const translations = {
+ en: {
+ receiptNumber: "Receipt №",
+ paymentType: "Payment Type:",
+ testPayment: "Test Payment:",
+ paymentAddress: "PAYMENT ADDRESS:",
+ issueDate: "ISSUE DATE:",
+ total: "TOTAL:",
+ thankYou: "Thank you for using Mulberry",
+ transactionId: "Transaction ID:",
+ status: "Status:",
+ receiptRejected: "Receipt Rejected",
+ paymentDate: "PAYMENT DATE:",
+ amountToPay: "Amount to Pay:",
+ cancel: "Cancel",
+ pay: "Pay",
+ paymentAddressExample:"Uzbekistan, Tashkent",
+ // Add more translations as needed
+ },
+ ru: {
+ receiptNumber: "Квитанция №",
+ paymentType: "Тип оплаты:",
+ testPayment: "Тестовый платеж:",
+ paymentAddress: "АДРЕС ПЛАТЕЖА:",
+ issueDate: "ДАТА ВЫПУСКА:",
+ total: "ИТОГО:",
+ thankYou: "Спасибо за использование Mulberry",
+ transactionId: "ID транзакции:",
+ status: "Статус:",
+ receiptRejected: "Квитанция отклонена",
+ paymentDate: "ДАТА ПЛАТЕЖА:",
+ amountToPay: "Сумма к оплате:",
+ cancel: "Отмена",
+ pay: "Оплатить",
+ paymentAddressExample:"Волгоград, пр. им. В. И. Ленина",
+ // Add more translations as needed
+ }
+};
+
+module.exports = translations;
\ No newline at end of file
diff --git a/views/index.ejs b/views/index.ejs
index 31e70e8..307a922 100644
--- a/views/index.ejs
+++ b/views/index.ejs
@@ -4,11 +4,11 @@
POS Configuration
-
+
-
+
@@ -21,7 +21,7 @@
-
+
@@ -56,15 +56,15 @@
-
+
@@ -81,7 +81,7 @@
<% }); %>
-
+
@@ -104,6 +104,14 @@
>Printer
+
+
+
+
+
+
@@ -113,7 +121,8 @@
-
@@ -150,13 +158,13 @@
-
+
-
+
@@ -171,12 +179,13 @@
-
-
-
+
+
+
@@ -194,16 +203,16 @@
-
+
+
+
+
+
+
+
+
diff --git a/views/receipt copy.ejs b/views/receipt copy.ejs
deleted file mode 100644
index c255842..0000000
--- a/views/receipt copy.ejs
+++ /dev/null
@@ -1,182 +0,0 @@
-
-
-
- <%= title %>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Тип оплаты:
- <%= paymentMethod %>
-
-
- Тестовая оплата:
- <%= amount %> UZS
-
-
- АДРЕС РАСЧЁТОВ:
- Узбекистан, г. Ташкент
-
-
- ДАТА ВЫДАЧИ:
- <%= timestamp %>
-
-
-
-
-
-
-
- ИТОГ:
- <%= amount %> UZS
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Статус:
- Чек отклонен
-
-
- ДАТА ПЛАТЕЖА:
- <%= timestamp %>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/views/receipt.ejs b/views/receipt.ejs
index 507544d..9ad27fb 100644
--- a/views/receipt.ejs
+++ b/views/receipt.ejs
@@ -10,29 +10,30 @@
-
+
- Payment Type:
+ <%= t.paymentType %>
<%= paymentMethod %>
- Test Payment:
- <%= amount %> $
+ <%= t.testPayment %>
+ <%= amount %> <%= currencyS %>
+
- PAYMENT ADDRESS:
- Uzbekistan, Tashkent
-
-
- ISSUE DATE:
+ <%= t.issueDate %>
<%= timestamp %>
@@ -41,37 +42,37 @@
- TOTAL:
- <%= amount %> $
+ <%= t.total %>
+ <%= amount %> <%= currencyS %>
- Status:
- Receipt Rejected
+ <%= t.status %>
+ <%= t.receiptRejected %>
- PAYMENT DATE:
+ <%= t.paymentDate %>
<%= timestamp %>
@@ -79,29 +80,29 @@
@@ -128,6 +129,11 @@
const rejectedReceipt = document.getElementById('rejectedReceipt');
const buttons = document.getElementById('buttons');
const id = '<%= deviceId %>'; // Pass the id from EJS
+ console.log(id);
+
+ console.log("Data in receipt.ejs :");
+
+
document.getElementById('payButton').addEventListener('click', async () => {
const response = await fetch(`/pay-command?id=${id}`, { method: 'POST' });
diff --git a/websocket.js b/websocket.js
new file mode 100644
index 0000000..3e7dd5c
--- /dev/null
+++ b/websocket.js
@@ -0,0 +1,126 @@
+const WebSocket = require('ws');
+
+class WebSocketManager {
+ constructor() {
+ this.connectedDevices = new Map(); // deviceId -> WebSocket
+ this.wss = null;
+ }
+
+ initialize(server) {
+ this.wss = new WebSocket.Server({ server });
+
+ this.wss.on('connection', (ws) => {
+ console.log('New client connected');
+
+ ws.on('message', (message) => {
+ try {
+ const data = JSON.parse(message);
+
+ // Handle device identification
+ if (data.type === 'identification') {
+ this.connectedDevices.set(data.deviceId, ws);
+ console.log(`Device ${data.deviceId} (${data.model}) connected`);
+
+ // Send configuration immediately after identification
+ if (data.requestConfig) {
+ this.sendConfiguration(data.deviceId);
+ }
+ return;
+ }
+
+ if (data.type === 'redirect') {
+ console.log(`message ${data.amount}
+ ${data.currencySymbol}
+ ${data.id} `);
+
+ const ws = this.connectedDevices.get(data.id);
+ if (ws && ws.readyState === WebSocket.OPEN) {
+
+ message = JSON.stringify(data);
+
+ ws.send(message);
+ return true;
+ }
+
+ return;
+ }
+
+ if (data.type === 'message') {
+ console.log(`message ${data.content} `);
+ return;
+ }
+
+ // Handle other message types...
+
+ } catch (e) {
+ console.error('Error parsing message:', e);
+ }
+ });
+
+ ws.on('close', () => {
+ // Clean up disconnected devices
+ for (let [id, connection] of this.connectedDevices.entries()) {
+ if (connection === ws) {
+ this.connectedDevices.delete(id);
+ console.log(`Device ${id} disconnected`);
+ break;
+ }
+ }
+ });
+
+ ws.on('error', (error) => {
+ console.error('WebSocket error:', error);
+ });
+ });
+ }
+
+ sendToDevice(deviceId, message) {
+ const ws = this.connectedDevices.get(deviceId);
+ if (ws && ws.readyState === WebSocket.OPEN) {
+ if (typeof message !== 'string') {
+ message = JSON.stringify(message);
+ }
+ ws.send(message);
+ return true;
+ }
+ console.log(`Device ${deviceId} not connected`);
+ return false;
+ }
+
+ sendConfiguration(deviceId) {
+ // In a real app, you would fetch device-specific config from database
+ const config = {
+ type: "configuration",
+ deviceId: deviceId,
+ navItems: [{
+ navPage: "NAV_HOME",
+ navName: "PaymentZ",
+ icon: { type: "resource", value: "home" },
+ position: 0,
+ enabled: true
+ },
+ {
+ navPage: "NAV_SCAN",
+ navName: "ScannerZ",
+ icon: { type: "resource", value: "scan" },
+ position: 1,
+ enabled: true
+ },
+ ],
+ timestamp: new Date().toISOString()
+ };
+ this.sendToDevice(deviceId, config);
+ }
+
+ broadcast(message) {
+ this.connectedDevices.forEach((ws, deviceId) => {
+ this.sendToDevice(deviceId, message);
+ });
+ }
+
+ getConnectedDevices() {
+ return Array.from(this.connectedDevices.keys());
+ }
+}
+
+module.exports = new WebSocketManager();
\ No newline at end of file