Download the PHP package jiannius/myinvois without Composer
On this page you can find all versions of the php package jiannius/myinvois. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package myinvois
Jiannius MyInvois SDK
A Laravel SDK for the Malaysian LHDN MyInvois e-invoicing API. Handles authentication, UBL 2.1 document building, XAdES digital signing, submission, and lifecycle tracking against both the production and preprod (sandbox) MyInvois portals.
Requirements
- PHP ^8.3
- Laravel ^13.0
- A MyInvois client ID, client secret, private key, and X.509 certificate registered against your company TIN on the MyInvois portal (or the preprod portal)
Host apps still on Laravel 12 should pin the ^0.1 release line — the ^1.0 line is L13-only.
Installation
The package's service provider is auto-discovered via Laravel's package discovery — it also auto-loads the myinvois_documents migration. Just run:
The migration is idempotent (it short-circuits if myinvois_documents already exists), so it's safe even if you've already created the table by other means.
Configuration
Add a myinvois section to config/services.php:
Quick start
Submit the bundled sample document against preprod:
That builds a fully populated test invoice from Helpers/Sample::build(), signs it with your configured cert, and POSTs to MyInvois. The response includes the submission UID and per-document acceptance/rejection.
Resolving the SDK
The package binds a singleton-ish myinvois key to a fresh Myinvois instance:
You can override any of the configured settings per-instance via fluent setters:
| Setter | Purpose |
|---|---|
setClientId($value) |
OAuth2 client ID |
setClientSecret($value) |
OAuth2 client secret |
setPreprod($bool) |
Force preprod (true) or production (false). Omit to auto-detect via app()->environment(). |
setOnBehalfOf($tin, $brn = null) |
For intermediaries — submit as if logged in as another taxpayer. $brn is only used when $tin matches the IG-prefix intermediary format. |
setPrivateKey($pem) |
PEM-encoded RSA private key (only needed for submitDocuments) |
setCertificate($pem) |
PEM-encoded X.509 certificate (only needed for submitDocuments) |
setFailedCallback(fn) |
Closure invoked when an API call returns a non-2xx response. Receives the Illuminate\Http\Client\Response; whatever it returns replaces the original response. |
Submitting documents
submitDocuments($documents) accepts an array of document arrays (see Document shape below) and runs the full pipeline:
- UBL 2.1 schema build (
Helpers\UBL::build) - XAdES enveloped signature (
Helpers\Signature::build) - SHA-256 hash + base64 encode
- POST to
/api/v1.0/documentsubmissions - Create one
myinvois_documentsrow per accepted document (or one per line item for consolidated submissions, see below) - Poll the submission up to 3 times (2s intervals) to flip
submitted→valid/invalidimmediately
Consolidated submissions
When every line item across all submitted documents has only classification code 004, the SDK treats it as a consolidated submission and creates one myinvois_documents row per line item (using description as document_number and the parent invoice number as consolidate_number), rather than one row per document.
Failure handling
A non-2xx response from MyInvois passes through failedCallback if set:
A 403 always throws (Permissions denied from MyInvois Portal). A 4xx during token acquisition aborts with a human-friendly message derived from LHDN's OAuth error — e.g. invalid_client becomes "MyInvois rejected the API credentials…" (see getTokenErrorMessage). Preprod and prod credentials never fall back to one another: requesting preprod without sandbox credentials throws Missing MyInvois sandbox (preprod) Client ID / Client Secret rather than silently using prod creds against the preprod endpoint.
Document shape
The SDK accepts a flat array shape (translated internally to UBL). Below is the canonical structure with required fields marked. See Helpers/Sample.php for a full populated example.
Validating before submission
validator() returns a Laravel Validator instance against the document shape — handy for surfacing user-friendly errors before paying for a rejected MyInvois call:
Pass 'sample' to validate (or submit) the bundled test fixture:
Retrieving documents
All retrieval methods return the raw decoded JSON response from MyInvois. getSubmission and getDocumentDetails additionally call updateMyinvoisDocuments to sync the local myinvois_documents row's status and response columns.
Cancelling and rejecting
cancelDocument updates the local row's status to cancelled and stores the reason in response. The 72-hour window is enforced client-side via MyinvoisDocument::isCancellable().
Taxpayer TIN lookup
Code helper
LHDN publishes static code tables (countries, states, currencies, MSIC industry codes, units of measure, taxes, classifications, payment modes, document types/versions). The SDK ships these as JSON in json/codes/ and exposes them via Jiannius\Myinvois\Helpers\Code:
get($needle) matches by either the code value or the human-readable label. value($needle) returns just the code, label($needle) returns just the description. The countries helper auto-uppercases input; the states helper auto-prefixes Wilayah Persekutuan for KL/Labuan/Putrajaya.
Tracking documents in your host app
The package's MyinvoisDocument Eloquent model is morph-related to whatever host model "owns" each invoice. Attach the HasMyinvoisDocument trait to that model:
The trait adds:
myinvoisDocuments()—MorphManyfor non-preprod docspreprodMyinvoisDocuments()—MorphManyfor preprod docslatestMyinvoisDocument()/preprodLatestMyinvoisDocument()—MorphOne(vialatestOfMany)isSubmittedToMyinvois($submitted = true, $preprod = false)— boolean checkgetMyinvoisValidationLink($preprod = false)— share URL on the MyInvois portalgetMyinvoisQrCode($preprod = false)— data-URI PNG of the QR for the validation linkscopeWithSubmittedMyinvoisDocument($submitted, $preprod)— query scope- Casts:
myinvois_statusandmyinvois_preprod_status(bothStatusenum)
The trait registers an observer that auto-syncs myinvois_status / myinvois_preprod_status columns on the host model whenever a child MyinvoisDocument is saved (separated so preprod testing never overwrites prod state).
To wire up the parent relationship when submitting, just set parent_type / parent_id after submission (or extend submitDocuments in your own code to set them inline).
Swapping the document model
By default, the trait and Myinvois itself use the package's own Jiannius\Myinvois\Models\MyinvoisDocument. Host apps that want to add custom scopes/methods can extend it and register their subclass from a service provider:
Both the trait's morph relations and the SDK's internal create()/query() calls resolve through MyinvoisDocument::$useModel, so both stay consistent.
Enums
Jiannius\Myinvois\Enums\Status
Jiannius\Myinvois\Enums\TinType
Special TINs LHDN reserves for non-traditional buyers:
When a buyer's TIN matches one of these, the validator skips the BRN/NRIC/phone/address requirements. (GENERAL_PUBLIC cannot be used on standard non-consolidated invoices — only credit/debit/refund notes or consolidated invoices.)
On-behalf-of (intermediary submissions)
If you're a tax service provider submitting on behalf of clients, set the target TIN before each call:
The TIN goes into the OAuth onbehalfof header. If $clientTin matches config('services.myinvois.client_tin'), the SDK clears the header (you're submitting as yourself).
The intermediary IG-prefixed format requires both TIN and BRN, joined as IG...:BRN — handled automatically by setOnBehalfOf.
Rate limiting
The SDK enforces the per-endpoint limits LHDN publishes, using Laravel's RateLimiter. When the local counter hits the cap, the call sleeps for 60 / perMinute seconds and clears the counter rather than rejecting outright:
| Endpoint family | Limit (per minute) |
|---|---|
documentsubmissions (submit), documents/*/raw, documents/*/details, taxpayer search, documentsubmissions/{uid} |
60 |
documents/recent, documents/search, documents/state/*/state |
12 |
Token caching
OAuth tokens are cached under myinvois_<clientId>_<onBehalfOf> for 50 minutes (MyInvois issues 60-minute tokens; the 10-minute buffer prevents edge-of-window failures). The cache is automatically invalidated on expiry.
License
MIT — see LICENSE.md.
All versions of myinvois with dependencies
illuminate/support Version ^13.0
illuminate/database Version ^13.0
illuminate/http Version ^13.0
symfony/http-client Version ^7.0
noki/laravel-xml-converter Version ^1.0
simplesoftwareio/simple-qrcode Version ^4.2