PHPackages                             pristavu/laravel-anaf - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. pristavu/laravel-anaf

ActiveLibrary[API Development](/categories/api)

pristavu/laravel-anaf
=====================

Laravel package for interacting with ANAF web services

v0.3.0(6mo ago)3261↓50%[2 PRs](https://github.com/pristavu/laravel-anaf/pulls)MITPHPPHP ^8.3|^8.4CI passing

Since Oct 11Pushed 4mo agoCompare

[ Source](https://github.com/pristavu/laravel-anaf)[ Packagist](https://packagist.org/packages/pristavu/laravel-anaf)[ Docs](https://github.com/pristavu/laravel-anaf)[ GitHub Sponsors]()[ RSS](/packages/pristavu-laravel-anaf/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (4)Dependencies (19)Versions (7)Used By (0)

laravel-anaf
============

[](#laravel-anaf)

[![Latest Version on Packagist](https://camo.githubusercontent.com/e43654f34a440ab1763c746c8bc61965652ff18a00550bfb40cc2fcfb5dd8f84/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f70726973746176752f6c61726176656c2d616e61662e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pristavu/laravel-anaf)[![GitHub Tests Action Status](https://camo.githubusercontent.com/1878ade712f795764d5dfd312edb592f941debf654d7322cf1a5be683d628cae/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f70726973746176752f6c61726176656c2d616e61662f72756e2d74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/pristavu/laravel-anaf/actions?query=workflow%3Arun-tests+branch%3Amain)[![GitHub Code Style Action Status](https://camo.githubusercontent.com/bfeddac6e36af6d6383b306638729aa553b0786f939014c750d1678876a5db42/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f70726973746176752f6c61726176656c2d616e61662f6669782d7068702d636f64652d7374796c652d6973737565732e796d6c3f6272616e63683d6d61696e266c6162656c3d636f64652532307374796c65267374796c653d666c61742d737175617265)](https://github.com/pristavu/laravel-anaf/actions?query=workflow%3A%22Fix+PHP+code+style+issues%22+branch%3Amain)[![Total Downloads](https://camo.githubusercontent.com/e75cfcbfba55a1c2498678a85fbd985cb471fdfd4df5f679fe3ae73a297a89a1/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f70726973746176752f6c61726176656c2d616e61662e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/pristavu/laravel-anaf)

This package makes it easy to work with ANAF services in Laravel applications.

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require pristavu/laravel-anaf
```

You can publish and run the migrations with:

```
php artisan vendor:publish --tag="anaf-migrations"
php artisan migrate
```

You can publish the config file with:

```
php artisan vendor:publish --tag="anaf-config"
```

What you can do with this package
---------------------------------

[](#what-you-can-do-with-this-package)

- OAuth2 - authentication/authorization.
    - get authorization url
    - retrieve access token
    - refresh access token
- eFactura - client/connector (Oauth2 token required) for interacting with the eFactura API.
    - retrieve messages/invoices (regular and paginated)
    - download invoices as zip
    - extract invoice xml, signature and invoice dto from zip
    - validate xml invoices
    - upload xml invoices (B2B, B2C)
    - convert xml invoices to PDF
    - check message status
- taxPayer - client/connector (public API / No need for Oauth2) for interacting with the taxpayer API.
    - vat status check and other taxpayer information (by cif)
    - balance sheet retrieval (by year)

---

Usage
-----

[](#usage)

You can use the package via the Anaf facade:

```
use Pristavu\Anaf\Facades\Anaf;

Anaf::oauth(...);
Anaf::eFactura(...);
Anaf::taxPayer(...);
```

or via helper function:

```
use function Pristavu\Anaf\anaf;

anaf()->oauth(...);
anaf()->eFactura(...);
anaf()->taxPayer(...);
```

---

OAuth2 usage
------------

[](#oauth2-usage)

Add the following to your `.env` file:

```
ANAF_CLIENT_ID=your-client-id
ANAF_CLIENT_SECRET=your-client-secret
ANAF_REDIRECT_URI=http://your-callback-url/auth/anaf/callback

```

or update config/anaf.php with your environment variables

```
[
  'oauth' => [
        'client_id' => env('ANAF_CLIENT_ID'),
        'client_secret' => env('ANAF_CLIENT_SECRET'),
        'redirect_uri' => env('ANAF_REDIRECT_URI', 'http://localhost/auth/anaf/callback'),
  ],
  ...
]

// you can pass the config values directly when initializing the oauth2 authenticator
$connector = Pristavu\Anaf\Facades\Anaf::oauth(
    clientId: 'your-client-id',
    clientSecret: 'your-client-secret',
    redirectUri: 'http://your-callback-url/auth/anaf/callback'
);
```

### Redirect to authorization server

[](#redirect-to-authorization-server)

```
// In redirect controller method eg: '/auth/anaf/redirect'

public function __invoke(): RedirectResponse
{
    $connector = Pristavu\Anaf\Facades\Anaf::oauth();
    $state = Str::random(32);
    // store the state in a scoped session (L12) or cache for later validation
    session()->cache()->put('anaf_oauth_state', $state, now()->addMinutes(5));

    $authorizationUrl = $connector->getAuthorizationUrl(
        state: $state,
        additionalQueryParameters: ['token_content_type' => 'jwt']
    );

    return redirect()->away($authorizationUrl);
}
```

### Token retrieval on auth callback

[](#token-retrieval-on-auth-callback)

```
// In callback controller method eg: '/auth/anaf/callback'

public function __invoke(Request $request): ?RedirectResponse
{
    $code = $request->get('code');
    $state = $request->get('state');
    $expectedState = session()->cache()->get('anaf_oauth_state');

    if($request->has('error')){
        abort(400, 'Error from authorization server: '.$request->get('error'));
    }

    if (!$code || !$state) {
        abort(400, 'Invalid state or code');
    }

    try {
        $connector = Pristavu\Anaf\Facades\Anaf::oauth();
        $authenticator = $connector->getAccessToken($code, $state, $expectedState);
    } catch (\Exception $e) {
        abort(400, 'Failed to get access token: ' . $e->getMessage());
    }

    // you can store the serialized token in session or cache for later use
    session()->cache()->put('anaf_oauth_authenticator', $authenticator->serialize());

    // or store the token in database or other persistent storage
    Pristavu\Anaf\Models\AccessToken::create([
      'user_id' => auth()->id(),
      'provider' => Provider::ANAF,
      'access_token' => $authenticator->getAccessToken(),
      'refresh_token' => $authenticator->getRefreshToken(),
      'expires_at' => $authenticator->getExpiresAt(),
    ]);

    return redirect('/home'); // or wherever you want to redirect the user
}
```

### Refreshing existing access token

[](#refreshing-existing-access-token)

```
// initialize the oauth2 authenticator
$connector = \Pristavu\Anaf\Facades\Anaf::oauth();

// get serialized authenticator from session or cache
$serialized = session()->cache()->get('anaf_oauth_authenticator');
$authenticator = \Saloon\Http\Auth\AccessTokenAuthenticator::unserialize($serialized);

// or retrieve it from database access token model
$accessToken = Pristavu\Anaf\Models\AccessToken::query()->where('user_id', auth()->id())->first();
$authenticator = $accessToken->authenticator();

if ($authenticator->hasExpired()) {
    // We'll refresh the access token which will return a new authenticator
    $authenticator = $connector->refreshAccessToken($authenticator);

    // Store the new token serialized in session or cache
    session()->cache()->put('anaf_oauth_authenticator', $authenticator->serialize());

    // or update the existing token model in database
    $accessToken->update([
        'token' => $authenticator->getAccessToken(),
        'refresh_token' => $authenticator->getRefreshToken(),
        'expires_at' => $authenticator->getExpiresAt(),
    ]);
}
```

---

---

Efactura usage (Oauth2 required)
--------------------------------

[](#efactura-usage-oauth2-required)

### Initializing the client

[](#initializing-the-client)

```
// You need a valid access token to initialize the efactura connector

// retrieve access token from database or other storage
$accessToken = Pristavu\Anaf\Models\AccessToken::query()->where('user_id', auth()->id())->first()->access_token;

// initialize the efactura connector / client
$connector = Pristavu\Anaf\Facades\Anaf::eFactura(accessToken: $accessToken);
```

### Switching to test mode (sandbox)

[](#switching-to-test-mode-sandbox)

```
// Live (production) endpoint is used by default and is forced for certain operations eg: validateInvoice, convertInvoice
// Test mode can be used only for uploading, messages ,messagesPaginated, uploadInvoice, messageStatus, downloadInvoice
$connector->inTestMode();
```

### Debugging Request &amp; Response

[](#debugging-request--response)

```
// enable logging of requests and responses
// die will stop execution after logging the response
$connector->debug();  // $connector->debug(die: true);

// Separate Debuggers
$connector->debugRequest(); // connector->debugRequest(die: true);
$connector->debugResponse(); // connector->debugResponse(die: true);
```

### Caching

[](#caching)

- For certain operations like downloading invoices, cache is enabled by default to avoid hitting ANAF download limit rate (10 downloads/day for same $downloadId).

```
// If you want to disable caching for all operations you can do it like this:
$connector->disableCaching()->downloadInvoice(downloadId: $downloadId);
// or for invalidating cached content before downloading again:
$connector->invalidateCache()->downloadInvoice(downloadId: $downloadId);
```

### Retrieving messages/invoices

[](#retrieving-messagesinvoices)

- You need to use paginated messages if you expect more than 500 messages/invoices for specified period.

```
// days - number of days between 1 and 60
// type can be one of: MessageType::{SENT/RECEIVED/ERROR/MESSAGE} -  if none provided, all types are retrieved

// eg: retrieve sent messages/invoices for cif 123456 from last 60 days
$response = $connector->messages(cif: 123456, days: 60, type: MessageType::SENT); // returns a MessagesResponse

if($response->success){
    $response->messages->each(function(Message $message){
        // do something with $message
        $message->cif; // the cif associated with the message/invoice
        $message->upload_id; // the upload id for the message/invoice
        $message->download_id; // the download id for the message/invoice
        $message->type; // the type of message/invoice
        $message->created_at; // Carbon instance of creation date
        $message->description; // description/details of the message/invoice
    });
} else {
    // handle error
    $response->error;

// eg: retrieve any type of messages/invoices for cif 123456 from last 10 days
$response = $connector->messages(cif: 123456, days: 10);
```

### Retrieving paginated messages/invoices

[](#retrieving-paginated-messagesinvoices)

- Somehow even if the paginated response should provide 500 messages per page and total messages are less than 500, messages are divided into two pages (eg: total 95 messages are returned as 2 pages, first with 49 and second with 46 messages).

```
// period - interval must not exceed 60 days
// type can be one of: MessageType::{SENT/RECEIVED/ERROR/MESSAGE} -  if none provided, all types are retrieved

// retrieve sent messages/invoices for cif 123456 from last 60 days (paginated page 1)
$period = \Carbon\CarbonPeriod::create(now()->subDays(60), now());
$response = $connector->messagesPaginated(cif: 123456, period: $period, page: 1, type: MessageType::SENT); // returns a PaginatedMessagesResponse

if($response->success){
    $response->messages->each(function(Message $message){
        // do something with $message
        $message->cif; // the cif associated with the message/invoice
        $message->upload_id; // the upload id for the message/invoice
        $message->download_id; // the download id for the message/invoice
        $message->type; // the type of message/invoice
        $message->created_at; // Carbon instance of creation date
        $message->description; // description/details of the message/invoice
    });

    // paginated response metadata
    $response->meta->total; // number of messages in selected period
    $response->meta->per_page; // messages per page (default 500)
    $response->meta->current_page; // current page number
    $response->meta->last_page; // last page number
} else {
    // handle error
    $response->error;
}

// or using the toPeriod method
// retrieve any messages/invoices  for cif 123456 from last 10 days (paginated page 2)
$period = now()->subDays(10)->toPeriod(now());
$response = $connector->messagesPaginated(cif: 123456, period: $period, page: 2);
```

### Downloading messages/invoices

[](#downloading-messagesinvoices)

```
$downloadId = 987654321; // the download_id of the message/invoice
$response = $connector->downloadInvoice(downloadId: $downloadId);

if($response->success){
    // save the zip content to a file
    Storage::disk('private')->put("/invoices/{$downloadId}.zip",$response->content);

    // optionally you can extract files from the zip message/invoice using the Extract helper without saving archive to disk
    $message = Pristavu\Anaf\Support\Extract::from($response->content);

    // get xml invoice, signature and dto invoice objects
    $message->xmlInvoice();
    $message->signature();
    // dto invoice will be null if unzipping a non invoice message (eg: xml response error message)
    $message->dtoInvoice();
}
else {
    // handle error
    $response->error; // array of download errors
}
```

### Validating messages/invoices

[](#validating-messagesinvoices)

```
$xml = Storage::disk('private')->get('invoices/12345/987654321.xml');
// optionally you can pass the full path to xml
$xml = Storage::disk('private')->path('invoices/12345/987654321.xml');

$response = $connector->validateInvoice(
    xml: $xml,
    standard: \Pristavu\Anaf\Enums\DocumentStandard::FCN, // optional, default is FACT1
);

if($response->success){
   // do something with $response
} else {
   // handle errors
   $response->errors; // array of validation errors
}
```

### Uploading an invoice

[](#uploading-an-invoice)

```
$xml = Storage::disk('private')->get('invoices/12345/987654321.xml');
// optionally you can pass the full path to xml
$xml = Storage::disk('private')->path('invoices/12345/987654321.xml');
$response = $connector->uploadInvoice(
    cif: 123456,
    xml: $xml,
    standard: \Pristavu\Anaf\Enums\XmlStandard::UBL, // optional, default is UBL
    isExternal:  false, // optional, default is false
    isSelfInvoice: false, // optional, default is false
    isLegalEnforcement: false // optional, default is false
);

if($response->success){
    // do something with $response
    $response->upload_id; // the upload id of the invoice

}
else {
    // handle error
    $response->error;
}
```

### Converting invoice to PDF

[](#converting-invoice-to-pdf)

```
$xml = Storage::disk('private')->get('invoices/12345/987654321.xml');
// optionally you can pass the full path to xml
$xml = Storage::disk('private')->path('invoices/12345/987654321.xml');
$response = $connector->convertInvoice(xml: $xml, standard: DocumentStandard::FACT1, withoutValidation: true);

if($response->success){
    // save the pdf content to a file
    Storage::disk('private')->put("/invoices/12345/987654321.pdf",$response->content);
}
```

### Message status

[](#message-status)

```
$uploadId = 987654321; // the message id to check status for
$response = $connector->messageStatus(uploadId: $uploadId);

if($response->success){
   // do something with $response
   $response->status
   $response->download_id; // download id if available
}
else {
   // handle error
   $response->error;
}
```

---

TaxPayer usage
--------------

[](#taxpayer-usage)

### Initializing the client

[](#initializing-the-client-1)

```
// initialize the taxPayer connector / client
$connector = Pristavu\Anaf\Facades\Anaf::taxPayer();
```

### Checking VAT status

[](#checking-vat-status)

```
$vatStatus = $connector->vatStatus(cif: 123456, date: '2023-12-31');
```

### Retrieving balance sheet

[](#retrieving-balance-sheet)

```
$balanceSheet = $connector->balanceSheet(cif: 123456, year: 2022);
```

---

Testing
-------

[](#testing)

```
composer test
```

### Using the fake client

[](#using-the-fake-client)

You can use the mock client to simulate API responses during testing in your laravel application.

```
use Saloon\Http\Faking\MockClient;
use Requests\Efactura\MessagesRequest;

test('my test', function () {
    // arrange
    $mockClient = new MockClient([
        MessagesRequest::class => MockResponse::make(
        body: [
            'mesaje' => [
                [
                    'data_creare' => 202508291153,
                    'cif' => 123456,
                    'id_solicitare' => 999999999,
                    'detalii' => 'Factura cu id_incarcare=999999999 emisa de cif_emitent=123456 pentru cif_beneficiar=987654',
                    'tip' => 'FACTURA TRIMISA',
                    'id' => 888888888,
                ],
                ...
            ]
        ],
        status: 200
        ),
    ]);

    // act
    $connector = Anaf::eFactura(accessToken: 'TEST_TOKEN');
    $messages = $connector->withMockClient($mockClient)->messages(cif: 123456, days: 60);

    // assert
    expect($messages)->toBeArray();
});
```

---

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security Vulnerabilities
------------------------

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

Credits
-------

[](#credits)

- [Andrei Pristavu](https://github.com/pristavu)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

39

—

LowBetter than 86% of packages

Maintenance72

Regular maintenance activity

Popularity18

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~3 days

Total

4

Last Release

203d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/2875b9ef72b09212f72a878c4eb30c237ad1adf460ddd1a1f376ef7d4090f83e?d=identicon)[pristavu](/maintainers/pristavu)

---

Top Contributors

[![pristavu](https://avatars.githubusercontent.com/u/4123729?v=4)](https://github.com/pristavu "pristavu (16 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

---

Tags

laravellaravel-anafeFacturaAndrei Pristavuanaf-phpANAF Romania

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/pristavu-laravel-anaf/health.svg)

```
[![Health](https://phpackages.com/badges/pristavu-laravel-anaf/health.svg)](https://phpackages.com/packages/pristavu-laravel-anaf)
```

###  Alternatives

[codebar-ag/laravel-docuware

DocuWare integration with Laravel

1221.1k](/packages/codebar-ag-laravel-docuware)[harris21/laravel-fuse

Circuit breaker for Laravel queue jobs. Protect your workers from cascading failures.

3786.5k](/packages/harris21-laravel-fuse)[codebar-ag/laravel-zammad

Zammad integration with Laravel

106.1k](/packages/codebar-ag-laravel-zammad)[simplestats-io/laravel-client

Client for SimpleStats!

4515.5k](/packages/simplestats-io-laravel-client)[njoguamos/laravel-plausible

A laravel package for interacting with plausible analytics api.

208.8k](/packages/njoguamos-laravel-plausible)[ntanduy/cloudflare-d1-database

Easy configuration and setup for D1 Database connections in Laravel.

215.4k](/packages/ntanduy-cloudflare-d1-database)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
