rubensalban/sfec-client-php
Composer 安装命令:
composer require rubensalban/sfec-client-php
包简介
Client PHP communautaire pour l'API SFEC (Systeme de Facturation Electronique Certifiee, Republique du Congo). Couvre les modes ERP, TCC et TFC.
README 文档
README
Client PHP communautaire pour l'API SFEC (Système de Facturation Électronique Certifiée, République du Congo).
Supporte les trois modes d'intégration documentés :
- ERP en ligne — authentification par clé API (
X-API-Key) - TCC (Module de Contrôle) — authentification mTLS
- TFC (Terminal Fiscal Certifié) — authentification mTLS renforcée
Documentation officielle de l'API : https://docs.sfec.gouv.cg/
À propos de ce package
Ce package est un outil communautaire indépendant, développé par un tiers pour faciliter l'intégration de l'API SFEC dans des projets PHP / Laravel. Il n'est ni officiel, ni affilié, ni endossé par l'administration congolaise ou par les éditeurs du système SFEC. La documentation officielle reste la seule référence faisant autorité.
Version
0.x— interface du package en cours de stabilisationTant que la version
1.0.0n'est pas atteinte, les signatures de méthodes et la structure des objets exposés par ce package peuvent évoluer entre deux versions mineures (cela ne concerne que l'interface PHP du package, pas l'API SFEC elle-même).Recommandations :
- Épinglez la version exacte :
"rubensalban/sfec-client-php": "0.1.0"(et non"^0.1.0").- Consultez le
CHANGELOGavant chaque mise à jour.- À partir de la
1.0.0, l'interface du package suivra strictement semver.
Caractéristiques
- PSR-18 / PSR-17 — vous injectez le client HTTP de votre choix (Guzzle, Symfony, Buzz, ...)
- Sécurité par construction — clé API et certificats wrappés,
json_encode($client)ne fuite jamais de secret - Validation côté client — payload vérifié avant tout appel réseau, erreurs typées avec chemins précis
- Recalcul systématique des totaux — impossible d'envoyer des montants incohérents
electronic_stamp_duty: 0garanti par construction- Idempotence —
invoice_idUUID v4 généré automatiquement - Retry exponentiel sur 5xx et erreurs réseau uniquement, jamais sur 4xx
- Bridge Laravel optionnel — ServiceProvider, Facade, config publiable
- 170 tests (160 core + 10 Laravel)
Installation
Standalone (Symfony, Slim, CLI, etc.)
composer require rubensalban/sfec-client-php
Laravel (recommandé pour les apps Laravel)
composer require rubensalban/sfec-client-laravel
Auto-discovery activé : aucune intervention manuelle requise.
Requiert PHP ≥ 8.0.
Configuration
Variables d'environnement (mode ERP)
SFEC_CLIENT_BASE_URL=https://api.sfec.gouv.cg SFEC_CLIENT_API_KEY=sk_votre_cle_api SFEC_CLIENT_ENV=production
| Variable | Requis | Description |
|---|---|---|
SFEC_CLIENT_BASE_URL |
Oui | URL de base de l'API (HTTPS obligatoire, sauf localhost) |
SFEC_CLIENT_API_KEY |
Mode ERP | Clé API obtenue via le portail e-Facture |
SFEC_CLIENT_ENV |
Non | Label informatif (sandbox, production, ...) |
SFEC_CLIENT_TIMEOUT_MS |
Non | Timeout (défaut : 30000) |
SFEC_CLIENT_RETRY_MAX |
Non | Tentatives de retry (défaut : 3) |
SFEC_CLIENT_RETRY_BASE_DELAY_MS |
Non | Délai de base (défaut : 500) |
Important :
SFEC_CLIENT_BASE_URLn'est jamais codée en dur dans le package. C'est à vous de fournir l'URL exacte conforme à la documentation officielle.
Démarrage rapide
Laravel
use Sfec\Client\Laravel\Facades\Sfec; $result = Sfec::erp()->submit([ 'taxpayer_niu' => 'M987654321', 'recipient' => [ 'type' => 'business', 'name' => 'ACME SARL', 'niu' => 'P123456789', 'address' => 'Brazzaville', 'email' => 'contact@acme.cg', ], 'items' => [ [ 'designation' => 'Prestation de conseil', 'type' => 'service', 'unit_price' => 50000, 'quantity' => 2, 'tax_rate' => '18', ], ], 'payment' => [ 'method' => 'mobile_money', 'currency' => 'XAF', 'reference' => 'MM-2026-0001', ], ]); echo $result['invoiceNumber']; // "F-2026-0042" echo $result['certificationNumber']; // "CERT-XYZ..." echo $result['qrCode']; // "data:image/png;base64,..."
Ou via injection de dépendance :
use Sfec\Client\SfecClient; public function store(Request $request, SfecClient $sfec) { return $sfec->erp()->submit($request->validated()); }
Standalone (sans Laravel)
use Sfec\Client\SfecClient; use Nyholm\Psr7\Factory\Psr17Factory; use GuzzleHttp\Client as GuzzleClient; $psr17 = new Psr17Factory(); $http = new GuzzleClient(['verify' => true, 'allow_redirects' => false]); $sfec = SfecClient::create([ 'baseUrl' => 'https://api.sfec.gouv.cg', 'apiKey' => 'sk_xxx', ], $http, $psr17, $psr17); $result = $sfec->erp()->submit($invoiceInput);
Mode TCC / TFC (mTLS)
Deux étapes : bootstrap des certificats, puis utilisation.
1. Bootstrap (une fois)
use Sfec\Client\Config\ConfigLoader; use Sfec\Client\Certificates\CertificateClaimer; use Sfec\Client\Transport\HttpTransport; $config = ConfigLoader::bootstrap(['baseUrl' => 'https://api.sfec.gouv.cg']); $transport = new HttpTransport($config, $http, $psr17, $psr17); $claimer = new CertificateClaimer($transport); $credentials = $claimer->claim([ 'token' => 'token_obtenu_via_portail', 'niu' => 'M987654321', 'terminalIdentifier' => 'CAISSE-01', // optionnel ]); // IMPORTANT : reveal() expose le matériel cryptographique en clair. // À persister IMMÉDIATEMENT dans un secret store sécurisé. $secrets = $credentials->reveal(); // $secrets['signingPrivateKey'] // $secrets['encryptionMasterKey'] // $secrets['mtlsClientCertificate'] // $secrets['mtlsClientPrivateKey']
2. Utilisation après bootstrap
$sfec = SfecClient::create([ 'baseUrl' => 'https://api.sfec.gouv.cg', 'mtls' => [ 'cert' => $certPemFromSecretStore, 'key' => $keyPemFromSecretStore, ], ], $http, $psr17, $psr17); $sfec->tcc()->submit($invoiceInput); $invoices = $sfec->tcc()->list(['page' => 1, 'pageSize' => 20]); // Mode TFC (mêmes endpoints, sémantique distincte) $sfec->tfc()->submit($invoiceInput);
Configuration mTLS Laravel
SFEC_CLIENT_MTLS_CERT_PEM="-----BEGIN CERTIFICATE-----\n..." SFEC_CLIENT_MTLS_KEY_PEM="-----BEGIN PRIVATE KEY-----\n..."
Le bridge Laravel construit automatiquement un Guzzle configuré pour mTLS.
API Reference
SfecClient::create(array $options, $psr18, $psr17Factory, $psr17Factory)
| Option | Type | Description |
|---|---|---|
baseUrl |
string |
URL de l'API |
apiKey |
string |
Clé API (mode ERP) |
mtls |
array{cert, key, ca?} |
Certificats PEM (TCC/TFC) |
env |
string |
Label informatif |
timeoutMs |
int |
Timeout (défaut : 30000) |
retry |
array{max, baseDelayMs} |
Retry (défaut : [max => 3, baseDelayMs => 500]) |
hooks |
Hooks |
Callbacks de télémétrie |
$sfec->erp()->submit(array $input, ?Hooks $hooks = null): array
Soumet une facture au mode ERP. Retour :
[
'invoiceId' => string,
'invoiceNumber' => string,
'certificationNumber' => string,
'shortIdentifier' => ?string,
'qrCode' => ?string, // data URL base64 PNG
'certificationDate' => string,
'raw' => array, // réponse brute serveur
]
$sfec->erp()->list(array $params = [], ?Hooks $hooks = null): array
| Paramètre | Type | Description |
|---|---|---|
page |
int |
Page (défaut : 1) |
pageSize |
int |
Entre 1 et 20 (défaut : 10) |
invoiceType |
'salesInvoice'|'creditNote' |
Filtre |
dateStart |
string (ISO 8601) |
Borne début |
dateEnd |
string (ISO 8601) |
Borne fin |
$sfec->tcc() / $sfec->tfc()
Mêmes signatures que erp(), avec auth mTLS et endpoint list distinct (/v1/terminals/invoices).
Schéma d'input facture
Voir [docs/SCHEMA.md] (TODO) ou directement le code de src/Validators/InvoiceValidator.php.
Les totaux (subtotal, total_amount, etc.) sont toujours recalculés par le builder. Toute valeur fournie est silencieusement écrasée.
Gestion d'erreurs
Toutes les exceptions héritent de SfecException et implémentent JsonSerializable.
use Sfec\Client\Exceptions\SfecValidationException; use Sfec\Client\Exceptions\SfecHttpException; use Sfec\Client\Exceptions\SfecNetworkException; use Sfec\Client\Exceptions\SfecConfigException; try { Sfec::erp()->submit($invoice); } catch (SfecValidationException $e) { foreach ($e->getFields() as $field) { // $field['path'], $field['message'], $field['code'] } } catch (SfecHttpException $e) { if ($e->isConflict()) { /* 409 : déjà certifiée */ } if ($e->isUnauthorized()) { /* 401 */ } if ($e->isUnprocessable()) { /* 422 : validation métier serveur */ } if ($e->isServerError()) { /* 5xx, déjà retry */ } } catch (SfecNetworkException $e) { if ($e->isTimeout()) { /* timeout */ } } catch (SfecConfigException $e) { /* mauvaise configuration */ }
Sécurité
Garanties par construction
-
Clé API jamais loggable : wrappée dans
Secret.(string) $secret === '***',json_encoderenvoie"***",var_dumpmasque via__debugInfo. Lue uniquement au dernier moment viareveal()pour construire le header HTTP. -
Certificats mTLS jamais loggables :
MtlsMaterialetCredentialsmasquent tout en sérialisation. Acceptés uniquement en mémoire (string PEM), pas de chemins de fichier dans la config — empêche le path traversal. -
HTTPS obligatoire :
baseUrldoit être enhttps://. Exception :localhost/127.0.0.1/::1pour tests locaux. mTLS :verify: truefigé. -
Bodies d'erreur redactés automatiquement :
SfecHttpException::getBody()passe parRedactor::redact(). Impossible de logger une 401 qui fuite la clé. -
Hooks safe :
onRequest/onResponse/onErrorreçoivent des objets déjà redactés. -
allow_redirects: falsecôté Guzzle : pas de fuite de headerX-API-Keyvers un autre host via redirection serveur. -
Pas de path injection :
pathdoit commencer par/, sinon refus immédiat.
Recommandations utilisateur
- Ne jamais commit
.envou les fichiers de certificats. Le.gitignoredu package bloque.env,*.pem,*.key,*.crt,certs/. - Persister
$credentials->reveal()immédiatement dans un secret store (Vault, AWS Secrets Manager, GCP Secret Manager) après bootstrap.
Hooks d'observabilité
use Sfec\Client\Transport\Hooks; $hooks = new Hooks( onRequest: function (array $info) { // $info['headers']['X-API-Key'] === '***' Log::info("-> {$info['method']} {$info['url']} (tentative {$info['attempt']})"); }, onResponse: function (array $info) { Log::info("<- {$info['status']}"); }, onError: function (array $info) { // $info['error']['details']['body'] est déjà redacté Sentry::captureException($info['error']); }, ); $sfec = SfecClient::create([..., 'hooks' => $hooks], $http, $psr17, $psr17); // Hooks ponctuels (en plus du global) $sfec->erp()->submit($invoice, new Hooks(onError: fn($i) => alertOps($i)));
Recettes
Idempotence
$result = Sfec::erp()->submit([ 'invoice_id' => "INV-{$orderId}", // toujours le même pour une commande // ... ]); // Un second appel retournera 409 (SfecHttpException::isConflict())
Avoir (credit note)
Sfec::erp()->submit([ 'invoice_type' => 'creditNote', 'reference_invoice_id' => 'INV-original-id', // REQUIS // ... ]);
Builder seul (sans appel réseau)
use Sfec\Client\Builders\InvoiceBuilder; $payload = InvoiceBuilder::build($input); // Payload prêt à être envoyé ou stocké, totaux recalculés, UUID généré.
Tests
# Core composer test # Bridge Laravel cd laravel && composer test
Packages
| Package Packagist | Rôle |
|---|---|
rubensalban/sfec-client-php |
Core PHP (ce dépôt) |
rubensalban/sfec-client-laravel |
Bridge Laravel (sous-dossier laravel/) |
Licence
MIT
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-06-16