mlucas/invoiceiq-bundle
InvoiceIQ Bundle provides a Laravel/PHP integration for InvoiceIQ, making it easy to connect your application to InvoiceIQ services for invoice creation, management, and billing workflows.
composer require <vendor>/invoiceiq — the service provider and the POST /_invoiceiq/validate route are auto-discovered; there is nothing to register by hand.
Publish and skim the config first: php artisan vendor:publish --tag=invoiceiq-config. That file is the map for everything else (OCR driver, tolerance thresholds, storage toggle).
Send your first request with curl before writing any code:
curl -X POST https://app.test/_invoiceiq/validate \
-H "Accept: application/json" \
-F "file=@storage/app/fixtures/invoice-001.pdf"
You get a JSON rapport back: extracted fields, totals/VAT checks, duplicate flag.
Read the OCR stub caveat before you demo anything. In v0.1.0 extraction does not really happen. Start by exercising the pipeline end-to-end with a fixture that already yields text (or bind a fake extractor — see below), so you are testing the parser and the checks, not an empty string. Everything downstream of extraction is real; extraction is not.
First real use case: a supplier-invoice intake form. User uploads a PDF, your controller forwards it, you read the report, and you route low-confidence documents to a human review queue rather than auto-approving.
Call the service, not the route, from server-side code. The HTTP endpoint is convenient for external callers and for a quick smoke test, but inside your own app an extra loopback request buys you nothing and costs you a timeout budget. Resolve the underlying validator/report service from the container and pass it the uploaded file directly; keep the route for third parties and for the JS front end.
Wrap the report in a DTO immediately. A 0.x JSON contract will move. One mapper class that turns the raw array into your own value object means a key rename in 0.2.0 is a one-file diff instead of a grep across the codebase.
Use pre_validate for context, post_validate for consequences. Attach your tenant id, upload id, or expected vendor in pre_validate; persist the report, write an audit row, or notify an approver in post_validate. Both fire synchronously inside the request — implement ShouldQueue on any listener that touches the database heavily or calls an external API, or your endpoint latency becomes your listener latency.
Persist the report yourself. Storage is OFF by default, so the report is ephemeral. Write it to your own table (invoice_id, report_json, created_at, plus the raw text if you can get it) — you will want it for debugging and for the duplicate check.
Duplicate detection needs a corpus, and storage is the corpus. With local storage disabled there is nothing to compare against across requests, so the duplicate check is close to a no-op in the default configuration. Either enable storage, or maintain your own fingerprint index (vendor + invoice number + total + date) and treat the package's flag as one signal among several.
Bulk intake belongs in a job. One queued job per file, with Http::pool or a bus batch, explicit timeout and a retry policy. Never loop over fifty uploads inside a web request.
In tests, fake the driver and Http::fake() the endpoint. Bind a fake extractor that returns a canned text blob per fixture filename; then your suite covers parser and checks deterministically without depending on the stub's behaviour changing under you.
Treat the report as advisory. Surface confidence, per-check pass/fail and reasons, and always allow a manual override that records who overrode what and why. The checks are arithmetic heuristics, not an audit opinion.
/_invoiceiq/ is a public file-upload endpoint by default. Put it behind auth/signed middleware, add throttle, and validate MIME type and size — an open upload endpoint that hands files to an OCR engine is an easy resource-exhaustion target.web group, browser POSTs need a _token and multipart forms will 419 in ways that look like a parsing bug. Either exclude the path in VerifyCsrfToken or consume it from the api group.post_max_size / upload_max_filesize beat Laravel validation. Big scans get rejected by PHP before your max: rule ever runs, and the failure looks like an empty request rather than a 422. Set both, and set an explicit client timeout — OCR on a multi-page PDF easily exceeds the default.^0.1 and read each changelog. No breaking changes in this release, but pre-1.0 packages reserve the right. The JSON shape and event payloads are the two things most likely to move — keep both behind your own layer and upgrades stay cheap.Extension points worth knowing: the OCR driver contract (swap the stub for a real engine without touching the parser), the two events, and the config tolerance/threshold block.
How can I help you explore Laravel packages today?