You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement Peppol e-invoicing with a pluggable provider + format architecture so that country-specific access points (LetsPeppol for Belgium, PDP/Sequino for France, Storecove internationally, e-invoice.be for Belgium) and document formats (UBL, CII, Factur-X, NLCIUS, etc.) can be added without touching the core.
Related:#487 (Dutch NLCIUS) is a configuration sub-scope of this issue — not a separate implementation. See NLCIUS section below.
Provider = WHO carries the document to the Peppol network
Format = HOW the invoice document is structured
Modules/Invoices/Peppol/
├── Contracts/
│ └── ProviderInterface.php ← contract every provider must implement
├── Providers/
│ ├── BaseProvider.php ← shared error classification, base URL
│ ├── EInvoiceBe/ ← ✅ FULL
│ │ └── EInvoiceBeProvider.php
│ ├── Storecove/ ← ⚠️ STUB
│ │ └── StorecoveProvider.php
│ ├── LetsPeppol/ ← ❌ MISSING
│ │ └── LetsPeppolProvider.php
│ ├── Pdp/ ← ❌ MISSING (France)
│ │ └── PdpProvider.php
│ └── Sequino/ ← ❌ MISSING (France)
│ └── SequinoProvider.php
├── FormatHandlers/
│ ├── InvoiceFormatHandlerInterface.php ← contract
│ ├── BaseFormatHandler.php
│ ├── UblHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── PeppolBisHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── CiiHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── FacturXHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── ZugferdHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── FatturaPaHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── FacturaeHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── OioublHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ ├── EhfHandler.php ← ✅ skeleton, ⚠️ generateXml() is a stub
│ └── FormatHandlerFactory.php ← ✅ strategy pattern, register() for custom formats
└── Services/
├── PeppolService.php ← ✅ send, cancel, status — wired to EInvoiceBe
├── PeppolManagementService.php ← ✅ integration CRUD
└── PeppolTransformerService.php ← ✅ data transformation
Adding a new provider is a single directory drop-in — ProviderFactory scans Peppol/Providers/ subdirectories for *Provider.php classes implementing ProviderInterface. No registration code needed anywhere.
All 9 handlers implement the correct data transformation in transform() but generateXml() currently returns json_encode($data) instead of real XML. This must be replaced with proper XML generation before any handler is usable for real transmission.
Priority order for real XML output:
UblHandler (UBL 2.1 / 2.4) — covers NL, BE, most of Europe
PeppolBisHandler (Peppol BIS 3.0) — default fallback
Use horstoeko/ubl (Composer), sabre/xml, or PHP's DOMDocument directly.
StorecoveProvider
All methods return 'not yet implemented'. Needs full API integration against https://api.storecove.com/api/v2.
SendInvoiceToPeppolAction
Currently a standalone action class, not a Filament Action. Needs to extend Filament\Actions\Action so it can be registered in EditInvoice::getHeaderActions() and InvoicesTable.
NLCIUS is fully covered once UblHandler::generateXml() produces real XML.
Filament UI
RelationForm — conditional Peppol section (show only for EU customers): peppol_id (TextInput), peppol_format (Select from PeppolDocumentFormat), enable_e_invoicing (Toggle)
Peppol integration settings page per company: select provider, enter API key, test connection button
SendInvoiceToPeppolAction as a Filament Action on EditInvoice header and InvoicesTable row
Status indicator on invoice list/edit showing Peppol transmission state
Config file
config/invoices.php (or dedicated config/peppol.php) with env-driven defaults as documented in IMPLEMENTATION_SUMMARY.md.
Feature tests
Unit tests cover isolated classes. Integration tests are needed for the full Filament/Livewire flow:
Overview
Implement Peppol e-invoicing with a pluggable provider + format architecture so that country-specific access points (LetsPeppol for Belgium, PDP/Sequino for France, Storecove internationally, e-invoice.be for Belgium) and document formats (UBL, CII, Factur-X, NLCIUS, etc.) can be added without touching the core.
Branch:
feature/126-implement-peppol— PR #471 (draft, open)Architecture
Two independent plug-in points:
Adding a new provider is a single directory drop-in —
ProviderFactoryscansPeppol/Providers/subdirectories for*Provider.phpclasses implementingProviderInterface. No registration code needed anywhere.✅ Built (PR #471)
Core infrastructure
ProviderInterface— 8-method contract (sendInvoice,validatePeppolId,testConnection,getTransmissionStatus,registerWebhookCallback,fetchAcknowledgements,cancelDocument,classifyError)ProviderFactory— auto-discovers providers via directory scan;make(PeppolIntegration),makeFromName(string),getAvailableProviders(),isSupported()BaseProvider— default error classification (transient/permanent/unknown), base URL resolution,getApiToken()FormatHandlerFactory— strategy pattern;create(PeppolDocumentFormat),createForInvoice(Invoice),register()for runtime additionsInvoiceFormatHandlerInterface+BaseFormatHandlerPeppolDocumentFormatenum — 11 formats withrecommendedForCountry(),formatsForCountry(),isMandatoryFor(),xmlNamespace()PeppolEndpointSchemeenum — 17 schemes (BE:CBE, NL:KVK, DE:VAT, FR:SIRENE, IT:VAT, etc.) with country auto-selectionEInvoiceBe provider (full)
DocumentsClient—submitDocument(),getDocumentStatus(),cancelDocument()ParticipantsClient—searchParticipant(),lookupParticipant(),checkCapability(),getServiceMetadata()TrackingClient—getTransmissionHistory(),getStatus(),getDeliveryConfirmation(),listDocuments(),getErrors()WebhooksClient— full CRUD + test + secret rotationHealthClient— ping, status, metrics, connectivity, version, readiness, livenessDatabase
peppol_integrations— provider_name, api_token, company_id, enabledpeppol_integration_config— per-integration key/value configpeppol_transmissions— invoice_id, external_id, status, format, providerpeppol_transmission_responses— raw provider responsescustomer_peppol_validation_historycustomer_peppol_validation_responsesrelationstable:peppol_id,peppol_format,enable_e_invoicingcolumns addedModels, Events, Jobs, Commands
PeppolIntegration,PeppolTransmission,PeppolIntegrationConfig,PeppolTransmissionResponse,CustomerPeppolValidationHistory,CustomerPeppolValidationResponsePeppolTransmissionCreated,PeppolTransmissionSent,PeppolTransmissionFailed,PeppolTransmissionDead,PeppolAcknowledgementReceived,PeppolIntegrationCreated,PeppolIntegrationTested,PeppolIdValidationCompletedSendInvoiceToPeppolJob(with retry logic),PeppolStatusPoller,RetryFailedTransmissionsPollPeppolStatusCommand,RetryFailedPeppolTransmissionsCommand,TestPeppolIntegrationCommandLogsPeppolActivitytrait,HttpClientExceptionHandlerdecoratorTests (15 files, ~71 unit tests, all
#[Group('peppol')])ProviderFactoryTest— discovery, caching, instantiationPeppolServiceTest— send, validate, cancel, error handling (Http::fake)FormatHandlerFactoryTest+FormatHandlersTest+FatturaPaHandlerTestPeppolDocumentFormatTest+PeppolEndpointSchemeTestDocumentsClientTestApiClientTest+HttpClientExceptionHandlerTestSendInvoiceToPeppolActionTestPeppolConnectionStatusTest,PeppolErrorTypeTest,PeppolTransmissionStatusTest,PeppolValidationStatusTestFormat handlers —
generateXml()is a placeholderAll 9 handlers implement the correct data transformation in
transform()butgenerateXml()currently returnsjson_encode($data)instead of real XML. This must be replaced with proper XML generation before any handler is usable for real transmission.Priority order for real XML output:
UblHandler(UBL 2.1 / 2.4) — covers NL, BE, most of EuropePeppolBisHandler(Peppol BIS 3.0) — default fallbackCiiHandler(CII) — covers DE, FR, ATFacturXHandler— covers FR (PDF/A-3 embedding)Use
horstoeko/ubl(Composer),sabre/xml, or PHP'sDOMDocumentdirectly.StorecoveProvider
All methods return
'not yet implemented'. Needs full API integration againsthttps://api.storecove.com/api/v2.SendInvoiceToPeppolAction
Currently a standalone action class, not a Filament
Action. Needs to extendFilament\Actions\Actionso it can be registered inEditInvoice::getHeaderActions()andInvoicesTable.❌ Missing
Providers to add
Each provider is a single file drop-in:
ProviderFactorydiscovers it automatically. No other files need touching.Providers/LetsPeppol/Providers/Pdp/Providers/Sequino/Providers/Storecove/NLCIUS (Dutch e-invoicing, issue #487)
NLCIUS is not a new format handler — it is a UBL 2.1 document with a specific
customizationID. TheUblHandleralready reads this from config:Two changes needed:
PeppolDocumentFormat::recommendedForCountry('NL')— currently returnsUBL_24but NLCIUS mandates UBL 2.1:NLCIUS is fully covered once
UblHandler::generateXml()produces real XML.Filament UI
RelationForm— conditional Peppol section (show only for EU customers):peppol_id(TextInput),peppol_format(Select fromPeppolDocumentFormat),enable_e_invoicing(Toggle)SendInvoiceToPeppolActionas a FilamentActiononEditInvoiceheader andInvoicesTablerowConfig file
config/invoices.php(or dedicatedconfig/peppol.php) with env-driven defaults as documented inIMPLEMENTATION_SUMMARY.md.Feature tests
Unit tests cover isolated classes. Integration tests are needed for the full Filament/Livewire flow:
PHPUnit test requirements for new providers
Every new provider must ship a test file:
Sprint 5 scope
Given the work already in PR #471, the remaining Sprint 5 deliverables are:
UblHandler::generateXml()produce real UBL 2.1 XML — this unblocks NLCIUS ([Invoices]: NLCIUS (Dutch CIUS) e-invoicing profile for UBL 2.1 export #487), BE, NL, and most of EuroperecommendedForCountry('NL')→UBL_21(one line)SendInvoiceToPeppolActionto a Filament Action and wire it toEditInvoiceLetsPeppolProviderdirectory + testsStorecove, PDP, and Sequino providers are Sprint 6+.