Architecture Scell.io
Version : 1.0 Derniere mise a jour : 2026-03-03 Audience : Developpeurs integrateurs, contributeurs internes, CTO/DSI evaluateurs
Table des matieres
- 1. Vue d'ensemble
- 2. Architecture Backend
- 3. Architecture Frontend
- 4. Architecture Multi-Tenant
- 5. Architecture Fiscale (NF525)
- 6. Integrations Externes
- 7. Decisions d'Architecture (ADR)
1. Vue d'ensemble
Scell.io est une plateforme B2B micro-SaaS de facturation electronique (Factur-X/UBL/CII) et de signature electronique simple (eIDAS EU-SES). L'architecture suit trois principes fondamentaux : API-first, multi-tenant et event-driven.
1.1 Structure Monorepo
Le projet est organise en monorepo avec trois composants principaux :
scell.io/
backend/ -> Laravel 12 API (PHP 8.2+)
frontend/ -> React 19 + TypeScript SPA
superpdp-agent/ -> MCP Server Agent (Node.js)
assets/ -> Brand assets (logos, icones)
docs/ -> Documentation technique
nginx.conf.example -> Configuration reverse proxy1.2 Diagramme d'architecture globale
graph TB
subgraph Clients
SPA["React 19 SPA<br/>scell.io"]
SDK["SDK / API directe<br/>Developpeurs tiers"]
WIDGET["Widget Onboarding<br/>Sites partenaires"]
MCP_CLIENT["Agents IA<br/>MCP Protocol"]
end
subgraph "Scell.io Platform"
subgraph "Backend Laravel 12"
API["API REST<br/>api.scell.io"]
HORIZON["Laravel Horizon<br/>Queue Worker"]
SCHEDULER["Task Scheduler<br/>Cron Jobs"]
MCP_SERVER["MCP Server<br/>54 outils"]
end
subgraph "Data Layer"
PG["PostgreSQL 17<br/>Base principale"]
REDIS["Redis 7.4<br/>Cache / Sessions / Queues"]
S3["Scaleway S3<br/>Documents / PDF"]
end
end
subgraph "Services Externes"
OPENAPI["OpenAPI.com<br/>Signatures eIDAS"]
SUPERPDP["SuperPDP<br/>Facturation PDP"]
BULKGATE["BulkGate<br/>SMS OTP"]
STRIPE["Stripe<br/>Paiements"]
INSEE["INSEE Sirene<br/>Verification SIRET"]
VIES["VIES UE<br/>Verification TVA"]
TSA["FreeTSA<br/>Horodatage RFC3161"]
end
SPA --> API
SDK --> API
WIDGET --> API
MCP_CLIENT --> MCP_SERVER
API --> PG
API --> REDIS
API --> S3
HORIZON --> PG
HORIZON --> REDIS
API --> OPENAPI
API --> SUPERPDP
API --> BULKGATE
API --> STRIPE
API --> INSEE
API --> VIES
HORIZON --> TSA
OPENAPI -.->|Webhooks| API
SUPERPDP -.->|Webhooks| API
STRIPE -.->|Webhooks| API1.3 Stack technique
| Couche | Technologie | Version | Role |
|---|---|---|---|
| Backend | Laravel | 12 | Framework API REST |
| ORM | Eloquent | Built-in | Couche d'acces donnees |
| Database | PostgreSQL | 17 | Stockage principal (UUID natif, JSONB) |
| Cache/Queue | Redis | 7.4 | Sessions, cache, file de taches |
| Queue Worker | Laravel Horizon | Latest | Traitement asynchrone |
| Auth | Sanctum + Spatie Permission | Latest | Authentification multi-mode |
| Frontend | React | 19 | Interface utilisateur SPA |
| Build | Vite | 7 | Bundler et dev server |
| Admin UI | Refine | v5 | Framework CRUD admin |
| Composants | Ant Design | 5 | Bibliotheque de composants |
| Routage client | React Router | 7 | Navigation SPA |
| API Docs | L5-Swagger | Latest | Generation OpenAPI |
| MCP | Anthropic MCP | 2024-11-05 | Integration agents IA |
1.4 Principes architecturaux
- API-first : toute fonctionnalite est d'abord exposee via l'API REST. Le frontend et les SDK sont des consommateurs au meme titre que les clients externes.
- Multi-tenant : isolation stricte des donnees par
tenant_idavec verification a chaque couche (middleware, scope Eloquent, policies). - Event-driven : les operations longues (generation Factur-X, appels signature, webhooks) sont traitees de maniere asynchrone via Laravel Horizon.
- Immutabilite fiscale : conformite NF525 avec un grand livre append-only, chaine de hachage SHA256, et horodatage RFC3161.
- Sandbox-first : chaque fonctionnalite dispose d'un mode sandbox avec des services mock pour les tests sans cout.
2. Architecture Backend
2.1 Couches applicatives
Le backend suit une architecture en couches classique avec un pattern Service Layer pour isoler la logique metier des controleurs.
graph TB
subgraph "Couche HTTP"
MW["Middleware<br/>(18 middleware custom)"]
CTRL["Controllers<br/>(28 controllers)"]
REQ["Form Requests<br/>(Validation)"]
RES["API Resources<br/>(Serialisation)"]
end
subgraph "Couche Metier"
SVC["Services<br/>(Logique metier)"]
OBS["Observers<br/>(Evenements modele)"]
JOBS["Jobs<br/>(Traitement async)"]
NOTIF["Notifications<br/>(Email, SMS)"]
end
subgraph "Couche Donnees"
MDL["Models Eloquent<br/>(41 modeles)"]
TRAITS["Traits<br/>(HasTenantScope, HasUuids)"]
SCOPES["Scopes<br/>(forTenant, active)"]
end
subgraph "Couche Persistance"
PG["PostgreSQL 17"]
REDIS["Redis 7.4"]
S3["S3 Storage"]
end
MW --> CTRL
CTRL --> REQ
CTRL --> SVC
CTRL --> RES
SVC --> MDL
SVC --> JOBS
OBS --> SVC
MDL --> TRAITS
MDL --> SCOPES
MDL --> PG
JOBS --> REDIS
SVC --> S32.2 Pattern Service Layer
La logique metier est encapsulee dans des services dedies, jamais dans les controleurs. Chaque service a une responsabilite unique.
Services principaux :
| Service | Responsabilite |
|---|---|
InvoiceNumberingService | Numerotation sequentielle par tenant/sub-tenant |
HorstekoFacturXGenerator | Generation Factur-X / UBL / CII |
FiscalLedgerService | Grand livre fiscal immutable |
FiscalAnchoringService | Horodatage RFC3161 TSA |
FiscalClosingService | Clotures quotidiennes/mensuelles/annuelles |
FecExportService | Export FEC (Fichier des Ecritures Comptables) |
OnboardingService | Workflow d'onboarding B2B |
TenantBillingService | Facturation consolidee des tenants |
OpenAPIClient | Integration OpenAPI.com (signatures) |
SuperPDPService | Integration SuperPDP (facturation) |
SmsService | Integration BulkGate (SMS OTP) |
Exemple de pattern Service Layer :
// Controller : delegation au service
class TenantInvoiceController extends Controller
{
public function store(StoreInvoiceRequest $request, string $subTenantId)
{
$invoice = app(InvoiceNumberingService::class)
->getNextNumber($request->tenant->id, $subTenantId);
// Le controleur orchestre, le service execute
return new TenantInvoiceResource($invoice);
}
}
// Service : logique metier isolee
class InvoiceNumberingService
{
public function getNextNumber(
string $tenantId,
?string $subTenantId,
?int $year = null,
?int $month = null
): string {
// Gestion atomique de la sequence
// Formats: YYYYNNNNN, NNNNNN, PREFIXNNNNNNSUFFIX, YYMMx{N}
}
}2.3 Pattern Observer
Les observers Eloquent interceptent les evenements du cycle de vie des modeles pour appliquer les regles metier de maniere transversale.
| Observer | Modele | Responsabilite |
|---|---|---|
InvoiceObserver | Invoice | Immutabilite fiscale, enregistrement audit, creation entree fiscale |
CreditNoteObserver | CreditNote | Immutabilite fiscale, blocage suppression si non-brouillon |
Mecanisme d'immutabilite :
// Dans Invoice::updating()
// Les champs fiscaux sont verrouilles une fois le statut != 'draft'
const IMMUTABLE_FISCAL_FIELDS = [
'total_ht', 'total_ttc', 'total_tax', 'invoice_number',
'issue_date', 'seller_name', 'seller_siret', 'seller_address',
'buyer_name', 'buyer_siret', 'buyer_address', 'currency',
'user_id', 'company_id',
];
// Toute tentative de modification est :
// 1. Bloquee (RuntimeException)
// 2. Journalisee dans fiscal_audit_log2.4 Pattern Circuit Breaker
Le service SuperPDP implemente un circuit breaker pour prevenir les defaillances en cascade lors d'indisponibilite du service externe.
stateDiagram-v2
[*] --> CLOSED
CLOSED --> OPEN: Seuil d'echecs atteint
OPEN --> HALF_OPEN: Timeout de recuperation expire
HALF_OPEN --> CLOSED: Appel reussi
HALF_OPEN --> OPEN: Appel echoue
CLOSED: Fonctionnement normal
CLOSED: Compteur d'echecs actif
OPEN: Appels bloques
OPEN: Reponse fallback immediate
HALF_OPEN: Test de recuperation
HALF_OPEN: 1 seul appel autoriseConfiguration :
// config/superpdp.php
'circuit_breaker' => [
'failure_threshold' => 5, // Nombre d'echecs avant ouverture
'recovery_timeout' => 60, // Secondes avant test de recuperation
],2.5 Systeme de queues (Horizon)
Laravel Horizon supervise le traitement asynchrone des taches.
Jobs principaux :
| Job | Declencheur | Timeout | Description |
|---|---|---|---|
ProcessInvoice | Creation facture | 120s | Generation Factur-X, audit trail, entree fiscale, webhooks |
ProcessSignature | Creation signature | 120s | Appel OpenAPI.com, audit trail, webhooks |
SendWebhook | Evenement metier | 30s | Envoi webhook avec retry exponentiel |
ProcessSuperPDPWebhook | Webhook entrant | 120s | Mise a jour statut facture/avoir |
ProcessIncomingInvoiceWebhook | Webhook entrant | 120s | Reception facture fournisseur |
SyncIncomingInvoicesJob | Cron quotidien | 300s | Synchronisation factures entrantes |
Commandes planifiees :
| Commande | Frequence | Description |
|---|---|---|
fiscal:daily-closing | Quotidien 00:05 UTC | Cloture fiscale journaliere |
fiscal:monthly-closing | Mensuel | Cloture fiscale mensuelle |
fiscal:integrity-check | Quotidien 02:00 UTC | Verification integrite chaine |
fiscal:check-version-change | Quotidien 03:00 UTC | Detection changement version logiciel |
invoices:generate-monthly | 1er du mois 01:00 UTC | Facturation tenant consolidee |
superpdp:sync-statuses | Toutes les 30 min | Synchronisation statuts SuperPDP |
2.6 Middleware Stack
18 middleware personnalises organisent le pipeline de traitement des requetes.
graph LR
subgraph "Authentification"
A1["ValidateApiKey<br/>X-API-Key"]
A2["TenantApiKeyMiddleware<br/>X-Tenant-Key"]
A3["ValidatePartnerKey<br/>X-API-Key"]
A4["VerifySuperPDPSignature<br/>HMAC-SHA256"]
end
subgraph "Isolation Tenant"
B1["EnsureTenantIsolation"]
B2["SubTenantMiddleware"]
B3["ResolveDatabaseConnection"]
end
subgraph "Balance & Rate Limit"
C1["CheckBalance"]
C2["TenantBalanceMiddleware"]
C3["ApiRateLimiter"]
C4["TenantRateLimiter"]
C5["OnboardingRateLimiter"]
C6["McpRateLimiter"]
end
subgraph "Fiscal NF525"
D1["FiscalKillSwitchGuard"]
D2["FiscalPeriodGuard"]
D3["FiscalScopeGuard<br/>fiscal:read/write/admin"]
D4["FiscalAccessLogger"]
end
A2 --> B1 --> B2 --> C2 --> D1 --> D2 --> D43. Architecture Frontend
3.1 Structure des composants
Le frontend est une SPA React 19 utilisant Refine v5 pour les fonctionnalites CRUD admin et Ant Design 5 comme bibliotheque de composants.
graph TB
subgraph "Application React 19"
APP["App.tsx<br/>ConfigProvider + Refine"]
subgraph "Pages Publiques"
HOME["Home"]
PRICING["Pricing"]
DOCS["Documentation"]
FAQ["FAQ"]
SDK["SDK"]
LEGAL["Pages legales<br/>CGU, CGV, RGPD"]
end
subgraph "Pages Authentifiees"
DASH["Dashboard"]
COMP["Companies"]
KEYS["API Keys"]
BAL["Balance"]
WH["Webhooks"]
end
subgraph "Pages Admin"
ADMIN["AdminDashboard"]
USERS["UsersList"]
CREDITS["CreditStats"]
OA_SETTINGS["OpenAPISettings"]
end
subgraph "Pages Tenant"
T_DASH["TenantDashboard"]
T_BILL["TenantBilling"]
T_SUB["SubTenantsList"]
T_KYB["KybDocuments"]
end
subgraph "Onboarding"
ONB_START["Start"]
ONB_VERIFY["Verify SIRET/TVA"]
ONB_DOCS["Documents KYB"]
ONB_DONE["Complete"]
end
end
subgraph "Providers"
AUTH["authProvider<br/>Sanctum Bearer Token"]
DATA["dataProvider<br/>REST API"]
T_AUTH["tenantAuthProvider<br/>X-Tenant-Key"]
T_DATA["tenantDataProvider<br/>REST API Tenant"]
end
subgraph "Contexts"
CTX_THEME["ThemeContext<br/>Dark/Light"]
CTX_ENV["EnvironmentContext<br/>Sandbox/Production"]
CTX_TENANT["TenantContext<br/>Tenant courant"]
CTX_ONB["OnboardingContext<br/>Etat du flux"]
end
APP --> AUTH
APP --> DATA
APP --> T_AUTH
APP --> T_DATA
APP --> CTX_THEME
APP --> CTX_ENV3.2 Integration Refine v5
Refine v5 fournit les hooks CRUD et la gestion de l'authentification :
dataProvider: traduit les operations CRUD en appels REST versapi.scell.io/api/v1authProvider: gere login/logout/register via Sanctum (Bearer Token)tenantDataProvider: meme pattern mais avec headerX-Tenant-Key- Resources declarees : companies, invoices, signatures, webhooks, api-keys, balance
3.3 Structure du routage
/ -> Home (landing)
/pricing -> Pricing
/documentation -> Documentation API
/sdk -> SDK info
/faq -> FAQ
/contact -> Contact
/about -> A propos
/changelog -> Notes de version
/legal/* -> Pages legales
/login -> Connexion
/register -> Inscription
/dashboard -> Dashboard utilisateur
/dashboard/companies -> Gestion entreprises
/dashboard/api-keys -> Gestion cles API
/dashboard/balance -> Solde et transactions
/dashboard/webhooks -> Gestion webhooks
/admin/* -> Pages administration
/tenant/* -> Pages tenant multi-tenant
/onboarding/* -> Flux d'onboarding partenaire3.4 Gestion d'etat
L'application utilise les Context React pour la gestion d'etat globale, sans bibliotheque externe (pas de Redux, Zustand, etc.) :
| Context | Perimetre | Donnees |
|---|---|---|
ThemeContext | Global | Mode dark/light, variables CSS |
EnvironmentContext | Global | Sandbox ou production |
TenantContext | Tenant | Tenant courant, liste tenants, switch |
OnboardingContext | Onboarding | Session, progression, erreurs |
3.5 Internationalisation
Le frontend supporte le francais et l'anglais via i18next et react-i18next :
src/i18n/locales/
en/ -> common.json, dashboard.json, landing.json, pricing.json, tenant.json
fr/ -> common.json, dashboard.json, landing.json, pricing.json, tenant.json3.6 Widget d'onboarding embarquable
Un widget autonome (frontend/packages/onboarding-widget/) permet aux partenaires d'integrer le flux d'onboarding dans leurs propres sites :
- Technologie : TypeScript pur (pas de React), bundle Vite
- Etapes : Start -> SIRET -> Verification TVA -> Documents KYB -> Complete
- Integration :
<script src="scell-onboarding.js"></script>+new ScellOnboarding({...})
4. Architecture Multi-Tenant
4.1 Modele de donnees hierarchique
L'architecture multi-tenant suit une hierarchie a 4 niveaux :
graph TB
PARTNER["Partner<br/>Partenaire integrateur<br/>(ex: cabinet comptable)"]
TENANT["Tenant<br/>Entreprise cliente<br/>(ex: PME)"]
SUBTENANT["SubTenant<br/>Sous-entite<br/>(ex: filiale, client du tenant)"]
COMPANY["Company<br/>Entite juridique<br/>(SIRET, TVA)"]
INVOICE["Invoice / CreditNote<br/>Documents"]
PARTNER -->|1:N| TENANT
TENANT -->|1:N| SUBTENANT
TENANT -->|1:N| COMPANY
SUBTENANT -->|1:N| COMPANY
COMPANY -->|1:N| INVOICERelations :
- Un Partner (cabinet comptable, ERP) onboarde plusieurs Tenants
- Un Tenant gere ses SubTenants (ses propres clients) et ses Companies
- Chaque Company possede des Invoices, CreditNotes et Signatures
4.2 Strategies d'isolation
L'isolation des donnees est appliquee a trois niveaux :
Niveau 1 -- Middleware : chaque requete est authentifiee et le tenant est resolu avant d'atteindre le controleur.
sequenceDiagram
participant Client
participant TenantApiKeyMiddleware
participant EnsureTenantIsolation
participant SubTenantMiddleware
participant Controller
Client->>TenantApiKeyMiddleware: X-Tenant-Key: sk_live_xxx
TenantApiKeyMiddleware->>TenantApiKeyMiddleware: Verifier cle, tenant actif, KYB verifie
TenantApiKeyMiddleware->>EnsureTenantIsolation: Request->tenant = Tenant
EnsureTenantIsolation->>EnsureTenantIsolation: Verifier que l'entite demandee<br/>appartient au tenant
EnsureTenantIsolation->>SubTenantMiddleware: Passer si valide
SubTenantMiddleware->>SubTenantMiddleware: Valider sub-tenant<br/>appartient au tenant
SubTenantMiddleware->>Controller: Request enrichie<br/>(tenant + subTenant)Niveau 2 -- Scopes Eloquent : le trait HasTenantScope filtre automatiquement les requetes par tenant_id.
// Trait HasTenantScope
trait HasTenantScope
{
public function scopeForTenant($query, ?string $tenantId)
{
return $query->where('tenant_id', $tenantId);
}
}Niveau 3 -- Row-Level Security (RLS) : des policies PostgreSQL ajoutent une couche de protection au niveau de la base de donnees pour les tables fiscales sensibles.
4.3 Modes d'authentification
| Mode | Header | Cible | Middleware |
|---|---|---|---|
| Bearer Token | Authorization: Bearer {token} | Dashboard utilisateur | auth:sanctum |
| Secret Key | X-Tenant-Key: sk_live_xxx | API externe | tenant.key |
| Tenant Key | X-Tenant-Key: sk_live_xxx | API multi-tenant | tenant.key |
| Publishable Key | X-Publishable-Key: pk_live_xxx | API onboarding | publishable.key |
4.4 Resolution de connexion base de donnees
Le middleware ResolveDatabaseConnection permet de resoudre la connexion base de donnees par tenant. Actuellement, tous les tenants partagent la meme base avec isolation par colonne (tenant_id). L'architecture est prete pour une evolution vers une isolation par base de donnees si necessaire.
5. Architecture Fiscale (NF525)
Le module fiscal assure la conformite a la norme NF525 (certification des logiciels de caisse) et aux exigences ISCA (Inalterable, Securise, Conserve, Archive).
5.1 Flux fiscal complet
graph TB
subgraph "Declenchement"
INV["Facture creee"]
CN["Avoir cree"]
PAY["Paiement enregistre"]
end
subgraph "Enregistrement"
FE["FiscalEntry<br/>Entree immutable<br/>data_snapshot + data_hash"]
SEQ["FiscalSequence<br/>Numero sequence atomique<br/>(verrouillage optimiste)"]
CHAIN["Chaine de hachage<br/>previous_hash -> chain_hash<br/>SHA256"]
end
subgraph "Cloture"
DAILY["Cloture quotidienne<br/>00:05 UTC"]
MONTHLY["Cloture mensuelle"]
YEARLY["Cloture annuelle"]
FEC["Export FEC<br/>Fichier des Ecritures<br/>Comptables"]
end
subgraph "Attestation"
ATT["Attestation annuelle<br/>PDF signe"]
ANCHOR["Ancrage TSA<br/>RFC3161<br/>(FreeTSA)"]
end
subgraph "Integrite"
CHECK["Verification quotidienne<br/>02:00 UTC"]
AUDIT["Fiscal Audit Log<br/>Tentatives de modification<br/>Violations d'immutabilite"]
KS["Kill Switch<br/>Arret d'urgence"]
end
INV --> FE
CN --> FE
PAY --> FE
FE --> SEQ
SEQ --> CHAIN
CHAIN --> DAILY
DAILY --> MONTHLY
MONTHLY --> YEARLY
DAILY --> FEC
YEARLY --> ATT
FE --> ANCHOR
CHECK --> CHAIN
FE -.->|Violation| AUDIT
KS -.->|Bloque| FE5.2 Chaine de hachage
Chaque FiscalEntry est chainee cryptographiquement a la precedente via SHA256 :
Entry N-1: chain_hash = SHA256(data_hash + previous_hash + sequence_number)
Entry N : previous_hash = Entry(N-1).chain_hash
chain_hash = SHA256(data_hash + previous_hash + sequence_number)
Entry N+1: previous_hash = Entry(N).chain_hash
...Garanties :
- Inalterable : les champs fiscaux sont verrouilles apres soumission (traits
LogsImmutabilityViolation) - Securise : chaque modification tentee est journalisee dans
fiscal_audit_log - Conserve : retention minimale de 6 ans (configurable)
- Archive : ancrage TSA RFC3161 pour preuve juridique
5.3 Separation des roles (ISCA S-10)
Le middleware FiscalScopeGuard applique une separation stricte des roles :
| Scope | Operations autorisees | Exemple |
|---|---|---|
fiscal:read | Consultation grand livre, closures, FEC, attestations | GET /tenant/fiscal/* |
fiscal:write | Clotures manuelles, creation/modification regles | POST /tenant/fiscal/closings/daily |
fiscal:admin | Kill-switch (activation/desactivation) | POST /tenant/fiscal/kill-switch/activate |
5.4 Tables fiscales
| Table | Role | Particularite |
|---|---|---|
fiscal_entries | Grand livre immutable | Append-only, pas de timestamps updatable |
fiscal_sequences | Compteur de sequence | Verrouillage optimiste (lock_version) |
fiscal_closings | Clotures periodiques | Quotidiennes, mensuelles, annuelles |
fiscal_attestations | Attestations annuelles | PDF signe, informations editeur |
fiscal_anchors | Horodatage TSA | RFC3161 via FreeTSA |
fiscal_kill_switches | Arret d'urgence | Un seul par tenant |
fiscal_integrity_checks | Rapports d'integrite | Quotidien a 02:00 UTC |
fiscal_audit_log | Journal d'audit | Violations, acces, modifications tentees |
fiscal_rules | Regles de conformite | Configurables par tenant |
fiscal_rule_applications | Journal d'application des regles | Resultat par execution |
6. Integrations Externes
6.1 OpenAPI.com (Signatures eIDAS)
Integration pour la signature electronique simple (EU-SES) conforme eIDAS.
sequenceDiagram
participant Client
participant Scell API
participant Horizon
participant OpenAPI.com
Client->>Scell API: POST /v1/signatures<br/>{file, signers, auth_method}
Scell API->>Scell API: Verifier solde (1.20 EUR)
Scell API->>Horizon: Dispatch ProcessSignature
Scell API-->>Client: 201 {id, status: "waiting_signers"}
Horizon->>OpenAPI.com: POST /signatures<br/>(OAuth2 Bearer Token)
OpenAPI.com-->>Horizon: {openapi_id, signing_urls}
Horizon->>Scell API: Mise a jour Signature (signing_urls)
Note over OpenAPI.com: Signataire signe le document
OpenAPI.com->>Scell API: POST /webhooks/openapi/signature<br/>{event: "completed"}
Scell API->>Scell API: Mise a jour statut
Scell API->>Client: Webhook: signature.completedAuthentification : OAuth2 avec refresh token automatique (OpenAPIOAuthService)
Methodes d'authentification signataire : SMS OTP (via BulkGate), email, biometrique
6.2 SuperPDP (Facturation electronique)
Integration avec la plateforme PDP pour la transmission de factures electroniques aux formats Factur-X, UBL et CII.
sequenceDiagram
participant Client
participant Scell API
participant Horizon
participant SuperPDP
participant CircuitBreaker
Client->>Scell API: POST /v1/invoices<br/>{seller, buyer, lines}
Scell API->>Scell API: Verifier solde (0.04 EUR)
Scell API->>Horizon: Dispatch ProcessInvoice
Scell API-->>Client: 201 {id, status: "draft"}
Client->>Scell API: POST /v1/invoices/{id}/submit
Scell API->>CircuitBreaker: Verifier etat
alt Circuit CLOSED (normal)
Scell API->>SuperPDP: POST /documents<br/>(Factur-X/UBL/CII)
SuperPDP-->>Scell API: {superpdp_id, status: "processing"}
else Circuit OPEN (fallback)
Scell API-->>Client: 503 Service temporairement indisponible
end
Note over SuperPDP: Traitement du document
SuperPDP->>Scell API: POST /webhooks/superpdp/invoice<br/>{status: "accepted"}
Scell API->>Scell API: Mise a jour statut
Scell API->>Client: Webhook: invoice.submittedFormats supportes : Factur-X (MINIMUM, BASIC_WL, BASIC, EN16931, EXTENDED), UBL 2.1, CII
Profils : Minimum, Basic WL, Basic, EN16931, Extended
6.3 BulkGate (SMS OTP)
Envoi de codes SMS pour l'authentification des signataires.
- Rate limit : 1 SMS par signataire par heure
- Suivi : table
sms_logspour audit - Verification : code OTP a 6 chiffres, expiration configurable
6.4 Stripe (Paiements)
Gestion des paiements et recharges de solde.
- Recharge :
POST /v1/tenant/billing/top-upcree un PaymentIntent Stripe - Confirmation :
POST /v1/tenant/billing/top-up/confirmfinalise le paiement - Facturation : generation mensuelle automatique (
GenerateMonthlyInvoices)
6.5 INSEE Sirene et VIES
- SIRET : verification aupres du registre INSEE avec cache 24h
- TVA : verification intracommunautaire via VIES avec cache 24h
7. Decisions d'Architecture (ADR)
ADR-001 : UUID pour toutes les cles primaires
Statut : Accepte
Contexte : le systeme est multi-tenant et distribue. Les identifiants auto-incrementes exposent l'ordre de creation et le volume de donnees. De plus, la generation d'ID cote client est necessaire pour les operations asynchrones.
Decision : utiliser UUID v4 pour toutes les cles primaires via le trait Eloquent HasUuids. PostgreSQL 17 offre un support natif performant du type uuid.
Consequences :
- Positif : pas de collision, generation cote client possible, securite renforcee
- Negatif : index plus larges (16 octets vs 4/8), jointures legerement plus lentes
- Attenuation : les performances restent excellentes avec les index PostgreSQL natifs
ADR-002 : Multi-tenant avec isolation middleware (colonne) vs base separee
Statut : Accepte
Contexte : deux strategies possibles -- isolation par base de donnees (chaque tenant a sa propre DB) ou isolation par colonne (tenant_id sur chaque table).
Decision : isolation par colonne avec tenant_id et verification a trois niveaux (middleware, scope Eloquent, RLS PostgreSQL).
Consequences :
- Positif : simplicite de deploiement, migrations uniques, requetes cross-tenant pour admin
- Negatif : risque de fuite de donnees si oubli de filtre
- Attenuation : le trait
HasTenantScope, le middlewareEnsureTenantIsolationet les RLS policies PostgreSQL forment un triple filet de securite
ADR-003 : Sanctum vs Passport pour authentification API
Statut : Accepte
Contexte : Laravel propose Sanctum (tokens simples) et Passport (OAuth2 complet). Scell.io utilise 4 modes d'authentification distincts.
Decision : Sanctum pour l'authentification utilisateur (Bearer Token) combine avec des middleware custom pour les 3 autres modes (Secret Key, Tenant Key, Publishable Key).
Consequences :
- Positif : legerete de Sanctum, flexibilite des middleware custom, pas de surcharge OAuth2
- Negatif : pas de scopes OAuth2 natifs (compense par Spatie Permission)
ADR-004 : Chaine de hachage SHA256 pour conformite NF525
Statut : Accepte
Contexte : la norme NF525 exige l'inalterabilite des donnees fiscales. Plusieurs approches possibles : blockchain, chaine de hachage, ou base de donnees append-only.
Decision : chaine de hachage SHA256 avec numero de sequence atomique par tenant. Chaque FiscalEntry inclut le hash de l'entree precedente, creant une chaine inviolable.
Consequences :
- Positif : performance elevee, verification rapide, pas de dependance externe
- Negatif : la verification de chaine complete est O(n) sur le nombre d'entrees
- Attenuation : verification par lots et ancrage TSA periodique pour preuve externe
ADR-005 : Horizon vs database driver pour les queues
Statut : Accepte
Contexte : Laravel supporte plusieurs drivers de queue (database, Redis, SQS). Horizon offre un dashboard de supervision pour Redis.
Decision : Laravel Horizon avec Redis 7.4 comme backend de queue.
Consequences :
- Positif : dashboard de monitoring, metriques temps reel, auto-scaling des workers
- Negatif : dependance a Redis (deja requis pour cache/sessions)
ADR-006 : MCP Server en Laravel integre vs Node.js externe
Statut : Accepte
Contexte : le protocole MCP (Model Context Protocol) permet aux agents IA d'interagir avec l'API. L'implementation peut etre integree au backend Laravel ou deployee separement.
Decision : MCP server integre au backend Laravel (app/MCP/) avec 54 outils, utilisant un transport HTTP+SSE (HttpStreamableTransport). Un agent SuperPDP supplementaire en Node.js (superpdp-agent/) sert de pont specialise.
Consequences :
- Positif : acces direct aux services et modeles Laravel, authentification unifiee
- Negatif : charge supplementaire sur le backend
- Attenuation : rate limiting dedie (
McpRateLimiter), sessions Redis avec TTL
ADR-007 : Factur-X via Horsteko vs implementation custom
Statut : Accepte
Contexte : la generation de factures Factur-X necessite l'integration de XML dans des PDF/A-3. Plusieurs bibliotheques PHP existent.
Decision : utiliser la bibliotheque Horsteko (HorstekoFacturXGenerator) pour la generation Factur-X, UBL et CII.
Consequences :
- Positif : support de tous les profils Factur-X, conformite EN16931 validee
- Negatif : dependance a une bibliotheque tierce
ADR-008 : Soft deletes desactivees pour credit notes (NF525)
Statut : Accepte
Contexte : la norme NF525 interdit la suppression de documents fiscaux. Les soft deletes Laravel (SoftDeletes) marquent les enregistrements comme supprimes sans les effacer physiquement, ce qui pourrait creer une ambiguite juridique.
Decision : desactiver les soft deletes sur la table credit_notes. La suppression n'est autorisee que pour les brouillons (status = draft). Une migration dediee (remove_soft_deletes_from_credit_notes) a retire la colonne deleted_at.
Consequences :
- Positif : conformite NF525 stricte, pas d'ambiguite sur les enregistrements fiscaux
- Negatif : aucune possibilite de suppression apres emission
- Attenuation : les avoirs permettent d'annuler economiquement une facture sans la supprimer