feat: implement cash drawer security gate to conditionally trigger drawer pulses based on payment type
This commit is contained in:
parent
5524546dab
commit
2397c6dcb3
@ -433,6 +433,29 @@ export class EscPosGenerator {
|
|||||||
return new Uint8Array(cmds);
|
return new Uint8Array(cmds);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strip ESC/POS cash drawer pulse commands (ESC p / 0x1B 0x70) from Uint8Array data.
|
||||||
|
*
|
||||||
|
* @param {Uint8Array} data
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
static stripCashDrawerCommands(data) {
|
||||||
|
if (!data || data.length === 0) return new Uint8Array(0);
|
||||||
|
const cleanData = [];
|
||||||
|
let i = 0;
|
||||||
|
while (i < data.length) {
|
||||||
|
// Check for ESC p (0x1B 0x70)
|
||||||
|
if (data[i] === 0x1B && data[i + 1] === 0x70) {
|
||||||
|
// ESC p m t1 t2 has 5 bytes
|
||||||
|
i += 5;
|
||||||
|
} else {
|
||||||
|
cleanData.push(data[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Uint8Array(cleanData);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper: combine multiple Uint8Arrays into one.
|
* Helper: combine multiple Uint8Arrays into one.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -107,6 +107,25 @@ patch(PosPrinterService.prototype, {
|
|||||||
console.log('[BluetoothPrint] Element tag:', el?.tagName);
|
console.log('[BluetoothPrint] Element tag:', el?.tagName);
|
||||||
console.log('[BluetoothPrint] Element classes:', el?.className);
|
console.log('[BluetoothPrint] Element classes:', el?.className);
|
||||||
|
|
||||||
|
// Determine print context
|
||||||
|
const pos = this.env?.services?.pos;
|
||||||
|
const currentPrintContext = pos?.hardwareProxy?.currentPrintContext;
|
||||||
|
const order = this._currentPrintOrder || currentPrintContext?.order || pos?.get_order();
|
||||||
|
|
||||||
|
let printType = currentPrintContext?.printType;
|
||||||
|
if (!printType) {
|
||||||
|
if (this._currentPrintBasic) {
|
||||||
|
printType = 'CHECKER';
|
||||||
|
} else if (el && el.querySelector && el.querySelector('.new-changes, .preset-name, .o-employee-name, .order-ref-prefix')) {
|
||||||
|
printType = 'CHECKER';
|
||||||
|
} else {
|
||||||
|
printType = 'PAYMENT';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._isCashPayment = printType === 'PAYMENT' && order && (typeof order.isPaidWithCash === 'function' ? order.isPaidWithCash() : false);
|
||||||
|
console.log(`[BluetoothPrint] printHtml context check - printType: ${printType}, order: ${order?.pos_reference}, resolved isCashPayment: ${this._isCashPayment}`);
|
||||||
|
|
||||||
// Check if a Bluetooth printer is configured (from localStorage)
|
// Check if a Bluetooth printer is configured (from localStorage)
|
||||||
const storage = new BluetoothPrinterStorage();
|
const storage = new BluetoothPrinterStorage();
|
||||||
|
|
||||||
@ -229,6 +248,7 @@ patch(PosPrinterService.prototype, {
|
|||||||
} finally {
|
} finally {
|
||||||
this._currentPrintOrder = null;
|
this._currentPrintOrder = null;
|
||||||
this._currentPrintBasic = false;
|
this._currentPrintBasic = false;
|
||||||
|
this._isCashPayment = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -380,6 +400,51 @@ patch(PosPrinterService.prototype, {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply security gate on ESC/POS data:
|
||||||
|
* - Strips cash drawer pulse commands if isCashPayment is false.
|
||||||
|
* - Appends cash drawer pulse commands if isCashPayment is true.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {Uint8Array} escposData - ESC/POS byte sequence
|
||||||
|
* @param {boolean} isCashPayment - True if context is cash payment print
|
||||||
|
* @returns {Uint8Array}
|
||||||
|
*/
|
||||||
|
_applyCashDrawerSecurityGate(escposData, isCashPayment) {
|
||||||
|
if (!escposData) return escposData;
|
||||||
|
|
||||||
|
// 1. Strip any existing cash drawer pulse commands (0x1B 0x70) to prevent unauthorized triggers
|
||||||
|
const initialLen = escposData.length;
|
||||||
|
const cleanData = EscPosGenerator.stripCashDrawerCommands(escposData);
|
||||||
|
const strippedCount = Math.floor((initialLen - cleanData.length) / 5);
|
||||||
|
if (strippedCount > 0) {
|
||||||
|
console.log(`[BluetoothPrint] Stripped ${strippedCount} ESC p command(s) from print stream.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let finalData = cleanData;
|
||||||
|
|
||||||
|
// 2. If it is a cash payment print context, inject the cash drawer pulse command
|
||||||
|
if (isCashPayment) {
|
||||||
|
console.log('[BluetoothPrint] Cash payment context: Appending cash drawer pulse commands to ESC/POS stream.');
|
||||||
|
// Standard ESC/POS cashbox open commands (pin 2 and pin 5)
|
||||||
|
const pulseCmds = new Uint8Array([
|
||||||
|
0x1B, 0x3D, 0x01, // ESC = 1 (Select printer)
|
||||||
|
0x1B, 0x70, 0x00, 0x19, 0x19, // ESC p 0 25 25 (pulse pin 2)
|
||||||
|
0x1B, 0x70, 0x01, 0x19, 0x19 // ESC p 1 25 25 (pulse pin 5)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Combine pulse commands with the clean receipt data
|
||||||
|
const combined = new Uint8Array(pulseCmds.length + finalData.length);
|
||||||
|
combined.set(pulseCmds, 0);
|
||||||
|
combined.set(finalData, pulseCmds.length);
|
||||||
|
finalData = combined;
|
||||||
|
} else {
|
||||||
|
console.log('[BluetoothPrint] Non-cash print context: Cash drawer pulse blocked.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return finalData;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print receipt via bluetooth thermal printer from HTML element
|
* Print receipt via bluetooth thermal printer from HTML element
|
||||||
* Uses graphics mode to print exact HTML layout
|
* Uses graphics mode to print exact HTML layout
|
||||||
@ -433,9 +498,12 @@ patch(PosPrinterService.prototype, {
|
|||||||
console.log('[BluetoothPrint] EscPosGraphics created successfully');
|
console.log('[BluetoothPrint] EscPosGraphics created successfully');
|
||||||
|
|
||||||
console.log('[BluetoothPrint] Generating bitmap commands...');
|
console.log('[BluetoothPrint] Generating bitmap commands...');
|
||||||
const escposData = graphicsGenerator.generateBitmapCommands(bitmap);
|
let escposData = graphicsGenerator.generateBitmapCommands(bitmap);
|
||||||
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of graphics data');
|
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of graphics data');
|
||||||
|
|
||||||
|
// Apply cash drawer security gate
|
||||||
|
escposData = this._applyCashDrawerSecurityGate(escposData, this._isCashPayment);
|
||||||
|
|
||||||
// Send data to printer
|
// Send data to printer
|
||||||
console.log('[BluetoothPrint] Sending graphics to printer...');
|
console.log('[BluetoothPrint] Sending graphics to printer...');
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
@ -498,9 +566,12 @@ patch(PosPrinterService.prototype, {
|
|||||||
|
|
||||||
// Generate ESC/POS commands
|
// Generate ESC/POS commands
|
||||||
console.log('[BluetoothPrint] Generating ESC/POS text commands...');
|
console.log('[BluetoothPrint] Generating ESC/POS text commands...');
|
||||||
const escposData = escposGenerator.generateReceipt(receiptData);
|
let escposData = escposGenerator.generateReceipt(receiptData);
|
||||||
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of text data');
|
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of text data');
|
||||||
|
|
||||||
|
// Apply cash drawer security gate
|
||||||
|
escposData = this._applyCashDrawerSecurityGate(escposData, this._isCashPayment);
|
||||||
|
|
||||||
// Send data to printer
|
// Send data to printer
|
||||||
console.log('[BluetoothPrint] Sending text to printer...');
|
console.log('[BluetoothPrint] Sending text to printer...');
|
||||||
await bluetoothManager.sendData(escposData, false);
|
await bluetoothManager.sendData(escposData, false);
|
||||||
@ -538,9 +609,12 @@ patch(PosPrinterService.prototype, {
|
|||||||
|
|
||||||
// Generate ESC/POS commands
|
// Generate ESC/POS commands
|
||||||
console.log('[BluetoothPrint] Generating ESC/POS commands...');
|
console.log('[BluetoothPrint] Generating ESC/POS commands...');
|
||||||
const escposData = escposGenerator.generateReceipt(receiptData);
|
let escposData = escposGenerator.generateReceipt(receiptData);
|
||||||
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of ESC/POS data');
|
console.log('[BluetoothPrint] Generated', escposData.length, 'bytes of ESC/POS data');
|
||||||
|
|
||||||
|
// Apply cash drawer security gate
|
||||||
|
escposData = this._applyCashDrawerSecurityGate(escposData, this._isCashPayment);
|
||||||
|
|
||||||
// Send data to printer
|
// Send data to printer
|
||||||
console.log('[BluetoothPrint] Sending to printer...');
|
console.log('[BluetoothPrint] Sending to printer...');
|
||||||
await bluetoothManager.sendData(escposData, false);
|
await bluetoothManager.sendData(escposData, false);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user