jiannius/myinvois
A Laravel package for integrating with Malaysia's MyInvois e-Invoicing platform, offering helpers to submit, validate, and manage electronic invoices through the LHDN MyInvois API from PHP applications.
Myinvois::queueSubmission() moves LHDN latency out of the web request. The web request only dispatches; SubmitDocuments/PollSubmission jobs do the waiting. This unlocks product decisions like "invoice issuance never blocks checkout," and makes p95 checkout latency independent of LHDN availability. Plain submitDocuments() still works, so the migration is a per-flow decision rather than a platform rewrite.Contracts\ResolvesMyinvoisClient and Myinvois::clientFor() enable per-org credentials resolved on the worker, with one client enforced per submission. If the roadmap includes serving multiple legal entities, subsidiaries, or intermediary/on-behalf-of flows, this release removes a blocker that would otherwise force a fork or a separate deployment.myinvois:sync with --limit, --dry-run, --include-valid, forParents(), withoutPolling(), and an opt-in schedule (services.myinvois.sync_schedule) turns "did every invoice actually land in LHDN?" into a background sweep. This directly informs support-cost and headcount assumptions in the business case.SubmissionAccepted, SubmissionSucceeded, SubmissionFailed (carrying LHDN's INVALID reasons), and DocumentStatusChanged give the product a clean seam for status timelines, notifications, dunning, or a customer invoice-status page — without scraping the database.rejectDocument(), payable_rounding, and INVALID-reason handling are not live-verified. That is a planning input, not just a footnote: those paths need a UAT line item before GA.Adopt when:
Look elsewhere, or defer, when:
rejectDocument() or payable_rounding now — neither is live-verified; treat both as UAT-gated. Same caution for relying on structured INVALID reasons in an automated decision path (unit-tested only).Myinvois::fake(), contracts, artisan, config), which you'd be paying for and not using.retry_after must exceed 120s. That single config incompatibility will silently cause duplicate processing; if you can't change it, don't adopt the queued path.To executives: this release closes the gap between "we can call LHDN's API" and "we can run e-invoicing in production." Three things change. One, invoices stop waiting on a government API inside the customer's request — submissions go to a queue, so checkout stays fast even when LHDN is slow or down. Two, the double-submission risk that finance and audit will inevitably raise is now handled in the library, not in bespoke code we'd have to write, test, and defend. Three, we can serve multiple legal entities with per-entity credentials, which removes a blocker for the next phase of the roadmap. All of it is opt-in, so there is no forced migration and no rework of existing flows.
To engineering: it's additive and non-breaking. submitDocuments() is unchanged; the queue path is a separate call. What you get: encrypted queued payloads with no secrets in the job, a per-document in-flight lock (MyinvoisInFlightException) so redelivered or superseded jobs never POST twice, ResolvesMyinvoisClient/clientFor() for per-org credentials on workers, an artisan sync sweep with dry-run and scoping, and lifecycle events carrying LHDN's reasons. There are also unconditional fixes worth taking regardless: status saves no longer bump the parent's updated_at, rate limiting is now correctly keyed per endpoint and client id (it was per submission uid), a null-returning failedCallback no longer crashes submission, a missing status field no longer blanks a row, plus a PHP 8.4 deprecation fix and a clearer unauthorized_client message.
Adoption steps and caveats to state plainly: wire the services.myinvois.* config — queue_connection, queue, lock_store, lock_seconds, sync_schedule, sync_limit — and verify queue retry_after exceeds 120s before enabling the queued path. Use Myinvois::fake() plus pollResolvesTo() in tests. Then set expectations: the release was validated end to end against the LHDN preprod sandbox, but INVALID-reason handling is unit-tested only, and rejectDocument() and payable_rounding are not live-verified — those need UAT coverage before we depend on them in a customer-facing flow.
How can I help you explore Laravel packages today?