tudorr89 / fgo-php-api-sdk
PHP client for the FGO Invoicing/Billing API v7.0 — create invoices, manage articles, query nomenclatures, and more. Ships with Laravel auto-discovery.
Requires
- php: >=8.1
- ext-json: *
- guzzlehttp/guzzle: ^7.0
Requires (Dev)
- illuminate/config: ^10.0|^11.0|^12.0
- illuminate/container: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^10.0
Suggests
- illuminate/support: Required to use the bundled Laravel service provider and Fgo facade (^10.0|^11.0|^12.0)
README
A fully-typed PHP client for the FGO Invoicing/Billing API v7.0.
Create and manage invoices, query nomenclatures, list articles, and more — with clean DTOs and Guzzle under the hood.
Requirements
Installation
composer require tudorr89/fgo-php-api-sdk
Quick Start
use FgoApi\Client; use FgoApi\Enums\Environment; use FgoApi\Types\AddressClient; use FgoApi\Types\InvoiceLine; $client = new Client( codUnic: 'YOUR_CUI', privateKey: 'YOUR_PRIVATE_KEY', platformUrl: 'https://your-app.com', environment: Environment::Test, ); // Create an invoice $invoice = $client->invoices()->create( series: 'BV', currency: 'RON', invoiceType: 'Factura', clientData: new AddressClient( name: 'Ionescu Popescu', country: 'RO', county: 'Bucuresti', type: 'PF', ), lines: [ new InvoiceLine( name: 'Servicii Consultanta', quantity: 2, unit: 'ORE', vatRate: 19, unitPrice: 150.00, ), ], ); echo "Invoice: {$invoice->series}{$invoice->number}\n"; echo "PDF: {$invoice->pdfLink}\n";
Laravel
The package ships with auto-discovered service provider and facade — no manual registration needed.
Configure
php artisan vendor:publish --tag=fgo-config
Add to .env:
FGO_COD_UNIC=YOUR_CUI FGO_PRIVATE_KEY=YOUR_PRIVATE_KEY FGO_PLATFORM_URL=https://your-app.com FGO_ENVIRONMENT=test # or "production" FGO_TIMEOUT=20
Use it
use FgoApi\Laravel\Fgo; use FgoApi\Types\AddressClient; use FgoApi\Types\InvoiceLine; $invoice = Fgo::invoices()->create( series: 'BV', currency: 'RON', invoiceType: 'Factura', clientData: new AddressClient(name: 'Acme SRL'), lines: [new InvoiceLine(name: 'Service', quantity: 1, unit: 'BUC', vatRate: 19, unitPrice: 100)], );
Or via DI:
use FgoApi\Client; public function __construct(private readonly Client $fgo) {}
Multi-tenant / credentials from the database
When each tenant (or merchant, or user) has their own FGO credentials, point the
package at a resolver instead of reading from .env. Implement
FgoApi\Laravel\Contracts\CredentialsResolver:
namespace App\Fgo; use App\Models\Tenant; use FgoApi\Laravel\Contracts\CredentialsResolver; class TenantCredentialsResolver implements CredentialsResolver { public function resolve(?string $key = null): array { $tenant = $key ? Tenant::findOrFail($key) : Tenant::current(); return [ 'cod_unic' => $tenant->fgo_cod_unic, 'private_key' => decrypt($tenant->fgo_private_key), 'platform_url' => $tenant->platform_url, 'environment' => $tenant->fgo_environment, // "test" or "production" ]; } }
Register it in config/fgo.php:
'resolver' => \App\Fgo\TenantCredentialsResolver::class,
Then:
use FgoApi\Laravel\Fgo; // Current tenant — resolver is called with null Fgo::invoices()->create(...); // Specific tenant — resolver is called with the key, result cached for the request Fgo::for($tenantId)->invoices()->create(...); // Ad-hoc credentials (e.g. testing, one-off jobs) Fgo::make([ 'cod_unic' => '...', 'private_key' => '...', 'platform_url' => 'https://my-app.test', 'environment' => 'test', ])->invoices()->getStatus('001', 'BV'); // After rotating credentials Fgo::forget($tenantId);
A closure is also accepted if you don't want a dedicated class:
// in a service provider config(['fgo.resolver' => fn (?string $key) => Tenant::resolve($key)->fgoConfig()]);
Authentication
All requests are signed with an SHA-1 hash. The Hash helper provides the correct calculation for each endpoint category:
use FgoApi\Hash; // Invoice creation — includes client name Hash::forInvoiceCreate('CUI', 'PRIVATE_KEY', 'Client Name'); // Invoice operations (print, cancel, etc.) — includes invoice number Hash::forInvoiceOperation('CUI', 'PRIVATE_KEY', '001'); // Articles, nomenclatures, warehouses — no extra data Hash::forArticle('CUI', 'PRIVATE_KEY');
The Client handles hashing automatically — you never need to call these directly.
API Reference
Invoices
| Method | Endpoint | Description |
|---|---|---|
invoices()->create(...) |
POST /factura/emitere |
Create and emit a new invoice |
invoices()->print($num, $serie) |
POST /factura/print |
Generate PDF download link |
invoices()->getStatus($num, $serie) |
POST /factura/getstatus |
Get invoice value and paid amount |
invoices()->cancel($num, $serie) |
POST /factura/anulare |
Cancel (keeps in history) |
invoices()->delete($num, $serie) |
POST /factura/stergere |
Permanently delete |
invoices()->reverse($num, $serie) |
POST /factura/stornare |
Reverse / credit note |
invoices()->addPayment(...) |
POST /factura/incasare |
Record a payment (Premium+) |
invoices()->deletePayment($num, $serie) |
POST /factura/stergereincasare |
Delete a payment |
invoices()->addTrackingNumber(...) |
POST /factura/awb |
Attach courier AWB |
invoices()->listAssociated($num, $serie) |
POST /factura/listfacturiasociate |
List linked invoices (Enterprise) |
// Full create example $result = $client->invoices()->create( series: 'BV', currency: 'RON', invoiceType: 'Factura', clientData: new AddressClient( name: 'SC Example SRL', fiscalCode: 'RO12345678', email: 'contact@example.com', phone: '0712345678', country: 'RO', county: 'Cluj', locality: 'Cluj-Napoca', address: 'Str. Principala, Nr. 10', type: 'PJ', ), lines: [ new InvoiceLine( name: 'Dezvoltare Software', quantity: 1, unit: 'BUC', vatRate: 19, unitPrice: 5000.00, description: 'Modul facturare — luna aprilie', ), ], number: null, issueDate: date('Y-m-d'), dueDate: null, checkDuplicate: false, vatOnCollection: false, ); // $result->number, $result->series, $result->pdfLink, $result->paymentLink, $result->stockInfo[]
Nomenclatures
| Method | Returns |
|---|---|
nomenclatures()->countries() |
All countries |
nomenclatures()->counties() |
All counties |
nomenclatures()->vatRates() |
VAT rates |
nomenclatures()->banks() |
Banks |
nomenclatures()->paymentTypes() |
Payment types |
nomenclatures()->invoiceTypes() |
Invoice types |
nomenclatures()->clientTypes() |
Client types (PF/PJ) |
nomenclatures()->localities('Bucuresti') |
Localities by county code |
$types = $client->nomenclatures()->invoiceTypes(); // [ { name: "Normal", value: "Factura" }, { name: "Simplified", value: "FacturaSimplificata" } ]
Articles
| Method | Description |
|---|---|
articles()->list($page, $perPage) |
Paginated article list (Enterprise) |
articles()->get($code) |
Single article by account code |
articles()->getList(array $codes) |
Multiple articles, max 30 — deprecated |
articles()->modifiedArticles($hoursBack, $hoursTo) |
Articles modified in time window (Enterprise) |
$result = $client->articles()->list(page: 1, perPage: 50); // $result->total, $result->articles[] — each Article has name, unitPrice, stock, barcode, etc.
Warehouses
$warehouses = $client->warehouses()->list(); // { code: "WH001", name: "Main Warehouse" }
Environments
use FgoApi\Enums\Environment; // Test (UAT) new Client(..., environment: Environment::Test); // Production new Client(..., environment: Environment::Production); // Custom URL new Client(..., environment: 'https://custom-fgo.example.com/v1');
Exception Handling
All exceptions extend FgoApi\Exceptions\FgoApiException:
| Exception | Trigger |
|---|---|
FgoApiException |
Generic API error (non-success response) |
AuthenticationException |
HTTP 401 — invalid credentials |
RateLimitException |
HTTP 429 — rate limit hit |
NotFoundException |
HTTP 404 — resource not found |
HttpException |
Other HTTP errors (includes status code + body) |
ValidationException |
Validation errors |
try { $invoice = $client->invoices()->create(...); } catch (ValidationException $e) { // 400 / `Errors` map from API print_r($e->getErrors()); } catch (AuthenticationException $e) { // 401 / 403 — wrong CUI or private key } catch (RateLimitException $e) { sleep(max(1, $e->getRetryAfter())); // ...retry } catch (FgoApiException $e) { // All other API errors }
Rate Limits
The API enforces per-endpoint rate limits. The client does not automatically retry — implement your own retry logic as needed:
| Endpoint | Limit |
|---|---|
| Invoice create / payment | 1 req/sec, 15s timeout |
| Articles | 1 req/5 sec |
| Standard endpoints | No explicit limit |
Development
git clone https://github.com/tudorr89/fgo-php-api-sdk cd fgo-php-api-sdk composer install # Static analysis composer analyse # Run tests composer test
License
MIT. See LICENSE.