mlevent/fatura
A Laravel package for generating invoices in PHP applications. Create itemized invoices with taxes, discounts, and customizable templates, then export them as PDFs for billing, accounting, or record-keeping workflows.
This is a patch release (v0.3.3) on a pre-1.0 package, so treat the API surface as still settling. There are no breaking changes announced — both entries are behavioural fixes — so an existing v0.3.1/v0.3.2 integration can be bumped with a composer update mlevent/fatura and a re-run of your invoice flow.
First steps:
composer require mlevent/fatura, then publish the config (php artisan vendor:publish --tag=... — check src/ for the exact tag) and fill in the GİB credentials, environment (test/production), and company VKN/TCKN.The single most useful first use case is exactly what v0.3.3 touched: create a draft, then immediately fetch back the ETTN/UUID the GİB assigned. That round-trip proves credentials, endpoint, UBL-TR payload shape, and the response parsing all work before you build anything on top. Persist the ETTN in your own invoices table the moment you get it — everything downstream (status polling, cancellation, PDF/HTML retrieval, e-Arşiv vs e-Fatura routing) keys off it.
Draft-then-confirm. Keep the package's draft creation and the later "sign/send" step as two separate jobs in your own code. v0.3.3 specifically improves retrieving the ETTN of the document produced after a draft is created — so the pattern is: create draft → capture ETTN from the returned document → store it → dispatch a queued job that signs/sends once business rules (stock, payment, approval) pass. Never treat the draft call as the invoice being issued.
Persist identifiers, not package objects. Store ettn, invoice_number, recipient_vkn_tckn, status, and raw response JSON on your model. You will need the raw response for support tickets with GİB, and the package's response objects are not a stable serialisation format across 0.x versions.
Reconciliation job. A scheduled command that pulls status for invoices stuck in a pending state is table stakes. With v0.3.3, the "last document" lookup now filters by recipient VKN/TCKN, which makes a recipient-scoped poll safe: lastDocumentFor($vkn)-style calls will no longer hand you another customer's invoice just because it was the most recent one in the account. Where the package exposes it, always pass the recipient explicitly rather than relying on an unscoped "last document" call — that is the whole point of the fix.
Wrap it in your own service. One InvoiceService with draft(), issue(), status(), pdf() methods that return your own DTOs. The package sits behind that boundary; your controllers, listeners, and jobs never import the vendor namespace. This keeps a future 0.4 bump to a single file.
Model events + queue. Fire an InvoiceIssued event after a successful ETTN capture and fan out to notifications, accounting exports, and customer email from listeners. All GİB calls go through queued jobs with $tries and a backoff — the portal is not reliably fast and a request that times out on the HTTP layer may still have created the document.
Multi-tenant. If you issue for several legal entities, put the credentials in a per-tenant config array or a database-backed settings model, and resolve them at call time rather than in the singleton constructor. Combined with the new recipient-scoped lookup, this prevents the worst failure mode: an invoice from tenant A returned for tenant B's query.
composer.json ("mlevent/fatura": "0.3.*") and read the diff between v0.3.1 and v0.3.3 before deploying, not just the changelog bullets.debug level with a correlation ID, but scrub credentials and customer tax IDs. When GİB returns a vague error, the raw XML response body is usually the only thing that tells you which field failed UBL-TR schema validation.InvoiceService boundary, and keep one optional integration test against the GİB test environment that runs nightly. Assert on ETTN being returned after draft creation — that is the exact path v0.3.3 hardened, so it is the one worth pinning with a test.How can I help you explore Laravel packages today?