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

Qr Payment Bundle Laravel Package

rikudou/qr-payment-bundle

Symfony bundle that integrates the rikudou/qr-payment library to generate scannable QR payment codes for bank transfers. Easily create Czech QR Platba codes in your PHP application with straightforward service configuration and framework-ready integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Understand what you're installing. rikudou/qr-payment-bundle is a Symfony bundle. Its only real job is registering the services from the framework-agnostic rikudou/qr-payment library and exposing a handful of config keys to the Symfony container. In a Laravel app, none of that wiring happens automatically — bundles are not auto-discovered, and there is no config/bundles.php equivalent. Pulling in the bundle gets you a symfony/framework-bundle-shaped dependency for zero benefit.

Start with the core library instead.

composer require rikudou/qr-payment
# only if you're bridging into an actual Symfony app:
# composer require rikudou/qr-payment-bundle

The core library builds the payment payload string for Czech QR Platba (SPD), Slovak payment QR, and the EPC/SEPA standard (EPC069-12), and hands back a renderer object for the image.

Read these first, in order:

  1. vendor/rikudou/qr-payment/README.md — the authoritative source for payload formats and per-standard field rules.
  2. Rikudou\QrPayment\QrPaymentInterface — the full setter surface (setAmount, setCurrency, setDueDate, setVariableSymbol, setIban, setAccountNumber, setComment, setRecipientName, setOption, …).
  3. The static factories: QrPayment::createCzechQrPayment(), createSlovakQrPayment(), createEuropeanQrPayment().
  4. If you did install the bundle, src/DependencyInjection/Configuration.php is the only place config keys exist — read it before assuming a config option is real.

First use case — a "scan to pay" QR on an invoice PDF.

use Rikudou\QrPayment\QrPayment;

$payment = QrPayment::createCzechQrPayment();
$payment
    ->setAmount('1499.00')                      // string, never a float
    ->setCurrency('CZK')
    ->setAccountNumber('123456789/0100')       // or ->setIban('CZ65...')
    ->setVariableSymbol('2024001234')
    ->setDueDate(new \DateTimeImmutable($invoice->due_at))
    ->setComment("Faktura {$invoice->number}");

$payload = $payment->getQrString();  // the SPD* / EPC* string — log this
$qr      = $payment->getQrCode();    // renderer object

During development, dump $payload before touching the image at all. It is short, human-readable, and immediately shows you a malformed IBAN, a bad decimal separator, or a comment that blew past the length limit. Debugging the string is minutes; debugging a blurry PNG in a banking app is days.

Implementation Patterns

Wrap construction in one service. Never call the factories in controllers.

final class PaymentQrCodeGenerator
{
    public function forInvoice(Invoice $invoice): QrPaymentInterface
    {
        $payment = $this->factoryFor($invoice->payment_rail);

        return $payment
            ->setAmount(number_format($invoice->total, 2, '.', ''))
            ->setCurrency($invoice->currency)
            ->setIban(config('payments.iban'))
            ->setVariableSymbol($invoice->number)
            ->setDueDate(new \DateTimeImmutable($invoice->due_at))
            ->setComment("Faktura {$invoice->number}");
    }

    private function factoryFor(string $rail): QrPaymentInterface
    {
        return match ($rail) {
            'cz'    => QrPayment::createCzechQrPayment(),
            'sk'    => QrPayment::createSlovakQrPayment(),
            'sepa'  => QrPayment::createEuropeanQrPayment(),
            default => throw new \InvalidArgumentException("Unsupported rail {$rail}"),
        };
    }
}

Bind it in AppServiceProvider:

$this->app->singleton(PaymentQrCodeGenerator::class);

Now controllers, Blade partials, and queued PDF jobs all share the same defaults (IBAN, comment policy, error-correction level, currency handling). When you swap the library out — and given 3 stars and 0 dependents, plan for it — you change one file.

Choose the format from the payment rail, not from the user's locale. A Czech customer paying a EUR IBAN needs EPC, not SPD. Derive the standard from a persisted payment_rail / country field on the invoice or order, decided at invoice-creation time. app()->getLocale() is a trap here.

Persist the payload, regenerate the image. Store getQrString() in an invoice column (payment_qr_string). A payload regenerated with a newer library version should be byte-identical; an image should be cheap to re-render. This gives you an audit trail, lets you diff changes after an upgrade, and makes golden-file tests trivial.

$invoice->update(['payment_qr_string' => $generator->forInvoice($invoice)->getQrString()]);

Render once per request, reuse everywhere.

$qr = $generator->forInvoice($invoice)->getQrCode();
$path = "qr/{$invoice->id}.png";
Storage::disk('local')->put($path, $qr->getString());

return view('invoice.pdf', ['qrPath' => Storage::disk('local')->path($path)]);

Feed the same file to the Blade/HTML view and the PDF renderer. Don't encode the same payload twice in one request, and don't embed base64 images in PDFs when DomPDF/Snappy can read a temp path.

Expose a single Blade partial. One resources/views/payments/qr.blade.php that takes a pre-rendered image URL and an "or pay manually" block with the same IBAN, amount, and variable symbol. Every surface — invoice PDF, dunning email, checkout confirmation — includes that partial. It gives you one place to add the fallback text and one place to swap QR providers.

Golden-file tests per standard. This is the highest-value thing you can do for a low-adoption dependency. Pull the published spec examples for SPD, Slovak, and EPC into fixtures and assert exact string equality:

public function test_czech_spd_matches_spec_example(): void
{
    $payload = (new PaymentQrCodeGenerator(...))
        ->forInvoice($this->czechInvoice())
        ->getQrString();

    $this->assertSame(
        file_get_contents(base_path('tests/fixtures/spd_example.txt')),
        $payload
    );
}

If an upgrade changes a byte, the test fails before a customer's banking app does.

Queue batch generation. For a monthly invoice run of 5,000 documents, generate QR images inside the existing PDF job, not synchronously in the HTTP request. Generation is stateless and horizontal-friendly; the only real constraint is PDF rendering, which you already queue.

Feature-flag the whole thing. Ship behind a flag per market. The rollback path is the manual IBAN + amount + variable symbol block, which should already be present in the template.

Gotchas and Tips

The bundle does nothing for you in Laravel. No auto-discovery, no config publishing, no service bindings that Laravel understands. Installing it as a Laravel dev is the single most common wasted hour on this package. Use the core library.

Amounts must be strings with a dot decimal separator. Passing a float (1499.0) risks locale-dependent formatting and binary rounding; both produce payloads that banking apps silently reject. Use number_format($total, 2, '.', '') or a money library, and never (string) a float directly. EPC is strict about exactly two decimals.

Currency and standard are coupled. SPD implies CZK, EPC implies EUR, the Slovak format implies EUR. Setting setCurrency('EUR') on a Czech payment produces a payload the CZ banks won't honour. Validate the rail/currency pairing in your wrapper and throw early.

Account number vs. IBAN. SPD accepts the domestic prefix-account/bankcode form via setAccountNumber(); EPC requires a full IBAN and, outside the EEA, a BIC. Store the canonical IBAN on your entity, derive the domestic form only for CZ output, and don't let an empty setIban('') slip through — the library will happily build a broken string.

Variable symbol is numeric only, and length-limited. Czech VS is max 10 digits, no separators. If your invoice numbering is INV-2024-0123, you need a separate numeric sequence or you'll get rejections. Same class of problem for the message/comment field: SPD and EPC cap it tightly, and diacritics count differently depending on encoding. Test with a real Příliš žluťoučký kůň comment before you ship Czech-language invoices.

Encoding bites in two places. The payload string itself must be UTF-8, and the QR renderer needs a byte-mode-capable encoder for non-ASCII comments. If the underlying QR library isn't installed (check composer show for endroid/qr-code or equivalent), getQrCode() fails at runtime, not at install. Verify the transitive dependency exists and is version-compatible with your PHP before committing.

Due date format is per-standard. SPD wants YYYYMMDD; EPC wants YYYYMMDD too but rejects dates in the past in some validators. Pass a DateTimeImmutable, not a preformatted string, and let the library format it — unless you've read the source and know it doesn't.

Test with real banking apps, not just fixtures. Golden files prove wire-format correctness; only Air Bank, Fio, Revolut, and your target Slovak/SEPA apps prove acceptance. Scan one generated code per standard, per bank, per platform, once, and keep screenshots in the PR. This is the failure mode that fixtures cannot catch.

WTFPL is public-domain-equivalent but triggers procurement. Get legal sign-off before this lands in a payment-adjacent flow. The name reliably raises questions even though the terms are maximally permissive. The real risk isn't licensing, though — it's that you own maintenance.

Pin the version and treat it as forkable. No SLA, no security-patch guarantee, one maintainer, zero dependents. Put an ^x.y constraint (not *), record the exact resolved version in your lockfile, and keep the rikudou/qr-payment fallback path documented in the adapter's class docblock so the next developer knows a swap is one file.

Never let this own payment state. A QR code is a static instruction — there is no webhook, no callback, no settlement signal. Your reconciliation is "match the bank statement against the variable symbol." Don't model pending → paid transitions off QR generation; the invoice status changes when the bank confirms, not when the code renders.

Tampering is possible and cheap. A QR in an emailed PDF can be replaced by anyone who can edit the PDF. The mitigation isn't cryptographic — it's that the customer verifies the IBAN against the one you separately publish, and that you never trust a scan as proof of payment. Keep the IBAN in text next to the code.

Support runbook, before launch. "The QR doesn't scan" is the incident you will get. Give support: which banks and countries are in scope, how to read the payload string back to the customer, and one sentence on falling back to manual transfer. Without that, every scan failure becomes an engineering ticket for a dependency you don't control.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager