rubensalban/sfec-client-php 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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 stabilisation

Tant que la version 1.0.0 n'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 CHANGELOG avant 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: 0 garanti par construction
  • Idempotenceinvoice_id UUID 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_URL n'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

  1. Clé API jamais loggable : wrappée dans Secret. (string) $secret === '***', json_encode renvoie "***", var_dump masque via __debugInfo. Lue uniquement au dernier moment via reveal() pour construire le header HTTP.

  2. Certificats mTLS jamais loggables : MtlsMaterial et Credentials masquent 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.

  3. HTTPS obligatoire : baseUrl doit être en https://. Exception : localhost / 127.0.0.1 / ::1 pour tests locaux. mTLS : verify: true figé.

  4. Bodies d'erreur redactés automatiquement : SfecHttpException::getBody() passe par Redactor::redact(). Impossible de logger une 401 qui fuite la clé.

  5. Hooks safe : onRequest/onResponse/onError reçoivent des objets déjà redactés.

  6. allow_redirects: false côté Guzzle : pas de fuite de header X-API-Key vers un autre host via redirection serveur.

  7. Pas de path injection : path doit commencer par /, sinon refus immédiat.

Recommandations utilisateur

  • Ne jamais commit .env ou les fichiers de certificats. Le .gitignore du 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
  • 点击次数: 2
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-06-16

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固