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

Sepa Bundle Laravel Package

rezzza/sepa-bundle

Symfony bundle for generating SEPA payment files, integrating the rezzza/sepa library to produce credit transfer and direct debit XML. Provides service configuration for adding SEPA payment generation to Symfony applications.

View on GitHub
Deep Wiki
Context7

Getting Started

rezzza/sepa-bundle is a Symfony bundle that generates SEPA Credit Transfer XML files (pain.001.001.03) — the batch files you hand to a bank to pay multiple European IBANs in one go. It is a thin wrapper over a SEPA XML builder, wired into Symfony's DI container.

Reality check for a Laravel codebase: this is a Symfony bundle, not a Laravel package. There is no service provider, and it predates Symfony Flex. You have two realistic paths:

  1. Use the underlying generator directly (recommended). Pull the library it depends on via Composer and call it from a plain PHP service class. You skip the bundle wiring entirely.
  2. Keep the bundle only if you already run Symfony components; otherwise you're installing an old Symfony DI stack just to build an XML string.

If you do install it:

composer require rezzza/sepa-bundle
# inspect what it pins:
composer show rezzza/sepa-bundle

Look first at these files in vendor/rezzza/sepa-bundle/:

  • DependencyInjection/Configuration.php — the config keys (initiating party name, IBAN, BIC, etc.).
  • Resources/config/services.xml — the real service IDs and constructor arguments. This is your API map.
  • The builder/generator classes — this is where transactions are added and XML is emitted.

First use case — pay a list of suppliers:

// Adapt names to the actual service/builder classes in services.xml.
$sepa = $container->get('rezzza.sepa.generator'); // verify ID

foreach ($suppliers as $s) {
    $sepa->addTransaction(
        $s->iban,
        $s->bic,
        $s->name,
        $s->amountCents,      // keep integers, format late
        'INV-'.$s->invoiceId  // remittance info
    );
}

$xml = $sepa->getXml(); // string ready for upload/archive

Generate one file, open it in an editor, and eyeball <GrpHdr>, <PmtInf>, and <CdtTrfTxInf> before wiring anything else.

Implementation Patterns

Wrap it in your own class. Never let the bundle's classes leak through your app. Create App\Payments\SepaFileGenerator that owns the builder, formats amounts, sanitizes names, and returns a DTO with { xml, messageId, createdAt }. The package is unmaintained; the wrapper is your escape hatch to swap in digitick/sepa-xml later.

Batch as a unit of work. SEPA files are per-execution-date, per-debtor-account. Model it:

$batch = SepaBatch::forDate($executionDate, $debtorIban, $debtorBic, $initiatingName);

foreach ($payments as $p) {
    $batch->add($p);            // validates + sanitizes inside
}

Storage::put("sepa/{$batch->messageId()}.xml", $batch->xml());
SepaFile::create([...metadata...]);   // keep the idempotency key

Idempotency via message ID. Generate a UUID (or date + sequence) for MsgId/PmtInfId and store it. If a job retries, you must not send a second file with a fresh ID — banks and reconciliation both hate that. Look up by message ID before regenerating.

One file per execution date and debtor account. Don't mix accounts inside a PmtInf. If you need to pay from two accounts, build two documents (or two PmtInf blocks if you know the schema well).

Queue it. Build + validate + store synchronously, upload asynchronously. Keep the generated XML immutable once stored — it's your audit trail.

Validate against the XSD in tests. Ship the pain.001.001.03.xsd into tests/fixtures/ and assert:

$dom = new DOMDocument();
$dom->loadXML($xml);
$this->assertTrue($dom->schemaValidate(__DIR__.'/fixtures/pain.001.001.03.xsd'));

Also snapshot-test the XML so a refactor can't silently change formatting.

Integration tip — amounts. Work in integer cents everywhere, then format at the edge:

number_format($cents / 100, 2, '.', '')  // "1234.50", no separators

Integration tip — sanitization. SEPA allows a restricted Latin character set. Strip accents/diacritics and reject control chars in names and remittance info before they reach the builder:

$name = Str::ascii($name);
$name = preg_replace('/[^A-Za-z0-9 \/\-\?\:\(\)\.,\'\+]/', '', $name);
$name = mb_substr($name, 0, 70);

Gotchas and Tips

It is effectively abandoned. Last release 2015-12, ~6 stars, no dependents. Expect Symfony 2.x era constraints. On modern PHP/Symfony you'll likely need "config": {"platform": {"php": "..."}} gymnastics, or you fork. A Laravel app on PHP 8+ should not fight this — extract the core or use a maintained library.

Schema version is old. pain.001.001.03 is still accepted by many banks but some now require .001.09 (or ISO 20022 variants). Confirm with your bank before building on this. If they want .09, this package won't get you there without transformation.

BIC/IBAN validation is not enforced. The package builds XML; it does not guarantee your identifiers are valid. Add ramsey/uuid for IDs and a proper IBAN/BIC validator (or the Symfony Validator IBAN constraint) on your side.

No enforcement of SEPA scheme limits. Nothing stops you from emitting >999 transactions in a PmtInf, EUR-only violations, negative amounts, or missing required EndToEndId. Add your own guards:

if (count($payments) > 999) { throw new BatchTooLarge(); }
if ($currency !== 'EUR') { throw new UnsupportedCurrency(); }

Currency must be EUR. The builder may default or ignore currency; hard-code EUR and assert it.

Dates matter. Execution date must be a valid banking day; the CreDtTm timestamp should be ISO 8601 in UTC. Don't let PHP's default timezone leak into the XML.

Config quirks. The bundle config lives under rezzza_sepa: in config.yml (Symfony 2 style). If you're on Flex/Symfony 5+, that extension may never load. Read Configuration.php and set the same values directly on the builder instead of relying on the bundle's config tree.

Debugging. Dump the raw XML and validate out-of-band:

xmllint --noout --schema pain.001.001.03.xsd sepa.xml

Banks reject with terse messages referencing element paths — having the XSD locally cuts debugging time massively.

Extension points. There aren't many. You can override the bundle's service definition (Symfony) or, more practically, compose around the builder: instantiate it yourself, pass formatted data in, and keep all policy (limits, sanitization, IDs) in your adapter. Treat the package as a serializer, not a domain layer.

Migration path. Keep the output contract (XML + metadata DTO) stable and you can replace the internals with digitick/sepa-xml or a hand-rolled DOMDocument builder without touching callers. Given the package's age, plan for that swap from day one.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource