facturino/facturino-php

Facturino PHP SDK — French e-invoicing API client

Maintainers

Package info

github.com/facturino/facturino-php

Homepage

pkg:composer/facturino/facturino-php

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-08-05 09:07 UTC

This package is auto-updated.

Last update: 2026-08-05 09:47:47 UTC


README

Official PHP client for the Facturino API — French e-invoicing made simple.

Tests Latest Version PHP Version License

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.