Add payment QR code to invoice PDFs

This commit is contained in:
2026-07-22 18:51:13 +02:00
parent 769185cd82
commit dd2a9926dc
3 changed files with 114 additions and 7 deletions

View File

@@ -5,6 +5,7 @@ import {FastifyInstance} from "fastify";
import { GetObjectCommand } from "@aws-sdk/client-s3";
import { s3 } from "./s3";
import { secrets } from "./secrets";
import bwipjs from "bwip-js"
const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => {
/*
@@ -29,6 +30,61 @@ const getRightAlignedX = (rightEdgeMm: number, text: string, size: number, font:
return getCoordinatesForPDFLib(rightEdgeMm, 0, page).x - font.widthOfTextAtSize(text, size)
}
const normalizePaymentQrText = (value: any, maxLength: number) => {
return String(value || "")
.replace(/\r?\n/g, " ")
.replace(/[^\x20-\x7EÄÖÜäöüß]/g, "")
.trim()
.slice(0, maxLength)
}
const normalizeIban = (value: any) => String(value || "").replace(/\s+/g, "").toUpperCase()
const getPaymentQrAmount = (value: any) => {
if (typeof value === "number" && Number.isFinite(value)) return value
const normalized = String(value || "")
.replace(/[^\d,.-]/g, "")
.replace(/\./g, "")
.replace(",", ".")
const amount = Number(normalized)
return Number.isFinite(amount) ? amount : 0
}
const getPaymentQrCodeBuffer = async (invoiceData: any) => {
if (invoiceData?.payment_type && invoiceData.payment_type !== "transfer") return null
const paymentQr = invoiceData?.paymentQr || {}
const iban = normalizeIban(paymentQr.iban)
const recipientName = normalizePaymentQrText(paymentQr.recipientName || paymentQr.name, 70)
const amount = getPaymentQrAmount(paymentQr.amount ?? invoiceData?.total?.totalSumToPay)
if (!iban || !recipientName || amount <= 0) return null
const epcPayload = [
"BCD",
"002",
"1",
"SCT",
normalizePaymentQrText(paymentQr.bic, 11),
recipientName,
iban,
`EUR${amount.toFixed(2)}`,
"",
normalizePaymentQrText(paymentQr.remittance || invoiceData?.documentNumber || invoiceData?.title, 140),
"",
].join("\n")
return bwipjs.toBuffer({
bcid: "qrcode",
text: epcPayload,
scale: 4,
includetext: false,
})
}
const getBackgroundSourceBuffer = async (server:FastifyInstance, path:string) => {
@@ -70,6 +126,8 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold)
const paymentQrCodeBuffer = await getPaymentQrCodeBuffer(invoiceData)
const paymentQrCode = paymentQrCodeBuffer ? await pdfDoc.embedPng(paymentQrCodeBuffer) : null
let pages = []
let pageCounter = 1
@@ -923,8 +981,31 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
maxWidth: paymentQrCode ? 360 : 500
})
if (paymentQrCode) {
const qrSize = 32 * 2.83
const qrPosition = getCoordinatesForPDFLib(166, rowHeight + endTextDiff + (invoiceData.totalArray.length - 3) * 8 - 1, page1)
pages[pageCounter - 1].drawText("Überweisung per QR-Code", {
y: qrPosition.y + qrSize + 7,
x: getCoordinatesForPDFLib(166, 0, page1).x,
size: 8,
color: rgb(0, 0, 0),
lineHeight: 8,
opacity: 1,
maxWidth: 120,
font: fontBold,
})
pages[pageCounter - 1].drawImage(paymentQrCode, {
x: qrPosition.x,
y: qrPosition.y,
width: qrSize,
height: qrSize,
})
}
}
return await pdfDoc.saveAsBase64()