Download the PHP package bee-coded/laravel-efactura without Composer

On this page you can find all versions of the php package bee-coded/laravel-efactura. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package laravel-efactura

Laravel e-Factura

A Laravel package that wraps bee-coded/laravel-efactura-sdk to provide token storage, job scheduling, and easy model integration for Romanian e-Factura (ANAF SPV) compliance.

Upgrading to v3.0 — read before deploying

v3 encrypts the ANAF credentials already in your database and adds a unique index to efactura_uploads. This upgrade requires a maintenance window with your queue workers stopped. That is not a recommendation — a worker left running across the migration can permanently orphan a company's credentials, and no re-run of the migration will fix it.

Read this whole section before running anything. The steps are ordered deliberately: the prerequisites all come before the commands, because doing them afterwards is how the outages happen.

Step 0 — Prerequisites, before you run any command

0a. Back up APP_KEY, somewhere other than the database it protects

From v3, APP_KEY decrypts your ANAF credentials. Lose it and every connected company must redo the OAuth flow (php artisan efactura:auth {cui}). It was never load-bearing for e-Factura before, so it may never have been treated as a secret worth backing up. It is now.

If you are rotating it, set APP_PREVIOUS_KEYS — see APP_KEY obligations.

0b. Audit for writes that bypass the model

Builder::update() and raw SQL skip casts, so DB::table('efactura_tokens')->update([...]) — or EfacturaToken::where(...)->update([...]) — silently writes a value nothing can decrypt. Load the model instance and update it, so the cast runs. Fix these before deploying; they fail silently.

0c. Size the unique-index build

The index migration runs ALTER TABLE efactura_uploads ADD UNIQUE (uploadable_type, uploadable_id). This is not free on a large table:

With workers stopped (Step 1) there is no concurrent write load, which is most of the risk gone. If the table is large enough that the window itself is the problem, build the index out of band with pt-online-schema-change or gh-ost instead, then mark the migration as run.

Step 1 — Stop the queue workers (MANDATORY)

Do this before composer update, and keep them down until after php artisan migrate.

php artisan down is a necessary start but is not sufficient on its own:

So: run down, then actually stop the worker processes (supervisorctl stop laravel-worker:*, or your orchestrator's equivalent) and confirm nothing is in flight before you migrate.

Why this is mandatory, and not a step-6 afterthought

A v2 worker that is still alive while the migration runs will:

  1. Refresh a token and write PLAINTEXT into an already-encrypted column. $token->update([...]) from v2 code has no cast. The encryption migration has by then already been recorded as run, so the usual remedy — "just re-run the migration" — does nothing at all. That company throws DecryptException on every operation until it re-authorises. This is the one failure in this upgrade with no clean recovery.
  2. Read an already-encrypted row and send the ciphertext to ANAF as a Bearer token. ANAF answers 401, and the invoices in that batch park as Failed.

Neither is a race you can win by being quick. Stop the workers.

Step 2 — Check for duplicate uploads (inside the window, writes stopped)

Run this now, with workers down — not the day before:

v3 adds a unique (uploadable_type, uploadable_id) index, and its migration aborts if pre-3.0 duplicates exist. Duplicates can mean an invoice was filed at ANAF more than once, so the migration does not resolve them for you: reconcile each against ANAF, keep the row reflecting the real filing, remove the rest.

Why inside the window: v2's queueUpload() has no duplicate guard, so a check run while v2 is still accepting writes is only true for as long as it takes the next request to reintroduce a duplicate. With writes stopped the check is authoritative. (Since v3 now encrypts tokens before touching this index, an abort here no longer takes e-Factura down — this check is advisory rather than load-bearing. It still saves you a failed deploy.)

Step 3 — Update and migrate

Between those two commands, e-Factura operations for existing companies fail with DecryptException: The payload is invalid. This is by design (see below). Keep the gap short — this is exactly what the window is for.

Four migrations ship; three are new in v3 and run in this order:

Order Migration What it does
1st ..._000004_encrypt_efactura_token_credentials Encrypts the plaintext access_token / refresh_token already in efactura_tokens. Runs first on purpose: it is what restores service for the v3 code you just deployed, so nothing that can abort is allowed to precede it
2nd ..._000005_add_failure_reason_to_efactura_uploads_table Adds the indexed failure_reason column; backfills rate_limited for rows whose legacy errors text carries a RATE_LIMIT_EXCEEDED: marker. Anything unrecognised stays NULL rather than being guessed. Re-entrant — an interrupted backfill resumes on the next migrate
3rd ..._000006_add_unique_uploadable_index_to_efactura_uploads_table Adds the unique (uploadable_type, uploadable_id) index. Aborts on pre-3.0 duplicates (Step 2). Runs last because it is the only one expected to abort
4th ..._000007_add_response_attempt_tracking_to_efactura_uploads_table Adds response_attempts / response_failed_at so a poisoned /descarcare body (2xx that isn't a ZIP) is retried a bounded number of times rather than on every run forever. Additive columns only; cannot abort

Step 4 — Bring it back up

Start your worker processes again (supervisorctl start laravel-worker:*). queue:restart is the belt-and-braces for any worker that survived Step 1: a stale one holds pre-v3 code in memory and will keep firing InvoiceFailed on rate limits and writing plaintext credentials.

If migrate aborts: how to get out of it

An abort is not a no-op at the database level. Migrations that already ran are committed and recorded; only the aborting one is not. What you have depends on where it stopped:

Aborted at State Do this
..._000004 (encryption) Tokens partly/not encrypted. e-Factura is down The migration is idempotent — fix the cause and re-run php artisan migrate. It resumes safely
..._000005 (failure_reason) Tokens encrypted, service is up. Column may exist without the backfill finished Re-run php artisan migrate. It is re-entrant: it skips the DDL it already did and resumes the backfill
..._000006 (unique index) Tokens encrypted, service is up. Only the index is missing Resolve the duplicates (Step 2), then re-run php artisan migrate. No rush — but the duplicate race stays open until you do

Emergency lever — restore service immediately. If you are down and need tokens working now without waiting to resolve anything else, run the encryption migration on its own:

That is safe to run at any point: it is idempotent, touches only efactura_tokens, and has no dependency on the other two. (If you published the migrations into your app, point --path at your copy instead.) Then sort out the rest at your own pace.

OAuth tokens are now encrypted at rest

efactura_tokens.access_token and refresh_token are encrypted via Laravel's encrypted cast. Before v3 they were stored in plaintext, so anyone with a database read, a backup, or a query log held live ANAF credentials — enough to file and read legal tax documents for every connected company. (The model's $hidden never protected against this; it only omits the fields from toArray() / toJson() and has no bearing on what is written to disk.)

The cast has no plaintext fallback: a fallback would let a migration that never ran pass unnoticed while your credentials stayed readable in the database. Encryption at rest has to be a verifiable fact rather than a hope, so an unmigrated row fails loudly at the point of use instead of quietly working.

The migration is idempotent — it skips values that are already ciphertext, so re-running it cannot double-encrypt, and it is safe after a partial failure or on a table mixing migrated and freshly-authorised rows. It is also reversible: php artisan migrate:rollback restores plaintext, so downgrading to v2 (whose model has no cast) leaves no unreadable ciphertext behind.

A rollback attempted under the wrong APP_KEY (rotated key, or a production dump restored elsewhere) aborts loudly rather than reporting success — it will not leave ciphertext sitting in a column that a downgraded v2 would hand to ANAF as a Bearer token.

APP_KEY now protects your ANAF credentials — this is new in v3

Your APP_KEY was never load-bearing for e-Factura before. It is now.

Writes that bypass the model now store unreadable plaintext. Builder::update() and raw SQL skip casts, so DB::table('efactura_tokens')->update(['access_token' => $raw]) — or EfacturaToken::where(...)->update([...]) — silently writes a value nothing can decrypt. Load the model instance and update it, so the cast runs. Audit for raw writes before deploying.

Other v3 breaking changes

Full step-by-step upgrade guide: ask the efactura MCP server for the migration topic (get-wrapper-docs → topic: "migration"). It covers each break, the symptom you will actually see, and the exact code change, in the order a real upgrade performs them.

Features

Requirements

Installation

Publish the configuration and migrations:

Configuration

Environment Variables

Config File

The configuration file (config/efactura.php) allows you to:

Job schedules are not configured here. There is no schedule or cron key in config/efactura.php — this package does not schedule anything for you. You register the schedules in your own application; see Job Scheduling.

Model Integration

1. Implement the Interface

Your invoice model must implement EFacturaUploadableInterface:

isVatPayer is required, and its position changed in SDK v3.0 — it is now the 4th PartyData constructor parameter, swapping places with registrationNumber, and has no default. Always use named arguments, as above. A v2-era positional call new PartyData($name, $id, $address, 'J40/1234/2020') now assigns the ONRC string to isVatPayer; from a caller without declare(strict_types=1) PHP silently coerces it to true and drops the registration number — no exception, and ANAF accepts the resulting document.

taxAmountRon is required for non-RON invoices (SDK v3.0) and rejected for RON ones — see the taxAmountRon line above. Pass the converted amount your ledger already holds (the BNR-rate figure), not an exchange rate: ANAF cannot verify the conversion, so a wrong value is accepted and filed as a true statement of VAT owed.

Using Date::use(CarbonImmutable::class)? Pass your datetime casts straight in, as above. CarbonImmutable is not a Carbon subclass, so this required SDK v2.3.0+ — on earlier versions issueDate: $this->issued_at threw a TypeError (or, from a caller without declare(strict_types=1), silently stringified the date and lost its timezone). The SDK normalises immutable dates to a mutable Carbon internally; nothing changes for apps on mutable Carbon.

2. Available Trait Methods

The HasEfacturaUpload trait provides:

Usage

Queue an Invoice for Upload

Queue a Credit Note for Upload

Credit notes work the same way as invoices — just set invoiceTypeCode to CreditNote and reference the original invoice:

Note: The SDK (v1.1+) automatically negates credit note line quantities before sending to ANAF (which expects positive values in <CreditNote> documents). Pass negative quantities for items being credited and positive for debit-back lines (e.g., discount reversals).

The taxAmount Parameter (Required since SDK v2.0)

Every InvoiceLineData requires a taxAmount — the pre-computed VAT amount for that line. The SDK uses this value directly in the XML instead of recalculating VAT internally.

Why this matters: In v1.x, the SDK grouped lines by tax rate and recalculated VAT as sum_of_bases × rate. This caused rounding discrepancies (typically 0.01 RON) when your application used tax-included pricing, because extracting VAT by subtraction (gross - net) can produce different results than multiplying (net × rate) after rounding. By passing your pre-computed taxAmount, the XML total matches your application's total exactly.

How to compute it:

Sign convention: The taxAmount sign follows the quantity — negative for credit note lines (negative qty), positive for regular lines.

Upgrading from SDK v1.x: Add taxAmount to every InvoiceLineData in your toEfacturaData() method. Pass the VAT amount your application already computes for each line item.

Process Upload Immediately

Access the SDK Client

For advanced operations, get an authenticated SDK client:

This client does not persist a token refresh — treat it as an escape hatch. ANAF rotates refresh tokens, so if the SDK refreshes during one of these calls, the new grant exists only inside $client. The stored token is now spent, and once $client goes out of scope the company must re-authorise from scratch. This needs no concurrency to happen — a token within the SDK's 120-second expiry buffer is enough.

For anything that files or fetches on a company's behalf, prefer executeWithClient(), which locks, refreshes and persists around your operation:

If you must use EFactura::client(), persist afterwards yourself: app(TokenService::class)->handleClientTokenRefresh($client, EFactura::getToken($cui));

Company Lookup (No Auth Required)

Use the SDK directly for company lookups:

Automatic Retry on ANAF's Rate Limit

ANAF's public company-lookup endpoint is capped at 1 request/second. The SDK enforces this client-side and throws RateLimitExceededException when the limit is exceeded — it does not retry. This wrapper decorates the SDK's AnafDetailsClientInterface binding with RetryingAnafDetailsClient, which retries up to 5 total attempts (configurable), sleeping the exception's retryAfterSeconds (≥ 1s) between attempts, then re-throws if still limited.

The retry is synchronous and blocking — a fully rate-limited lookup can block the request for up to ~(attempts − 1) seconds before re-throwing, so the consuming app can surface a "registry busy — retry" message. Only the rate-limit exception triggers a retry; a failure() result (invalid CUI, malformed response) passes straight through, and the SDK already retries transient 5xx/connection errors internally.

Because the binding is decorated, no code change is needed — anything resolving AnafDetailsClientInterface (or the AnafDetails facade) gets the resilient client transparently.

OAuth Flow

1. Redirect to ANAF

The package provides routes for OAuth:

In your application:

Or generate the URL manually:

2. Handle the Callback

The package automatically:

3. Listen to Events

Job Scheduling (Required)

Important: This package provides jobs but does NOT schedule them automatically. You must register the job schedules in your application.

Register Jobs in Your Scheduler

Add the following to your bootstrap/app.php:

Adjust the schedules to fit your application's needs. All batch jobs except RetryRateLimitedUploads and SweepStaleUploads accept an optional $cui parameter to process only a specific CUI:

RetryRateLimitedUploads and SweepStaleUploads take no arguments and always scan every CUI. Because PHP silently discards extra constructor arguments, new RetryRateLimitedUploads('12345678') will not filter by CUI — it processes all companies, with no error to tell you otherwise.

Dispatch a Single Upload or Status Check

For immediate processing of a single upload, dispatch the single-model jobs:

Available Jobs

Batch Jobs (Scheduled)

Job Purpose Suggested Schedule
ProcessPendingUploads Upload all pending invoices to ANAF Every 5 minutes
CheckUploadStatuses Check processing status at ANAF Every 10 minutes
DownloadResponses Download response ZIPs Every 15 minutes
DownloadReceivedInvoices Download received invoices Every 4 hours
SyncMessages Sync message list from ANAF Every hour
RetryRateLimitedUploads Reset safe-to-resubmit failures back to pending Every 10 minutes
SweepStaleUploads Park uploads stranded in uploading for reconciliation Every 15 minutes

Single-Model Jobs (On-Demand)

Job Purpose
ProcessSingleUpload Process a single upload immediately
CheckSingleUploadStatus Check status for a single upload

Job Configuration

Standard jobs (batch processing, status checks, downloads):

Exception — RetryRateLimitedUploads declares no $tries. It falls back to your queue worker's default (--tries=1 unless configured otherwise), so it runs once and is not retried; its $backoff and $maxExceptions are consequently inert. It does set a 120s timeout and uniqueFor = 600. This is largely harmless — the job is an idempotent scanner and the next scheduled run re-scans — but don't count on it retrying a transient failure.

Upload jobs (ProcessSingleUpload) have rate-limit-aware retry:

Queue Configuration

Jobs are dispatched to the queue specified in config/efactura.php:

Set EFACTURA_QUEUE=efactura in your .env to use a dedicated queue. This allows you to run a separate worker for e-Factura jobs:

Rate Limit Configuration

The SDK provides client-side rate limiting to stay within ANAF quotas. Upload jobs are rate-limit-aware and will delay instead of failing when quotas are exhausted. For uploads that do fail due to rate limiting (e.g., race conditions), schedule RetryRateLimitedUploads to automatically reset them.

Queue Worker

Ensure your queue worker is running:

Artisan Commands

Events

Listen to these events for custom logic:

Event Fired When Payload
TokenStored OAuth callback stores new token EfacturaToken $token
TokenRefreshed Token auto-refreshed EfacturaToken $token
InvoiceUploaded Invoice successfully uploaded EfacturaUpload $upload
InvoiceProcessed Response downloaded (success) EfacturaUpload $upload
InvoiceFailed Upload or processing failed terminally EfacturaUpload $upload, array $errors
InvoiceRateLimited Upload hit ANAF's rate limit and will be retried EfacturaUpload $upload, array $errors, ?int $retryAfterSeconds
InvoiceReceived Received invoice downloaded EfacturaMessage $message

InvoiceFailed means the pipeline has given up. It is safe to alert on. Transient rate-limit hits fire InvoiceRateLimited instead — the upload stays in the pipeline and is re-submitted automatically. Inspect $upload->failure_reason for the classification.

Example Listener

Database Schema

The package creates three tables:

efactura_tokens

Stores OAuth tokens per CUI.

access_token and refresh_token are encrypted at rest (Laravel's encrypted cast) as of v3.0.0, and are therefore readable only with the APP_KEY that wrote them — see Upgrading to v3.0. Both columns stay text: no schema change is needed, since Crypt inflates a value roughly 1.8x and ANAF's JWTs are a kilobyte or two. Nothing queries these columns by value, so encryption costs no lookups.

efactura_uploads

Tracks upload status for your invoices (polymorphic relationship).

A model has at most one upload row: (uploadable_type, uploadable_id) is unique. Re-uploading after a failure recycles that row rather than inserting another, so efacturaUpload always points at the live attempt.

The failure_reason column carries the authoritative classification of a Failed upload (rate_limited, transient, indeterminate, validation, configuration). Only rate_limited and transient are ever re-submitted automatically.

efactura_messages

Stores synced messages from ANAF (sent, received, errors).

Upload Status Flow

Reconciliation (indeterminate uploads)

Some failures leave delivery genuinely unknown: the worker died around the POST (job timeout, SIGKILL, deploy), or the transport failed ambiguously (5xx, dropped connection). ANAF may or may not have filed the document.

These are parked as Failed with failure_reason = indeterminate and are never re-submitted automatically — re-sending would risk double-filing a legal invoice. queueUpload() also refuses to recycle such a row, throwing DuplicateUploadException.

Resolving them requires a human, because ANAF's message listing carries no invoice number to match on and a stranded upload never received an index_incarcare:

Schedule SweepStaleUploads so rows stranded by a hard worker death are surfaced rather than sitting in uploading forever. Listen for InvoiceFailed to be alerted when one appears.

Logging

The SDK logs all API calls to a dedicated logging channel. Add the following channel to your config/logging.php:

You can customize the channel name via the EFACTURA_LOG_CHANNEL environment variable (defaults to efactura-sdk).

Testing

For testing, use the SDK's sandbox mode:

Troubleshooting

Token Not Found

Ensure you've completed the OAuth flow for the CUI:

Jobs Not Running

  1. Verify the scheduler is registered
  2. Check config('efactura.enabled') is true
  3. Check feature flags are enabled
  4. Review Laravel queue worker logs

Upload Failures

Check the failure_reason and errors columns in the efactura_uploads table, or listen to the InvoiceFailed event. failure_reason is the authoritative classification — never substring-match errors:

Rows with failure_reason = indeterminate are never retried — resolve them with php artisan efactura:reconcile.

Nothing Uploads, But Nothing Errors

efactura:upload and every batch job only dispatch work as of v3. Confirm a queue worker is running on config('efactura.queue'), or use php artisan efactura:upload --sync.

DecryptException: The payload is invalid.

The token-encryption migration has not run. See Upgrading to v3.0. If it has run, your APP_KEY changed — restore it, or set APP_PREVIOUS_KEYS, or re-authorise each company.

AI Assistant Integration (MCP)

This package and its SDK dependency both include MCP servers that help AI coding assistants understand the full e-Factura integration.

Setup: Add both to your AI tool's MCP configuration:

Requires Node.js 18+.

The wrapper MCP server provides these tools:

Tool Description
get-wrapper-docs Documentation topics: overview, setup, upload-pipeline, token-management, commands, migration (the v2 → v3.0 upgrade guide)
get-model-integration-guide Complete guide for integrating a model with e-Factura
get-event-reference All 7 events: TokenStored, TokenRefreshed, InvoiceUploaded, InvoiceProcessed, InvoiceFailed, InvoiceRateLimited, InvoiceReceived
get-job-reference All 9 background jobs with scheduling guidance
get-wrapper-config-reference Full configuration schema with env vars and defaults

The SDK MCP server (efactura-sdk) provides DTOs, enums, API reference, and SDK-level documentation.

License

This package is open-sourced software licensed under the Apache 2.0 License.

Credits


All versions of laravel-efactura with dependencies

PHP Build Version
Package Version
Requires php Version ^8.4
bee-coded/laravel-efactura-sdk Version ^3.0
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
illuminate/support Version ^11.0|^12.0|^13.0
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package bee-coded/laravel-efactura contains the following files

Loading the files please wait ...