Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Swiss Qr Bill Laravel Package

sprain/swiss-qr-bill

Generate Swiss QR bills (QR-Rechnungen) in PHP with sprain/swiss-qr-bill. Build compliant QR-bill PDFs including payment parts, QR codes, references, and ISO 20022 data—works standalone or in Laravel. Simple API, Swiss QR standard support.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require sprain/swiss-qr-bill

# Only needed for the printable payment part (the 210×105 mm slip):
composer require tecnickcom/tcpdf        # or setasign/fpdf

The library itself is plain PHP (no Laravel service provider), so there is nothing to register. It pulls in symfony/validator for validation and endroid/qr-code for image generation.

Where to look first

  • README.md — the examples cover ~90% of real usage.
  • src/QrBill.php — the aggregate you build up element by element.
  • src/DataGroup/Element/ — CombinedAddress, StructuredAddress, CreditorInformation, PaymentAmountInformation, PaymentReference, AdditionalInformation.
  • src/PaymentPart/Output/ — TcPdfOutput, FpdfOutput; these turn the object into the printable slip.
  • src/Reference/ — helpers/validators for QR (mod-10 recursive) and RF/SCOR (mod-97) references.

First use case: turn an invoice into a QR bill

use Sprain\SwissQrBill as QrBill;

$bill = QrBill\QrBill::create(); // header data group is created for you

$bill->setCreditor(
    QrBill\DataGroup\Element\CombinedAddress::create(
        'Acme AG', 'Bahnhofstrasse 1', '8001 Zürich'
    )
);
$bill->setCreditorInformation(
    QrBill\DataGroup\Element\CreditorInformation::create('CH4431999123000889012')
);
$bill->setDebtor(
    QrBill\DataGroup\Element\CombinedAddress::create(
        'Max Muster', 'Musterstrasse 12', '8000 Zürich'
    )
);
$bill->setPaymentAmountInformation(
    QrBill\DataGroup\Element\PaymentAmountInformation::create('CHF', 1250.00)
);
$bill->setPaymentReference(
    QrBill\DataGroup\Element\PaymentReference::create(
        QrBill\DataGroup\Element\PaymentReference::TYPE_QR,
        '210000000003139471430009017'
    )
);
$bill->setAdditionalInformation(
    QrBill\DataGroup\Element\AdditionalInformation::create('Rechnung 2024-0042')
);

if (! $bill->isValid()) {
    foreach ($bill->getViolations() as $v) {
        logger()->warning($v->getPropertyPath().': '.$v->getMessage());
    }
    throw new RuntimeException('QR bill is not valid');
}

// QR code as PNG (or embed via getQrCode()->getImagePath() which returns a data URI)
$bill->getQrCode()->writeFile(storage_path('app/qr-bill.png'));

// Payment part as PDF
$output = new QrBill\PaymentPart\Output\TcPdfOutput\TcPdfOutput($bill, 'de');
$output->getPaymentPart()->Output(storage_path('app/payment-part.pdf'), 'F');

Three things to internalise early

  1. The QrBill object is a builder, not a value object — you assemble it with set*() calls and only then validate.
  2. The QR image and the payment-part PDF are two independent outputs; you can use just the image in HTML/email, or the PDF for printing.
  3. Language (de/fr/it/en) is chosen per render, so one invoice can produce a German and a French slip from the same object.

Implementation Patterns

Config-driven creditor + a builder service. Never scatter IBANs and company names through controllers. Put creditor data in config/swissqr.php, and wrap assembly in a single class:

// config/swissqr.php
return [
    'creditor' => [
        'name'    => 'Acme AG',
        'address' => ['Bahnhofstrasse 1', '8001 Zürich'],
        'iban'    => env('SWISSQR_IBAN'),
    ],
    'default_language' => 'de',
];
final class QrBillFactory
{
    public function __construct(private array $config) {}

    public function forInvoice(Invoice $invoice): QrBill
    {
        $c = $this->config['creditor'];
        $bill = QrBill\QrBill::create();
        $bill->setCreditor(CombinedAddress::create($c['name'], ...$c['address']));
        // ... map invoice/customer fields
        return $bill;
    }
}

Address mapping. Use CombinedAddress::create($name, $line1, $line2) when your customer model stores free-form address lines; use StructuredAddress::create($name, $street, $buildingNumber, $postalCode, $city, $country) when you actually have parsed fields. The debtor may be omitted entirely (open-amount / bearer bills), and PaymentAmountInformation::create('CHF', null) handles donation-style open amounts.

Pick a reference strategy once and encode it in the domain.

Creditor account Reference type What the payer sees
QR-IBAN (IID 30000–31999) TYPE_QR with valid mod-10 reference 27-digit QR reference
Normal IBAN TYPE_SCOR with valid RF reference RF… reference
Normal IBAN TYPE_NON "No reference" + additional information

Generate the check digit when the invoice is created (not at render time), store the full reference on the invoice row, and pass it verbatim. This keeps re-renders deterministic and testable.

Validation as a gate, not an afterthought. Call isValid() in a form request, command, or job before touching PDFs, and convert getViolations() into a 422/exception. Invalid objects will still render, which is the most common way a broken bill reaches a customer.

Compose the invoice PDF with FPDI. The slip must be exactly 210×105 mm and sit flush at the bottom of an A4 page (leaving the top 192 mm for invoice content). A typical merge:

$pdf = new \setasign\Fpdi\Tcpdf\Fpdi();
$pdf->setPrintHeader(false);
$pdf->AddPage();

$pdf->setSourceFile($invoicePath);
$pdf->useTemplate($pdf->importPage(1), 0, 0, 210, 297);

$pdf->setSourceFile($paymentPartPath);
$pdf->useTemplate($pdf->importPage(1), 0, 192, 210, 105);

$pdf->Output($finalPath, 'F');

Queue it. TCPDF is memory-hungry and PDF generation is slow; put it in a GenerateInvoicePdf job, write to S3/local disk, and attach the file to a Mailable. Make the job idempotent on invoice ID so a retry doesn't duplicate the bill.

Locale routing. Map Laravel locales to the output languages: de* → de, fr* → fr, it* → it, everything else en. Store the chosen language on the invoice so re-downloads match the original.

Testing. Build a fixture Invoice, assert isValid(), and snapshot either the generated PDF hash or the decoded QR payload. Cover the three reference combinations, an open amount, a debtor-less bill, and a name with umlauts — those catch 90% of regressions.

Gotchas and Tips

  • isValid() is not automatic. Building an object with a missing debtor, invalid IBAN, or wrong reference type succeeds; only validation reports it. Always gate rendering on it and log getPropertyPath().
  • QR-IBAN ↔ QR reference is a hard coupling. A QR-IBAN with TYPE_SCOR/TYPE_NON fails validation, and a standard IBAN with TYPE_QR fails too. Don't assume any IBAN works with any reference.
  • Check digits are validated, not invented. QR references use mod-10 recursive, RF references mod-97. Calculate them when creating the invoice (look in src/Reference/ for helpers) and store the finished string. If you compute them at render time you'll eventually ship an invoice whose reference no longer matches your database.
  • Amounts are fiddly. Pass a numeric value with at most two decimals — not "1'250.00" and not a locale-formatted string. Max is 999999999.99; currencies are only CHF and EUR. Use integer cents in your domain and divide by 100 at the boundary to avoid float drift.
  • Character set and length limits. Names max 70 chars, additional information max 140, and only the QR-bill Latin character set is allowed. Emoji, curly quotes from a CMS, or multi-line addresses will either be rejected or silently mangled. Sanitise user input (StringModifier in src/String/ helps with trimming/line breaks) and never concatenate a full postal address into the name field.
  • Do not re-render the slip with Dompdf/HTML-to-PDF. The payment part has strict geometry (210×105 mm, 5 mm margins, 46×46 mm minimum QR). HTML renderers scale and reflow; use the TCPDF/FPDF outputs, or the data-URI image only for previews/emails.
  • Print at 100%. "Fit to page" in the PDF viewer shrinks the QR below spec and scanners reject it. Also don't crop the white border, and don't overlay logos on the slip.
  • Dependency friction. endroid/qr-code is a common source of Composer conflicts; ext-gd (or Imagick) is required for PNG output, and ext-mbstring/intl for TCPDF. Pin the library and re-run your PDF regression tests on upgrades — validator behaviour and output rendering do change between minor versions.
  • TCPDF memory. Rendering large invoices plus the slip can exceed 128 MB. Raise memory_limit on queue workers, and don't generate PDFs synchronously inside a web request if you can avoid it.
  • Output API is thin. The output classes hand you the underlying TCPDF/FPDF instance (getPaymentPart()); anything the library doesn't expose (watermarks, extra pages, headers) you do on that object. For other PDF engines, implement OutputInterface — that's the intended extension point.
  • Debugging a rejected bill. Dump the violations, then compare the QR payload against the official Swiss QR-bill validator/test data. When in doubt, decode the generated PNG with any QR reader — it's the exact string a banking app will parse.
  • Small-maintainer risk. 330 stars, MIT, effectively one maintainer. That's fine for a well-specified format, but pin the version, keep a golden-file test of a known-good bill, and be ready to fork the payment-part renderer if a spec change lands before an upstream release.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mehedimi/laravel-amp-mail
happones/laravel-vue-i18n-generator
jwerd/inertia-table
siberfx/flysystem-wasabi
hollyit/token-replace
ippanelcom/laravel-sdk
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel