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 - - - - - -
-
-

DatexPay Virtual Bank

-

Transaction 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 - + -
@@ -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 %> - - - - - - - -
-
- -

чек № 2949

-

https://www.mulberrypos.com

- -
- -
- -
-
- Тип оплаты: - <%= paymentMethod %> -
-
- Тестовая оплата: - <%= amount %> UZS -
-
- АДРЕС РАСЧЁТОВ: - Узбекистан, г. Ташкент -
-
- ДАТА ВЫДАЧИ: - <%= timestamp %> -
-
- -
- -
-
- ИТОГ: - <%= amount %> UZS -
-
- -
- - -
- - -
-
- -

чек № 2949

-

https://www.mulberrypos.com

- -
- -
- -
-
- Статус: - Чек отклонен -
-
- ДАТА ПЛАТЕЖА: - <%= timestamp %> -
-
- -
- - -
- - -
-
- -

чек № 2949

-

https://www.mulberrypos.com

- -
- -
- -
- -
- Сумма к оплате: - <%= 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 @@
- -

Receipt № 2949

+ + +

<%= t.receiptNumber %> 2949

- +
- +
- 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 %>
- -

Receipt № 2949

+ +

<%= t.receiptNumber %> 2949

- +
- Status: - Receipt Rejected + <%= t.status %> + <%= t.receiptRejected %>
- PAYMENT DATE: + <%= t.paymentDate %> <%= timestamp %>
@@ -79,29 +80,29 @@
- -

Receipt № 2949

+ +

<%= t.receiptNumber %> 2949

- +
- Amount to Pay: - <%= amount %> $ + <%= t.amountToPay %> + <%= amount %> <%= currencyS %>
- PAYMENT DATE: + <%= t.paymentDate %> <%= timestamp %>
@@ -109,14 +110,14 @@
- - + +
@@ -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