PHP code example of bee-coded / laravel-efactura-sdk

1. Go to this page and download the library: Download bee-coded/laravel-efactura-sdk library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

bee-coded / laravel-efactura-sdk example snippets


// config/efactura-sdk.php
return [
    'sandbox' => env('EFACTURA_SANDBOX', true),

    'oauth' => [
        'client_id' => env('EFACTURA_CLIENT_ID'),
        'client_secret' => env('EFACTURA_CLIENT_SECRET'),
        'redirect_uri' => env('EFACTURA_REDIRECT_URI'),
    ],

    'http' => [
        'timeout' => env('EFACTURA_TIMEOUT', 30),
        'retry_times' => env('EFACTURA_RETRY_TIMES', 3),
        'retry_delay' => env('EFACTURA_RETRY_DELAY', 5),
    ],

    'logging' => [
        'channel' => env('EFACTURA_LOG_CHANNEL', 'efactura-sdk'),
    ],
];

'efactura-sdk' => [
    'driver' => 'daily',
    'path' => storage_path('logs/efactura-sdk.log'),
    'level' => 'debug',
    'days' => 30,
],

use BeeCoded\EFacturaSdk\Facades\EFacturaSdkAuth;

// Generate authorization URL
$authUrl = EFacturaSdkAuth::getAuthorizationUrl();

// Or with custom state data
$authUrl = EFacturaSdkAuth::getAuthorizationUrl(new AuthUrlSettingsData(
    state: ['company_id' => 123, 'user_id' => 456],
    scope: 'custom-scope',
));

return redirect($authUrl);

use BeeCoded\EFacturaSdk\Facades\EFacturaSdkAuth;

public function handleCallback(Request $request)
{
    $code = $request->get('code');

    // Exchange authorization code for tokens
    $tokens = EFacturaSdkAuth::exchangeCodeForToken($code);

    // Store tokens in YOUR database
    YourTokenModel::create([
        'company_id' => $companyId,
        'access_token' => $tokens->accessToken,
        'refresh_token' => $tokens->refreshToken,
        'expires_at' => $tokens->expiresAt,
    ]);
}

use BeeCoded\EFacturaSdk\Facades\EFacturaSdkAuth;

$newTokens = EFacturaSdkAuth::refreshAccessToken($storedRefreshToken);

// Update stored tokens
$tokenModel->update([
    'access_token' => $newTokens->accessToken,
    'refresh_token' => $newTokens->refreshToken,
    'expires_at' => $newTokens->expiresAt,
]);

use BeeCoded\EFacturaSdk\Services\ApiClients\EFacturaClient;
use BeeCoded\EFacturaSdk\Data\Auth\OAuthTokensData;

// Retrieve your stored tokens
$storedTokens = YourTokenModel::where('company_id', $companyId)->first();

// Create tokens DTO
$tokens = new OAuthTokensData(
    accessToken: $storedTokens->access_token,
    refreshToken: $storedTokens->refresh_token,
    expiresAt: $storedTokens->expires_at,
);

// Create client
$client = EFacturaClient::fromTokens($vatNumber, $tokens);

use BeeCoded\EFacturaSdk\Data\Invoice\UploadOptionsData;
use BeeCoded\EFacturaSdk\Enums\StandardType;

// Basic upload
$result = $client->uploadDocument($xmlContent);

// With options
$result = $client->uploadDocument($xmlContent, new UploadOptionsData(
    standard: StandardType::UBL,
    extern: false,      // External invoice (non-Romanian supplier)
    selfBilled: false,  // Self-billed invoice (autofactura)
));

// B2C upload (to consumers)
$result = $client->uploadB2CDocument($xmlContent);

// Check result
if ($result->isSuccessful()) {
    $uploadId = $result->indexIncarcare;
    // Store uploadId for status checking
}

$status = $client->getStatusMessage($uploadId);

if ($status->isReady()) {
    $downloadId = $status->idDescarcare;
    // Document is ready for download
} elseif ($status->isInProgress()) {
    // Still processing, check again later
} elseif ($status->isFailed()) {
    // Processing failed
    $errors = $status->errors;
}

$download = $client->downloadDocument($downloadId);

// Save to file
$download->saveTo('/path/to/invoice.zip');

// Or get content directly
$zipContent = $download->content;
$contentType = $download->contentType;

use BeeCoded\EFacturaSdk\Data\Invoice\ListMessagesParamsData;
use BeeCoded\EFacturaSdk\Enums\MessageFilter;

// List messages from last 30 days
$messages = $client->getMessages(new ListMessagesParamsData(
    cif: '12345678',
    days: 30,  // 1-60 days allowed
    filter: MessageFilter::InvoiceSent,  // Optional: T, P, E, R
));

foreach ($messages->mesaje as $message) {
    echo $message->id;
    echo $message->dataCreare;
    echo $message->tip;
}

use BeeCoded\EFacturaSdk\Data\Invoice\PaginatedMessagesParamsData;

// Using timestamps (milliseconds)
$messages = $client->getMessagesPaginated(new PaginatedMessagesParamsData(
    cif: '12345678',
    startTime: $startTimestampMs,
    endTime: $endTimestampMs,
    page: 1,
    filter: MessageFilter::InvoiceReceived,
));

// Or create from Carbon dates
$messages = $client->getMessagesPaginated(
    PaginatedMessagesParamsData::fromDateRange(
        cif: '12345678',
        startDate: now()->subDays(30),
        endDate: now(),
        page: 1,
    )
);

// Pagination info
$messages->totalPages;
$messages->totalRecords;
$messages->currentPage;
$messages->hasNextPage();

use BeeCoded\EFacturaSdk\Enums\DocumentStandardType;

$validation = $client->validateXml($xmlContent, DocumentStandardType::FACT1);

if ($validation->valid) {
    // XML is valid
} else {
    // Validation errors
    $errors = $validation->errors;
    $details = $validation->details;
}

use BeeCoded\EFacturaSdk\Enums\DocumentStandardType;

// Convert without validation
$pdfContent = $client->convertXmlToPdf($xmlContent, DocumentStandardType::FACT1);

// Convert with validation first
$pdfContent = $client->convertXmlToPdf($xmlContent, DocumentStandardType::FACT1, validate: true);

file_put_contents('invoice.pdf', $pdfContent);

$result = $client->verifySignature($signedXmlContent);

if ($result->valid) {
    // Signature is valid
}

$client = EFacturaClient::fromTokens($vatNumber, $tokens);

// Make API calls
$result = $client->uploadDocument($xml);
$status = $client->getStatusMessage($uploadId);

// IMPORTANT: Check if tokens were refreshed
if ($client->wasTokenRefreshed()) {
    $newTokens = $client->getTokens();

    // You MUST persist ALL new token values
    $storedTokens->update([
        'access_token' => $newTokens->accessToken,
        'refresh_token' => $newTokens->refreshToken,  // Critical! Old one is now invalid
        'expires_at' => $newTokens->expiresAt,
    ]);
}

public function uploadInvoice(string $xml, Company $company): UploadResponseData
{
    $tokens = $this->getTokensForCompany($company);
    $client = EFacturaClient::fromTokens($company->vat_number, $tokens);

    try {
        $result = $client->uploadDocument($xml);

        return $result;
    } finally {
        // Always check for token refresh, even on errors
        if ($client->wasTokenRefreshed()) {
            $this->persistTokens($company, $client->getTokens());
        }
    }
}

use BeeCoded\EFacturaSdk\Exceptions\RateLimitExceededException;

try {
    $result = $client->uploadDocument($xml);
} catch (RateLimitExceededException $e) {
    // Rate limit exceeded
    $remaining = $e->remaining;              // 0 (no calls remaining)
    $retryAfter = $e->retryAfterSeconds;     // Seconds until reset
    $message = $e->getMessage();             // Human-readable message

    // Wait and retry, or queue for later
    Log::warning("Rate limit hit: {$message}. Retry in {$retryAfter}s");
}

$rateLimiter = $client->getRateLimiter();

// Check global limit (per minute)
$globalQuota = $rateLimiter->getRemainingQuota('global');
// ['limit' => 500, 'remaining' => 485, 'resetsIn' => 45]  // seconds until reset

// Check per-CUI limits
$listQuota = $rateLimiter->getRemainingQuota('simple_list', $vatNumber);
// ['limit' => 750, 'remaining' => 742, 'resetsIn' => 43200]  // seconds until reset

// Check per-message limits
$statusQuota = $rateLimiter->getRemainingQuota('status', $uploadId);
// ['limit' => 50, 'remaining' => 48, 'resetsIn' => 86400]

$downloadQuota = $rateLimiter->getRemainingQuota('download', $downloadId);
// ['limit' => 5, 'remaining' => 3, 'resetsIn' => 86400]

$rateLimiter = app(\BeeCoded\EFacturaSdk\Services\RateLimiter::class);

if ($rateLimiter->isEnabled()) {
    // Rate limiting is active
}

use BeeCoded\EFacturaSdk\Facades\UblBuilder;
use BeeCoded\EFacturaSdk\Data\Invoice\InvoiceData;
use BeeCoded\EFacturaSdk\Data\Invoice\PartyData;
use BeeCoded\EFacturaSdk\Data\Invoice\AddressData;
use BeeCoded\EFacturaSdk\Data\Invoice\InvoiceLineData;

$invoice = new InvoiceData(
    invoiceNumber: 'INV-2024-001',
    issueDate: now(),
    dueDate: now()->addDays(30),
    currency: 'RON',
    paymentIban: 'RO49AAAA1B31007593840000',

    supplier: new PartyData(
        registrationName: 'Supplier Company SRL',
        companyId: 'RO12345678',
        address: new AddressData(
            street: 'Str. Exemplu Nr. 1',
            city: 'Bucuresti',
            postalZone: '010101',
            county: 'Sector 1',  // Auto-sanitized to RO-B format
        ),
        registrationNumber: 'J40/1234/2020',
        isVatPayer: true,
    ),

    customer: new PartyData(
        registrationName: 'Customer Company SRL',
        companyId: 'RO87654321',
        address: new AddressData(
            street: 'Str. Client Nr. 2',
            city: 'Cluj-Napoca',
            postalZone: '400001',
            county: 'Cluj',  // Auto-sanitized to RO-CJ
        ),
        isVatPayer: true,
    ),

    lines: [
        new InvoiceLineData(
            name: 'Servicii consultanta',
            quantity: 10,
            unitPrice: 100.00,
            taxAmount: 190.00,   // Pre-computed: 10 * 100.00 * 0.19
            taxPercent: 19,
            unitCode: 'HUR',     // Hours
            description: 'Consultanta IT luna ianuarie',
        ),
        new InvoiceLineData(
            name: 'Licenta software',
            quantity: 1,
            unitPrice: 500.00,
            taxAmount: 95.00,    // Pre-computed: 1 * 500.00 * 0.19
            taxPercent: 19,
            unitCode: 'C62',     // Each
        ),
    ],
);

// Generate UBL 2.1 XML
$xml = UblBuilder::generateInvoiceXml($invoice);

use BeeCoded\EFacturaSdk\Enums\InvoiceTypeCode;

$creditNote = new InvoiceData(
    invoiceNumber: 'CN-2024-001',
    issueDate: now(),
    currency: 'RON',
    invoiceTypeCode: InvoiceTypeCode::CreditNote,
    precedingInvoiceNumber: 'INV-2024-001',  // BT-25: reference to the original invoice

    supplier: $supplier,
    customer: $customer,

    lines: [
        new InvoiceLineData(
            name: 'Returned product',
            quantity: -2,       // Negative = items being credited/returned
            unitPrice: 100.00,
            taxAmount: -38.00,  // Negative — sign follows quantity
            taxPercent: 19,
        ),
    ],
);

$xml = UblBuilder::generateInvoiceXml($creditNote);

$creditNote = new InvoiceData(
    invoiceNumber: 'CN-2024-002',
    issueDate: now(),
    currency: 'RON',
    invoiceTypeCode: InvoiceTypeCode::CreditNote,
    precedingInvoiceNumber: 'INV-2024-050',

    supplier: $supplier,
    customer: $customer,

    lines: [
        // Crediting 3 returned items (negative → becomes positive for ANAF)
        new InvoiceLineData(
            name: 'Returned product',
            quantity: -3,
            unitPrice: 150.00,
            taxAmount: -85.50,  // Pre-computed: -3 * 150.00 * 0.19 — sign follows quantity
            taxPercent: 19,
        ),
        // Reversing a discount that was on the original invoice (positive → becomes negative for ANAF)
        new InvoiceLineData(
            name: 'Discount reversal',
            quantity: 1,
            unitPrice: 50.00,
            taxAmount: 9.50,    // Pre-computed: 1 * 50.00 * 0.19
            taxPercent: 19,
        ),
    ],
);

$xml = UblBuilder::generateInvoiceXml($creditNote);

// Line-level calculations
$line = new InvoiceLineData(
    name: 'Product',
    quantity: 5,
    unitPrice: 100.00,
    taxAmount: 95.00,  // Pre-computed VAT for this line
    taxPercent: 19,
);

$line->getLineTotal();        // 500.00 (quantity * unitPrice)
$line->getTaxAmount();        // 95.00 (returns the pre-computed taxAmount)
$line->getLineTotalWithTax(); // 595.00

// Invoice-level calculations
$invoice->getTotalExcludingVat(); // Sum of all line totals
$invoice->getTotalVat();          // Sum of all per-line taxAmount values
$invoice->getTotalIncludingVat(); // Total with VAT

// County names are normalized
'Cluj' -> 'RO-CJ'
'Judetul Cluj' -> 'RO-CJ'
'BUCURESTI' -> 'RO-B'

// Bucharest sectors are extracted
'Sector 3' -> 'RO-B' (with sector in address)
'Sectorul 1, Str. Exemplu' -> extracts sector

// Diacritics are handled
'Brașov' -> 'RO-BV'
'Constanța' -> 'RO-CT'

use BeeCoded\EFacturaSdk\Facades\AnafDetails;

// Single company lookup
$result = AnafDetails::getCompanyData('RO12345678');

if ($result->success) {
    $company = $result->first();

    echo $company->name;              // Company name
    echo $company->cui;               // CUI without RO prefix
    echo $company->getVatNumber();    // CUI with RO prefix
    echo $company->address;           // General address
    echo $company->registrationNumber; // J40/1234/2020

    // VAT status
    $company->isVatPayer;
    $company->vatRegistrationDate;
    $company->vatDeregistrationDate;

    // Special regimes
    $company->isSplitVat;      // Split VAT payment
    $company->isRtvai;         // VAT on collection

    // Status
    $company->isActive();      // Not inactive and not deregistered (radiat)
    $company->isInactive;
    $company->isDeregistered;

    // Trade-registry status (stare_inregistrare)
    $company->isRegistered();           // status is "INREGISTRAT"
    $company->registrationStatus;       // RegistrationStatus enum (Registered|Deregistered|Unknown)
    $company->registrationStatusRaw;    // e.g. "RADIERE din data 29.03.2024"
    $company->registrationStatusDate;   // Carbon date parsed from the status string
    $company->registrationDate;         // Fiscal registration date (data_inregistrare)

    // RO e-Factura registry
    $company->isRegisteredInEFactura;   // enrolled in Registrul RO e-Factura
    $company->eFacturaRegistrationDate;

    // VAT registration history
    $company->vatPeriods;               // VatPeriodData[] (perioade_TVA)

    // Detailed addresses
    $company->headquartersAddress;     // AddressData object
    $company->fiscalDomicileAddress;   // AddressData object
    $company->getPrimaryAddress();     // Returns headquarters or fiscal
}

// Batch lookup (up to 100 companies — ANAF v9 limit)
$result = AnafDetails::batchGetCompanyData([
    'RO12345678',
    'RO87654321',
    '11223344',  // RO prefix is optional
]);

foreach ($result->companies as $company) {
    // Process each company
}

// Check for not found
foreach ($result->notFound as $cui) {
    echo "Company not found: $cui";
}

// Validate VAT code format
$isValid = AnafDetails::isValidVatCode('RO12345678'); // true

use BeeCoded\EFacturaSdk\Support\Validators\VatNumberValidator;

VatNumberValidator::isValid('RO12345678');  // true
VatNumberValidator::isValid('12345678');    // true (2-10 digits)
VatNumberValidator::isValid('invalid');     // false

VatNumberValidator::normalize('12345678');  // 'RO12345678'
VatNumberValidator::stripPrefix('RO12345678'); // '12345678'

use BeeCoded\EFacturaSdk\Support\Validators\CnpValidator;

CnpValidator::isValid('1234567890123'); // Validates checksum
CnpValidator::isValid('0000000000000'); // true (special ANAF case)

use BeeCoded\EFacturaSdk\Support\DateHelper;

// Format for ANAF API
DateHelper::formatForAnaf(now());           // '2024-01-15'
DateHelper::formatForAnaf('2024-01-15');    // '2024-01-15'

// Timestamps in milliseconds (for paginated messages)
DateHelper::toTimestamp(now());             // 1705312800000

// Day range for queries
[$start, $end] = DateHelper::getDayRange('2024-01-15');
// $start = 1705269600000 (00:00:00.000)
// $end = 1705355999999 (23:59:59.999)

// Validate days parameter
DateHelper::isValidDaysParameter(30);  // true (1-60 allowed)
DateHelper::isValidDaysParameter(100); // false

StandardType::UBL   // 'UBL' - UBL 2.1 format
StandardType::CN    // 'CN' - Credit Note
StandardType::CII   // 'CII' - Cross Industry Invoice
StandardType::RASP  // 'RASP' - Response

DocumentStandardType::FACT1  // 'FACT1' - Invoice
DocumentStandardType::FCN    // 'FCN' - Credit Note

MessageFilter::InvoiceSent     // 'T' - Sent invoices
MessageFilter::InvoiceReceived // 'P' - Received invoices
MessageFilter::InvoiceErrors   // 'E' - Errors
MessageFilter::BuyerMessage    // 'R' - Buyer messages

// Invoice document types (generates <Invoice> XML)
InvoiceTypeCode::CommercialInvoice  // '380' - Standard commercial invoice
InvoiceTypeCode::CorrectedInvoice   // '384' - Corrected invoice
InvoiceTypeCode::SelfBilledInvoice  // '389' - Self-billed invoice (autofactura)
InvoiceTypeCode::AccountingInvoice  // '751' - Invoice for accounting purposes

// Credit note (generates <CreditNote> XML)
InvoiceTypeCode::CreditNote         // '381' - Credit note

// Helper methods
$type->isCreditNote();  // true for 381
$type->isInvoice();     // true for 380, 384, 389, 751

use BeeCoded\EFacturaSdk\Exceptions\AuthenticationException;
use BeeCoded\EFacturaSdk\Exceptions\ValidationException;
use BeeCoded\EFacturaSdk\Exceptions\ApiException;
use BeeCoded\EFacturaSdk\Exceptions\RateLimitExceededException;
use BeeCoded\EFacturaSdk\Exceptions\XmlParsingException;

try {
    $result = $client->uploadDocument($xml);
} catch (AuthenticationException $e) {
    // OAuth token invalid or expired (and refresh failed)
    // User needs to re-authenticate
} catch (RateLimitExceededException $e) {
    // Rate limit exceeded
    $retryAfter = $e->retryAfterSeconds;  // Seconds until limit resets
    // Queue for later or wait
} catch (ValidationException $e) {
    // Input validation failed (empty XML, invalid parameters)
    $message = $e->getMessage();
} catch (ApiException $e) {
    // API call failed
    $statusCode = $e->statusCode;
    $details = $e->details;
} catch (XmlParsingException $e) {
    // Failed to parse XML response from ANAF
}

use BeeCoded\EFacturaSdk\Contracts\AnafAuthenticatorInterface;
use BeeCoded\EFacturaSdk\Contracts\AnafDetailsClientInterface;

// In your test
$this->mock(AnafAuthenticatorInterface::class, function ($mock) {
    $mock->shouldReceive('exchangeCodeForToken')
        ->andReturn(new OAuthTokensData(
            accessToken: 'test-token',
            refreshToken: 'test-refresh',
            expiresAt: now()->addHour(),
        ));
});
bash
php artisan vendor:publish --tag=efactura-sdk-config

base = round(100.00 / 1.19, 2) = 84.03
vat  = 100.00 - 84.03 = 15.97