facturino / facturino-php
Facturino PHP SDK — French e-invoicing API client
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.5
README
Official PHP client for the Facturino API — French e-invoicing made simple.
Requirements
- PHP 8.1+
- ext-curl
- ext-json
Installation
composer require facturino/facturino-php
Quick start
require 'vendor/autoload.php'; \Facturino\Facturino::setApiKey('fac_test_xxx'); // Create a customer $customer = \Facturino\Customer::create([ 'name' => 'ACME Corp', 'type' => 'company', 'email' => 'billing@acme.com', 'siret' => '73282932000074', 'address' => [ 'line1' => '42 rue des Acacias', 'postalCode' => '75001', 'city' => 'Paris', 'country' => 'FR', ], ]); // Create and finalize an invoice $invoice = \Facturino\Invoice::create([ 'customerId' => $customer['id'], 'buyer' => [ 'companyName' => 'Acme SAS', 'siret' => '55208131766522', 'address' => ['line1' => '10 rue de la Paix', 'postalCode' => '75002', 'city' => 'Paris', 'country' => 'FR'], ], 'lines' => [[ 'description' => 'Consulting', 'quantity' => '1', // decimal string 'unit' => 'flat_rate', 'unitPrice' => 10000, // 100.00 EUR in centimes 'vatRate' => 2000, // 20.00% in centipercent 'vatCode' => 'S', // 'vatexCode' => 'VATEX-FR-261', // optional: specific exemption code (BT-121) ]], 'dates' => [ 'issued' => '2026-01-15', 'due' => '2026-02-15', ], 'payment' => [ 'terms' => 'Paiement à 30 jours', 'termsDays' => 30, 'method' => 'transfer', 'latePaymentRate' => '10.00', 'collectionFee' => '40.00', ], ]); $invoice = \Facturino\Invoice::finalize($invoice['id']); // Send to PA (Plateforme Agreee) \Facturino\Invoice::send($invoice['id']); // One-shot alternative — finalize (and optionally deliver) in the create call: // \Facturino\Invoice::create([..., 'autoFinalize' => true, // 'autoSend' => ['email' => true, 'pa' => true]]); // Record a payment $payment = \Facturino\Payment::create($invoice['id'], [ 'amount' => 12000, // 120.00 EUR (100 HT + 20 TVA) 'method' => 'transfer', 'paidAt' => '2026-02-10', ]); // Cancel a payment (kept as "cancelled" for the audit trail) \Facturino\Payment::cancel($invoice['id'], $payment['id']);
Auto-pagination
// Iterate over all invoices automatically foreach (\Facturino\Invoice::all(['limit' => 10]) as $invoice) { echo $invoice['id'] . ' ' . $invoice['status'] . "\n"; } // Or access the first page directly $collection = \Facturino\Invoice::all(['limit' => 25]); $firstPage = $collection->getData(); $hasMore = $collection->hasMore();
Resources
| Resource | Class | API endpoint |
|---|---|---|
| Invoice | \Facturino\Invoice |
/v1/invoices |
| Payment | \Facturino\Payment |
/v1/invoices/:id/payments |
| Customer | \Facturino\Customer |
/v1/customers |
| Product | \Facturino\Product |
/v1/products |
| Quote | \Facturino\Quote |
/v1/quotes |
| Credit Note | \Facturino\CreditNote |
/v1/credit-notes |
| Event | \Facturino\Event |
/v1/events |
| Webhook Endpoint | \Facturino\WebhookEndpoint |
/v1/webhook-endpoints |
| Recurring Invoice | \Facturino\RecurringInvoice |
/v1/recurring-invoices |
| Company | \Facturino\Company |
/v1/companies |
| Export | \Facturino\Export |
/v1/exports |
| E-reporting | \Facturino\Ereporting |
/v1/ereporting/declarations |
| Job | \Facturino\Job |
/v1/jobs |
| Sandbox | \Facturino\Sandbox |
/v1/sandbox |
| Reference | \Facturino\Reference |
/v1/reference, /v1/pa-providers |
| Health | \Facturino\Health |
/v1/health |
Public token endpoints — the recipient-facing portals (
/pay/:token,/portal/:token,/quote-portal/:token) are intentionally not exposed by the SDK: they are opened by the end recipient through a hosted page, not called with an API key.
Quotes
// Clone a quote as a new draft (mirrors Invoice::clone) $draft = \Facturino\Quote::clone('quo_xxx'); // Convert an accepted quote to a draft invoice $invoice = \Facturino\Quote::convert('quo_xxx');
Filtering and expanding
The list and retrieve endpoints accept extra query parameters that pass straight through to the API:
// Invoices issued from a given quote $invoices = \Facturino\Invoice::all(['convertedFrom' => 'quo_xxx']); // Inline related resources on a single invoice. `expand` is // comma-separated and accepts `customer`, `items.product` and // `credit_notes`. With `credit_notes` the response gains // `expanded.credit_notes` (array) and `expanded.net_balance` (string). $invoice = \Facturino\Invoice::retrieve('inv_xxx', [ 'expand' => 'customer,credit_notes', ]); // Product filters: q (name prefix), category, active $products = \Facturino\Product::all([ 'q' => 'consult', 'category' => 'services', 'active' => true, ]);
Contacts and roles
Each entry of a customer's contacts[] may carry a role of billing,
technical or main. The billing contact receives invoices by default.
\Facturino\Customer::create([ 'name' => 'ACME Corp', 'type' => 'company', 'contacts' => [ ['name' => 'Compta', 'email' => 'compta@acme.com', 'role' => 'billing'], ['name' => 'IT', 'email' => 'it@acme.com', 'role' => 'technical'], ], ]);
Credit note numbering
creditNoteSettings.numberingMode controls how credit notes are
numbered: separate (default — their own number series) or unified
(credit notes share the invoice number series).
\Facturino\Company::update('comp_xxx', [ 'creditNoteSettings' => ['numberingMode' => 'unified'], ]);
Webhook verification
Verify incoming webhooks using HMAC-SHA256 with timing-safe comparison:
$payload = file_get_contents('php://input'); $sigHeader = $_SERVER['HTTP_FACTURINO_SIGNATURE']; $endpointSecret = 'whsec_xxx'; // From your webhook endpoint try { $event = \Facturino\Webhook::constructEvent($payload, $sigHeader, $endpointSecret); switch ($event['type']) { case 'invoice.finalized': $invoice = $event['data']; // Handle finalized invoice break; case 'invoice.paid': // Handle payment break; } http_response_code(200); echo json_encode(['received' => true]); } catch (\Facturino\Exception\InvalidRequestException $e) { http_response_code(400); echo json_encode(['error' => $e->getMessage()]); }
Idempotency
Pass an idempotency key on POST requests to safely retry:
$invoice = \Facturino\Invoice::create( ['customerId' => 'cus_xxx', 'buyer' => [...], 'lines' => [...], 'dates' => [...], 'payment' => [...]], 'idem_unique_request_id_123' );
Error handling
use Facturino\Exception\AuthenticationException; use Facturino\Exception\InvalidRequestException; use Facturino\Exception\RateLimitException; use Facturino\Exception\ApiException; try { $invoice = \Facturino\Invoice::retrieve('inv_nonexistent'); } catch (AuthenticationException $e) { // Invalid API key (401) echo 'Auth error: ' . $e->getMessage(); } catch (RateLimitException $e) { // Rate limit exceeded (429) — SDK retries automatically up to 3 times echo 'Rate limited: ' . $e->getMessage(); } catch (InvalidRequestException $e) { // Client error (400-499) echo 'Error: ' . $e->getMessage(); echo 'Code: ' . $e->getErrorCode(); echo 'Param: ' . $e->getParam(); echo 'Hint: ' . $e->getHint(); } catch (ApiException $e) { // Server error (500+) — SDK retries automatically up to 3 times echo 'API error: ' . $e->getMessage(); }
Amounts and rates
The API uses integers to avoid floating-point precision issues:
- Amounts: integer centimes.
10000= 100.00 EUR - VAT rates: integer centipercent.
2000= 20.00%
// 150.00 EUR HT with 20% VAT $item = [ 'description' => 'Service', 'quantity' => '1', 'unit' => 'flat_rate', 'unitPrice' => 15000, // 150.00 EUR 'vatRate' => 2000, // 20.00% 'vatCode' => 'S', ];
Async jobs
Some operations (PDF generation, FEC export) return a job object:
$result = \Facturino\Invoice::getPdf('inv_xxx'); if (isset($result['url'])) { // PDF already exists — download from signed URL $pdfUrl = $result['url']; } else { // Async generation — poll the job $jobId = $result['id']; do { sleep(2); $job = \Facturino\Job::retrieve($jobId); } while ($job['status'] === 'pending'); if ($job['status'] === 'completed') { $pdfUrl = $job['url']; } }
Sandbox
Use test-mode API keys (fac_test_*) for development:
\Facturino\Facturino::setApiKey('fac_test_xxx'); // Reset test data and load fixtures \Facturino\Sandbox::resetData(); // Simulate PA status changes \Facturino\Sandbox::simulateStatus('inv_xxx', 'deposited'); \Facturino\Sandbox::simulateStatus('inv_xxx', 'approved');
Configuration
// Override API base URL (for testing or proxying) \Facturino\Facturino::setApiBase('https://localhost:5001/api');
Development
git clone https://github.com/facturino/facturino-php.git
cd facturino-php
composer install
vendor/bin/phpunit
License
MIT. See LICENSE.