UNPKG

n8n-nodes-qrcode-generator

Version:

Generate QR Codes in N8N workflows - PIX payments, WiFi sharing, vCard contacts, URLs and more. Full EMV compliance for Brazilian PIX instant payments.

567 lines (566 loc) 22.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.QrCodeGenerator = void 0; const n8n_workflow_1 = require("n8n-workflow"); const QRCode = __importStar(require("qrcode")); class QrCodeGenerator { constructor() { this.description = { displayName: 'QR Code Generator', name: 'qrCodeGenerator', icon: 'file:qrcode.svg', group: ['transform'], version: 1, subtitle: '={{$parameter["contentType"] ? "QR Code: " + $parameter["contentType"] : "QR Code Generator"}}', description: 'Generate QR Codes for various content types including PIX, URLs, and structured data', defaults: { name: 'QR Code Generator', }, inputs: ["main"], outputs: ["main"], properties: [ { displayName: 'Operation', name: 'operation', type: 'options', noDataExpression: false, options: [ { name: 'Generate QR Code', value: 'generate', description: 'Generate a QR Code from input data', action: 'Generate a QR Code', }, ], default: 'generate', }, { displayName: 'Content Type', name: 'contentType', type: 'options', displayOptions: { show: { operation: ['generate'], }, }, options: [ { name: 'Text/URL', value: 'text', description: 'Plain text or URL', }, { name: 'PIX Payment', value: 'pix', description: 'Brazilian PIX payment data', }, { name: 'WiFi Network', value: 'wifi', description: 'WiFi network credentials', }, { name: 'Contact (vCard)', value: 'vcard', description: 'Contact information', }, ], default: 'text', description: 'Type of content to encode in the QR Code', }, { displayName: 'Content', name: 'content', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['text'], }, }, default: '', placeholder: 'Enter text or URL to encode', description: 'The text or URL to encode in the QR Code', }, { displayName: 'PIX Key', name: 'pixKey', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['pix'], }, }, default: '', placeholder: 'CPF, CNPJ, email, phone, or random key', description: 'PIX key for the payment', }, { displayName: 'Merchant Name', name: 'merchantName', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['pix'], }, }, default: '', placeholder: 'Merchant or person name', description: 'Name of the merchant or person receiving the payment', }, { displayName: 'Amount', name: 'amount', type: 'number', displayOptions: { show: { operation: ['generate'], contentType: ['pix'], }, }, default: 0, description: 'Payment amount (leave 0 for variable amount)', }, { displayName: 'City', name: 'city', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['pix'], }, }, default: '', placeholder: 'City name', description: 'City where the transaction takes place', }, { displayName: 'Transaction ID', name: 'transactionId', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['pix'], }, }, default: '', placeholder: 'Optional transaction identifier', description: 'Optional transaction identifier', }, { displayName: 'Network Name (SSID)', name: 'wifiSSID', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['wifi'], }, }, default: '', description: 'WiFi network name', }, { displayName: 'Password', name: 'wifiPassword', type: 'string', typeOptions: { password: true, }, displayOptions: { show: { operation: ['generate'], contentType: ['wifi'], }, }, default: '', description: 'WiFi network password', }, { displayName: 'Security Type', name: 'wifiSecurity', type: 'options', displayOptions: { show: { operation: ['generate'], contentType: ['wifi'], }, }, options: [ { name: 'WPA/WPA2', value: 'WPA', }, { name: 'WEP', value: 'WEP', }, { name: 'None', value: 'nopass', }, ], default: 'WPA', description: 'WiFi security type', }, { displayName: 'Full Name', name: 'vcardName', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['vcard'], }, }, default: '', description: 'Contact full name', }, { displayName: 'Phone', name: 'vcardPhone', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['vcard'], }, }, default: '', description: 'Contact phone number', }, { displayName: 'Email', name: 'vcardEmail', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['vcard'], }, }, default: '', description: 'Contact email address', }, { displayName: 'Organization', name: 'vcardOrg', type: 'string', displayOptions: { show: { operation: ['generate'], contentType: ['vcard'], }, }, default: '', description: 'Contact organization', }, { displayName: 'Options', name: 'options', type: 'collection', placeholder: 'Add Option', default: {}, options: [ { displayName: 'Output Format', name: 'outputFormat', type: 'options', options: [ { name: 'Base64 Data URL', value: 'dataURL', description: 'Base64 encoded data URL (default)', }, { name: 'Base64 String', value: 'base64', description: 'Base64 encoded string without data URL prefix', }, { name: 'Buffer', value: 'buffer', description: 'Binary buffer data', }, ], default: 'dataURL', description: 'Output format for the QR Code', }, { displayName: 'Size (Width)', name: 'width', type: 'number', default: 300, description: 'Width of the QR Code in pixels', }, { displayName: 'Error Correction Level', name: 'errorCorrectionLevel', type: 'options', options: [ { name: 'Low (~7%)', value: 'L', }, { name: 'Medium (~15%)', value: 'M', }, { name: 'Quartile (~25%)', value: 'Q', }, { name: 'High (~30%)', value: 'H', }, ], default: 'M', description: 'Error correction level', }, { displayName: 'Margin', name: 'margin', type: 'number', default: 4, description: 'Margin around the QR Code in modules', }, { displayName: 'Dark Color', name: 'darkColor', type: 'string', default: '#000000', description: 'Color of dark modules (hex color)', }, { displayName: 'Light Color', name: 'lightColor', type: 'string', default: '#FFFFFF', description: 'Color of light modules (hex color)', }, ], }, ], }; } async execute() { const items = this.getInputData(); const returnData = []; const nodeInstance = new QrCodeGenerator(); for (let i = 0; i < items.length; i++) { try { const operation = this.getNodeParameter('operation', i); const contentType = this.getNodeParameter('contentType', i); const options = this.getNodeParameter('options', i, {}); let content = ''; switch (contentType) { case 'text': content = this.getNodeParameter('content', i); break; case 'pix': content = nodeInstance.buildPixPayload.call(this, i); break; case 'wifi': content = nodeInstance.buildWiFiPayload.call(this, i); break; case 'vcard': content = nodeInstance.buildVCardPayload.call(this, i); break; default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown content type: ${contentType}`, { itemIndex: i, }); } if (!content) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Content cannot be empty', { itemIndex: i, }); } const qrCodeData = await nodeInstance.generateQRCode.call(this, content, options); const result = { json: { qrCode: options.outputFormat === 'buffer' ? '[Binary Data]' : qrCodeData, content, contentType, options, }, }; if (options.outputFormat === 'buffer' && Buffer.isBuffer(qrCodeData)) { result.binary = { data: { data: qrCodeData.toString('base64'), mimeType: 'image/png', fileName: 'qrcode.png', }, }; } returnData.push(result); } catch (error) { if (this.continueOnFail()) { returnData.push({ json: { error: error instanceof Error ? error.message : 'Unknown error', }, }); continue; } throw error; } } return [returnData]; } buildPixPayload(itemIndex) { const pixKey = this.getNodeParameter('pixKey', itemIndex); const merchantName = this.getNodeParameter('merchantName', itemIndex); const amount = this.getNodeParameter('amount', itemIndex); const city = this.getNodeParameter('city', itemIndex); const transactionId = this.getNodeParameter('transactionId', itemIndex, ''); if (!pixKey || !merchantName || !city) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'PIX Key, Merchant Name, and City are required for PIX payments', { itemIndex, }); } const nodeInstance = new QrCodeGenerator(); return nodeInstance.generatePixEMVPayload(pixKey, merchantName, city, amount, transactionId); } generatePixEMVPayload(pixKey, merchantName, city, amount, transactionId) { const formatEMVField = (id, value) => { const length = value.length.toString().padStart(2, '0'); return `${id}${length}${value}`; }; let payload = formatEMVField('00', '01'); payload += formatEMVField('01', amount > 0 ? '12' : '11'); const pixData = formatEMVField('00', 'BR.GOV.BCB.PIX') + formatEMVField('01', pixKey); payload += formatEMVField('26', pixData); payload += formatEMVField('52', '0000'); payload += formatEMVField('53', '986'); if (amount > 0) { payload += formatEMVField('54', amount.toFixed(2)); } payload += formatEMVField('58', 'BR'); payload += formatEMVField('59', merchantName.substring(0, 25)); payload += formatEMVField('60', city.substring(0, 15)); if (transactionId) { const additionalData = formatEMVField('05', transactionId.substring(0, 25)); payload += formatEMVField('62', additionalData); } const crc = this.calculateCRC16(payload + '6304'); payload += '63' + '04' + crc; return payload; } calculateCRC16(data) { let crc = 0xFFFF; for (let i = 0; i < data.length; i++) { crc ^= data.charCodeAt(i) << 8; for (let j = 0; j < 8; j++) { if (crc & 0x8000) { crc = (crc << 1) ^ 0x1021; } else { crc <<= 1; } crc &= 0xFFFF; } } return crc.toString(16).toUpperCase().padStart(4, '0'); } buildWiFiPayload(itemIndex) { const ssid = this.getNodeParameter('wifiSSID', itemIndex); const password = this.getNodeParameter('wifiPassword', itemIndex); const security = this.getNodeParameter('wifiSecurity', itemIndex); if (!ssid) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'WiFi SSID is required', { itemIndex, }); } const hidden = 'false'; return `WIFI:T:${security};S:${ssid};P:${password};H:${hidden};;`; } buildVCardPayload(itemIndex) { const name = this.getNodeParameter('vcardName', itemIndex); const phone = this.getNodeParameter('vcardPhone', itemIndex, ''); const email = this.getNodeParameter('vcardEmail', itemIndex, ''); const org = this.getNodeParameter('vcardOrg', itemIndex, ''); if (!name) { throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Contact name is required for vCard', { itemIndex, }); } let vcard = 'BEGIN:VCARD\nVERSION:3.0\n'; vcard += `FN:${name}\n`; if (phone) vcard += `TEL:${phone}\n`; if (email) vcard += `EMAIL:${email}\n`; if (org) vcard += `ORG:${org}\n`; vcard += 'END:VCARD'; return vcard; } async generateQRCode(content, options) { const qrOptions = { errorCorrectionLevel: options.errorCorrectionLevel || 'M', width: options.width || 300, margin: options.margin || 4, color: { dark: options.darkColor || '#000000', light: options.lightColor || '#FFFFFF', }, }; const outputFormat = options.outputFormat || 'dataURL'; switch (outputFormat) { case 'dataURL': return await QRCode.toDataURL(content, qrOptions); case 'base64': const dataURL = await QRCode.toDataURL(content, qrOptions); return dataURL.split(',')[1]; case 'buffer': return await QRCode.toBuffer(content, { ...qrOptions, type: 'png', }); default: throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown output format: ${outputFormat}`); } } } exports.QrCodeGenerator = QrCodeGenerator;