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.
Architecture fit
Myinvois facade, config under services.myinvois.*, an artisan command, and domain events. It slots into an existing app without introducing a new architectural pattern.queueSubmission() with SubmitDocuments/PollSubmission jobs is the right shape.Contracts\ResolvesMyinvoisClient + Myinvois::clientFor() is the significant addition for multi-tenant deployments — per-org credentials resolved on the worker, one client per submission enforced. This is what makes the package viable for a SaaS/agency model rather than single-tenant.MyinvoisInFlightException, parent refusal when latest submission is SUBMITTED/VALID) puts idempotency in the package rather than leaving it to the integrating app. That is the correct boundary and removes a common source of duplicate submissions.Integration feasibility
submitDocuments() is unchanged. The fixes ship unconditionally, so every app gets a patch-level upgrade path independent of the queue workstream.array or file on multi-host), a scheduler entrypoint, and stable APP_KEY across deploys (job payloads are encrypted).Technical risk
retry_after must exceed 120s — this is a hard configuration invariant documented in the release, and getting it wrong produces duplicate job execution. The in-flight lock mitigates but does not eliminate this; lock duration (lock_seconds) must also exceed worst-case job runtime including poll backoff.lock_store is left at a per-host store, the lock is decorative under horizontal scaling — a silent, environment-dependent failure that will not show up in single-node dev/test.sync_schedule only registers a schedule when configured. If an app enables queueSubmission() but never sets sync_schedule, submissions can sit in SUBMITTED indefinitely with no reconciliation. This is a coupling hazard: queueing and sweeping must be deployed together.clientFor() works on the web request but can fail on the worker (missing org context, different config source) — a split-brain symptom that is easy to misdiagnose.rejectDocument(), and payable_rounding. Rejection and rounding paths remain unverified against the sandbox, so they should not be treated as production-proven.Key questions
ResolvesMyinvoisClient resolve them from a worker without request/session context?updated_at being bumped on document status change (now removed by design)?Stack fit
schedule:run cron or schedule:work), cache/lock backend, and the package's config block. No new runtime, no new datastore beyond the lock store.Migration path
submitDocuments(). Phase 2: enable queueSubmission() behind a config flag, org by org.updated_at churn, shared rate limiting, no crash on null failedCallback, status no longer blanked by a missing status field) remove known production bugs.Compatibility
updated_at — anything keyed on that (reports, sync triggers, cache busting) needs checking; (b) rate limiting is now shared per endpoint and client id instead of per submission uid — real throughput to LHDN will change, potentially upward, so verify against actual LHDN quotas.APP_KEY; key rotation during a deploy window could invalidate queued work. Treat key rotation as a drain-first operation.Sequencing
updated_at and rate-limit dependencies; adjust downstream consumers.retry_after > 120s and lock_seconds > worst-case job time.ResolvesMyinvoisClient for per-org credentials and validate client resolution on the worker.sync_schedule — same release as queueing, not later.SubmissionSucceeded/SubmissionFailed/DocumentStatusChanged) into alerting and audit logging.queueSubmission() on one org or document type against preprod; keep submitDocuments() as a documented fallback.Maintenance
retry_after, lock_seconds, queue_connection). These are now part of the runbook, not the package's problem.queue_connection, queue, lock_store, lock_seconds, sync_schedule, sync_limit). Treat it as reviewed infrastructure config, not per-developer local preference — drift between environments is the likely source of subtle bugs.Myinvois::fake() with pollResolvesTo() substantially lowers the cost of testing the queued path in CI — worth building around deliberately.Support
SubmissionFailed, surface LHDN's INVALID reasons from the event payload to the operations or finance team, and log DocumentStatusChanged as the reconciliation audit trail.MyinvoisInFlightException is correct behaviour, not an outage — the runbook must say so explicitly, or on-call will treat duplicate-attempt protection as a bug.unauthorized_client message (intermediary not authorised for this on-behalf-of TIN on this environment) is a real support-cost reduction; it turns a credential misconfiguration into a self-describing error.Scaling
sync_limit on the sweep, not by request-path changes.Failure modes
lock_seconds exceeded → lock expiry mid-job. The redelivery/superseded guard prevents double POST in the common case, but the window should be monitored.sync_schedule unset or scheduler unhealthy → documents stuck in SUBMITTED with no status progression. Alert on submission age, not just on failures.APP_KEY rotation invalidating encrypted in-flight payloads; retry_after < 120s producing duplicate execution.rejectDocument(), and payable_rounding are unit-tested at best. Rejection and rounding are financially material; pilot them explicitly before relying on the package for those flows.Ramp-up
queueSubmission().How can I help you explore Laravel packages today?