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 Payment Bundle Laravel Package

nowo-tech/sepa-payment-bundle

Symfony bundle for creating SEPA XML payment files, including pain.001 credit transfers and pain.008 direct debits. Configure creditor and debtor details, mandates, and transactions, then generate bank-ready files with a simple PHP API. Useful for recurring payments and batch transfers.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and register. The bundle is MIT, PHP >= 8.1, Symfony 6/7/8. There is no Flex recipe (0 dependents), so expect a manual registration:

composer require nowo-tech/sepa-payment-bundle
// config/bundles.php
Nowo\SepaPaymentBundle\NowoSepaPaymentBundle::class => ['all' => true],

Confirm the real namespace and extension alias before writing config — with no recipe and one star, the README is the only source of truth. Discover it mechanically rather than by guessing:

bin/console config:dump-reference | grep -i -A2 sepa
bin/console debug:container --show-private | grep -i -E 'sepa|iban|mandate'

2. Where to look first. In this order: README.md → src/DependencyInjection/Configuration.php (authoritative list of every config key and default) → src/Resources/config/services.yaml (service IDs and tags) → the form types under src/Form/ if you want drop-in IBAN fields → tests/ for usage examples that actually run. Skip Resources/public/build, vendor, and any generated XML fixtures on the first pass.

3. Minimal config. Start with only what a single use case needs — a creditor identity and, if you store mandates, nothing else:

# config/packages/nowo_sepa_payment.yaml
nowo_sepa_payment:
    creditor:
        name: "Acme GmbH"
        iban: "DE89370400440532013000"
        bic: "COBADEFFXXX"
        creditor_id: "DE98ZZZ09999999999"

4. First use case: IBAN validation at the boundary. Do not start with XML generation. Start by validating and normalizing an IBAN in the flow where it enters your system (onboarding, payout setup, checkout). That gives you a fast feedback loop, proves the bundle is wired correctly, and is independently useful even if you later swap the package out.

$normalized = $ibanValidator->normalize($raw);   // strip spaces, uppercase
if (!$ibanValidator->isValid($normalized)) { /* reject */ }

Store the normalized value, never the raw input, so your data is consistent when you get to file generation.

5. Second use case: one SCT file, one payment. Generate a single-creditor, single-transaction pain.001 and inspect the XML by hand before automating anything. If that output looks right to you, batch generation will too.

Implementation Patterns

Wrap the bundle behind your own service. Given the adoption profile, do not let bundle classes leak into controllers, entities, or templates. Expose an application-level interface (SepaExporterInterface, IbanGuard) and adapt the bundle inside one small class. Swapping to another implementation later then costs one file, not a refactor.

IBAN validation at every boundary, not just forms. Validate + normalize on ingest (API payload, CSV import, admin edit form), and re-validate before generating a file. Cheap checksum validation upstream is much easier to debug than a bank rejecting a batch three days later.

Mandate lifecycle as an explicit state machine. The bundle gives you validation and generation primitives; it almost certainly does not own your persistence. Model mandates in your own entity with an explicit status (pending → signed → active → used → cancelled | expired), an immutable signed-mandate record (date, reference, debtor identity at signing time), and a unique mandate reference (UMR). Never reuse a UMR. Sequence type is derived from history, not user input:

  • first collection on a mandate → FRST
  • subsequent recurring → RCUR
  • final one → FNAL
  • one-off → OOFF

Encode that derivation in one place (a SequenceTypeResolver) so it cannot drift between call sites.

Batch generation as a job, not a request. Build files in a message handler / console command, persist the generated XML plus a checksum and the criteria used to build it, and write an audit row per included transaction. You want to answer "which invoices were in the file the bank rejected?" without regenerating.

Idempotency by construction. Assign a deterministic end-to-end ID per payment (e.g. invoice-{id}-{attempt}), enforce a unique DB constraint on it, and refuse to build a file containing an ID that has already been exported. Double payments are the expensive failure mode here; the XML library will not save you.

Golden-file tests. Snapshot the generated XML for a fixed set of inputs and diff on change. This is the highest-value test type for a payments bundle and it survives library upgrades:

$this->assertXmlStringEqualsXmlFile(__DIR__.'/fixtures/sct-single.xml', $xml);

Pair it with a validation test against the official EPC XSD for the schema version you target.

Customization through decoration. If the bundle's ID generators, naming, or character sanitization do not match your bank's requirements, decorate the service (decorates: in services.yaml) rather than forking. Same for form types: extend and add options, don't edit.

Configuration per environment. Keep creditor identity in config/packages/ and inject secrets (credentials, if any) via env vars. Use a distinct test creditor ID and test IBAN in when@test so fixtures never look like production data.

Gotchas and Tips

Treat the package as unvetted. One star, zero dependents, and a release date ahead of the current date are all reasons to pin an exact version ("nowo-tech/sepa-payment-bundle": "1.x.y"), read the diff before every bump, and keep the adapter interface so removal is a day's work. Check whether the repo has CI passing and whether the XML schema versions are pinned or hardcoded.

IBAN validity ≠ account validity. Mod-97 checksum and country length checks say nothing about whether the account exists, is open, or accepts debits. Never gate a shipment or a payout on IBAN validation alone.

Schema version is a compatibility decision, not a default. pain.001.001.03, pain.001.001.09, and pain.008.001.02 are accepted differently across banks and countries. Verify which one the bundle emits, confirm it against your bank's spec sheet, and validate against that XSD in tests. A schema mismatch usually surfaces as a rejection notice, not a parse error.

SEPA character set and field limits. Names, remittance information, and references must fit the restricted Latin character set (transliterate accents, & → +, etc.) and length caps (often 70 chars for names, 140 for remittance). Sanitize before handing data to the generator and test with deliberately hostile inputs — Müller & Söhne GmbH, Świętokrzyska 12, emoji, 200-character memo. Silent truncation is worse than an exception.

Money must not be a float. Keep amounts as integer minor units or numeric strings; format to a dot-decimal string only at XML build time. 0.1 + 0.2 bugs in payment files are the kind that only appear once per thousand rows.

Dates have two meanings. "Requested collection date" and "due date" are not the same field and not the same thing as your invoice date. Collection dates must be TARGET2 business days for many schemes; compute the settling date explicitly and store it, and be explicit about timezone (usually Europe/Brussels or plain dates).

Mandate rules the library can't enforce. The 14-month rule for recurring collections, the 36-month rule for one-off/first, debtor notification lead times, and the amendment rules after a bank/account change are your responsibility. Add them as guards in your mandate service; they are the most common audit findings.

Debugging checklist when a bank rejects a file: (1) confirm the configuration actually loaded with bin/console debug:config <alias>; (2) validate the XML against the XSD; (3) upload it to the bank's test/conversion portal rather than reasoning about it; (4) check for duplicate end-to-end IDs; (5) check total amount and transaction count in the header match the body (some generators compute these separately); (6) diff against a known-good file.

Cache and container quirks. After changing bundle config, run bin/console cache:clear — stale config is the most common "my creditor ID didn't change" cause. If a service isn't autowirable, check whether it's public/private in the bundle's services.yaml and inject the interface instead.

PII in logs. Full IBANs, names, and mandate references are personal data under GDPR. Log a masked IBAN (DE89 **** **** 3000) and internal IDs; keep the XML itself in restricted storage with a defined retention period.

Extension points worth knowing. Service decoration, custom ID/reference generators, form type options, and the Twig/asset integrations are the intended seams. Configuration keys and public service IDs are the backward-compatibility surface — if you need a change to one of those, prefer a local decorator over patching the bundle.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core