autoft-qris
Version:
Package untuk generate QRIS dengan 2 tema (Biru & Hijau) dan cek payment status secara realtime dengan API OrderKuota
129 lines (110 loc) • 5.18 kB
JavaScript
import PDFDocument from 'pdfkit';
import { existsSync, mkdirSync, createWriteStream } from 'fs';
import moment from 'moment';
class ReceiptGenerator {
constructor(config) {
this.config = config;
}
async generateReceipt(transactionData) {
return new Promise((resolve, reject) => {
try {
const doc = new PDFDocument({
size: [300, 450],
margin: 20,
layout: 'portrait'
});
const fileName = `receipt_${transactionData.reference}_${Date.now()}.pdf`;
const filePath = `receipts/${fileName}`;
if (!existsSync('receipts')) {
mkdirSync('receipts');
}
const writeStream = createWriteStream(filePath);
doc.pipe(writeStream);
if (this.config.logoPath && existsSync(this.config.logoPath)) {
const logoWidth = 40;
const logoHeight = 40;
const logoX = 20 + (260 - logoWidth) / 2;
const logoY = 10;
doc.image(this.config.logoPath, logoX, logoY, { width: logoWidth, height: logoHeight });
doc.y = logoY + logoHeight + 4;
} else {
doc.moveDown(0.5);
}
doc.fontSize(14)
.fillColor('black')
.font('Helvetica-Bold');
const headerText = 'QRIS PAYMENT RECEIPT';
const headerWidth = doc.widthOfString(headerText);
doc.text(headerText, 20 + (260 - headerWidth) / 2, doc.y, { width: headerWidth });
doc.fontSize(9)
.font('Helvetica');
const merchantText = this.config.storeName || 'STORE NAME';
const merchantWidth = doc.widthOfString(merchantText);
doc.text(merchantText, 20 + (260 - merchantWidth) / 2, doc.y, { width: merchantWidth });
doc.moveDown(0.3);
const lineY = doc.y;
doc.moveTo(20, lineY)
.lineTo(280, lineY)
.strokeColor('#888').stroke();
doc.moveDown(0.5);
doc.fontSize(9)
.font('Helvetica-Bold');
const detailsTitle = 'TRANSACTION DETAILS';
const detailsTitleWidth = doc.widthOfString(detailsTitle);
doc.text(detailsTitle, 20 + (260 - detailsTitleWidth) / 2, doc.y, { width: detailsTitleWidth });
doc.moveDown(0.2);
const formattedDate = moment(transactionData.date).format('DD/MM/YYYY HH:mm:ss');
const details = [
['Reference', transactionData.reference],
['Date', formattedDate],
['Amount', `Rp ${transactionData.amount.toLocaleString('id-ID')}`],
['Status', transactionData.status],
['Payment Method', transactionData.brand_name || '-'],
['Buyer Reference', transactionData.buyer_reff || '-']
];
const tableWidth = 240;
const startX = (300 - tableWidth) / 2;
const labelWidth = 100;
const valueWidth = 130;
let y = doc.y + 5;
details.forEach(([label, value], idx) => {
doc.font('Helvetica-Bold')
.fontSize(9)
.fillColor('black')
.text(label, startX, y, { width: labelWidth, align: 'left' });
doc.font('Helvetica')
.fontSize(9)
.fillColor('black')
.text(value, startX + labelWidth, y, { width: valueWidth, align: 'right' });
y += 14;
doc.moveTo(startX, y)
.lineTo(startX + labelWidth + valueWidth, y)
.strokeColor('#e0e0e0').stroke();
y += 2;
});
y += 6;
doc.moveTo(20, y)
.lineTo(280, y)
.strokeColor('#888').stroke();
y += 12;
doc.text('Thank you for your payment!', 0, y, { align: 'right', width: 260 });
y += 12;
doc.text(`Receipt No: ${transactionData.reference}`, 0, y, { align: 'right', width: 260 });
doc.end();
writeStream.on('finish', () => {
resolve({
success: true,
filePath: filePath,
fileName: fileName
});
});
writeStream.on('error', (error) => {
reject(error);
});
} catch (error) {
reject(error);
}
});
}
}
export default ReceiptGenerator;