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