Architecture Multi-Tenant B2B
Version: 1.0 Date: 2026-03-03
Table des matieres
- Introduction
- Modele de Donnees
- Onboarding B2B
- KYB (Know Your Business)
- Isolation Tenant
- Billing Tenant
- API Tenant
- Sub-Tenants
- Factures et Avoirs Tenant
- Factures Entrantes
1. Introduction
Modele multi-tenant de Scell.io
Scell.io utilise un modele multi-tenant par rangee (row-level) avec une base de donnees partagee. Chaque tenant (entreprise cliente) dispose d'un espace isole au sein de la meme infrastructure PostgreSQL.
Ce modele est concu pour les partenaires B2B (cabinets comptables, ERP, plateformes SaaS) qui souhaitent integrer les services de facturation et signature electronique de Scell.io pour le compte de leurs propres clients.
Hierarchie des acteurs
Tenant (Partenaire ou Entreprise)
Qui? Cabinet comptable, editeur ERP, marketplace, ou entreprise SaaS
Role: Initie l'onboarding, recoit les webhooks
Attribut: is_partner = true/false
|-- Sub-Tenant (Client du tenant / Filiale / Departement)
| Qui? Entite operationnelle du tenant
| Role: Espace logique pour isoler les factures
|
| |-- Company (Entite juridique)
| | Role: SIRET, adresse, donnees legales
| |
| |-- Invoices, Credit Notes, SignaturesCas d'usage concret
Un cabinet comptable (Tenant avec is_partner=true) inscrit ses 200 clients (autres Tenants) via l'API d'onboarding. Chaque client genere ses factures de maniere autonome avec sa propre cle API. Le cabinet supervise l'ensemble depuis son dashboard. Scell.io facture chaque tenant individuellement en fonction de sa consommation.
2. Modele de Donnees
Diagramme des relations
erDiagram
Tenant ||--o{ SubTenant : "contient"
Tenant ||--o{ Company : "possede"
Tenant ||--o{ TenantInvoice : "recoit (facturation)"
Tenant ||--o{ TenantTransaction : "consomme"
Tenant ||--o{ KybDocument : "fournit"
Tenant ||--o{ OnboardingSession : "initie (si partenaire)"
SubTenant ||--o{ Company : "represente"
SubTenant ||--o{ TenantInvoice : "concerne"
Company ||--o{ Invoice : "emet/recoit"
Company ||--o{ CreditNote : "emet"
Company ||--o{ Signature : "signe"
Company ||--o{ ApiKey : "possede"
OnboardingSession ||--o{ KybDocument : "collecte"
OnboardingSession ||--o{ Tenant : "cree (tenant enfant)"
Tenant {
uuid id PK
string name
string slug
string siret
string vat_number
string api_key_hash
string api_key_prefix
string publishable_key_hash
string publishable_key_prefix
string environment
string kyb_status
decimal balance
boolean is_active
boolean is_partner
uuid parent_tenant_id FK
json settings
}
SubTenant {
uuid id PK
uuid tenant_id FK
string external_id
string name
string siret
string status
}
Company {
uuid id PK
uuid tenant_id FK
uuid sub_tenant_id FK
string name
string siret
string status
}Modele Tenant
Le Tenant represente une entreprise cliente ou un partenaire operant dans son propre espace :
Tenant {
id: UUID
parent_tenant_id: UUID // Partenaire parent (si ce tenant est un client)
name: "ACME SAS"
slug: "acme-sas"
legal_name: "ACME Societe par Actions Simplifiee"
siret: "12345678900001"
vat_number: "FR12345678900"
// Contacts
billing_email: "compta@acme.fr"
technical_email: "tech@acme.fr"
// Authentification - Secret Keys (backend)
api_key_hash: "sha256:..." // Hash de la cle secrete API
api_key_prefix: "sk_live_aBcD" // Prefixe pour identification (sk_live_ ou sk_test_)
// Authentification - Publishable Keys (frontend)
publishable_key_hash: "sha256:..." // Hash de la cle publiable
publishable_key_prefix: "pk_live_xyz" // Prefixe de la cle publiable (pk_live_ ou pk_test_)
environment: "production" // production | sandbox
// KYB
kyb_status: "verified" // pending | documents_submitted | under_review | verified | rejected
kyb_verified_at: "2026-02-15"
// Billing
balance: 150.00 // Solde en EUR
stripe_customer_id: "cus_..."
// Webhooks
webhook_url: "https://..."
webhook_secret: "whsec_..."
// Configuration
is_partner: false // true = partenaire integrant Scell.io
settings: {
fiscal_permissions: ["fiscal:read", "fiscal:write"],
invoice_prefix: "FA",
default_vat_rate: 20,
}
is_active: true
}Methodes cles :
| Methode | Description |
|---|---|
generateApiKey(string $env) | Genere une cle secrete API (sk_live_* ou sk_test_*) |
generatePublishableKey(string $env) | Genere une cle publiable (pk_live_* ou pk_test_*) |
findByApiKey(string $key) | Recherche un tenant par sa cle API secrete (hash) |
findByPublishableKey(string $key) | Recherche un tenant par sa cle publiable (hash) |
hasSufficientBalance(float $amount) | Verifie le solde disponible |
debit(float $amount) | Debite le solde |
credit(float $amount) | Credite le solde |
isKybCompleted() | Verifie que le KYB est valide |
markKybVerified() | Marque le KYB comme verifie |
Modele SubTenant
Le SubTenant represente un client ou une entite du tenant :
SubTenant {
id: UUID
tenant_id: UUID
external_id: "CLI-42" // ID dans le systeme du tenant
name: "Filiale Nord SAS"
email: "contact@filiale-nord.fr"
siret: "98765432100001"
vat_number: "FR98765432100"
address: { line1: "...", city: "Lille", postal_code: "59000" }
status: "active"
metadata: {}
}L'external_id permet au tenant de mapper ses propres identifiants clients avec Scell.io.
3. Onboarding B2B
Flux complet (SuperPDP OAuth2)
Le processus d'onboarding permet a un Tenant partenaire (is_partner=true) d'inscrire un nouveau Tenant client en 3 etapes via le flux OAuth2 Authorization Code de SuperPDP. SuperPDP prend en charge l'inscription, le KYB et la verification d'identite directement dans son interface.
sequenceDiagram
participant W as Widget (Partenaire)
participant API as Scell.io API
participant SPDP as SuperPDP OAuth2
W->>API: POST /onboarding/superpdp/authorize
API->>W: {authorization_url, state}
W->>W: Ouvre popup SuperPDP (authorization_url)
SPDP->>SPDP: Inscription + KYB + verification identite
SPDP->>W: Callback avec ?code=...&state=...
W->>API: POST /onboarding/superpdp/callback {code, state}
API->>SPDP: Echange du code contre les tokens
SPDP->>API: {access_token, refresh_token, ...}
API->>API: Cree Tenant + stocke tokens SuperPDP
API->>W: {tenant_id, api_key: "sk_live_..."}
API->>W: Webhook: onboarding.completedEtape 1 : Creer une session d'onboarding
curl -X POST https://api.scell.io/api/v1/onboarding/sessions \
-H "X-Publishable-Key: pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"mode": "embedded",
"external_id": "customer_42"
}'Reponse : { "data": { "id": "session_uuid", "status": "pending", ... } }
Etape 2 : Initier l'autorisation SuperPDP
Le redirect_uri est configure cote SuperPDP (pointe vers https://api.scell.io/api/v1/onboarding/superpdp/callback), pas dans la requete.
curl -X POST https://api.scell.io/api/v1/onboarding/superpdp/authorize \
-H "X-Publishable-Key: pk_live_..." \
-H "Content-Type: application/json" \
-d '{ "session_id": "session_uuid" }'Reponse :
{
"authorize_url": "https://api.superpdp.tech/oauth2/authorize?client_id=...&state=...&redirect_uri=...&response_type=code",
"state": "scell_session_state_random"
}Le partenaire ouvre authorize_url dans un popup. SuperPDP gere entierement l'inscription de l'entreprise, la verification KYB et la verification d'identite du representant legal.
Etape 3 : Echanger le code
Apres completion du flux SuperPDP, l'utilisateur est redirige vers le callback Scell. Le widget recoit ensuite un postMessage et appelle :
curl -X POST https://api.scell.io/api/v1/onboarding/superpdp/callback \
-H "X-Publishable-Key: pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"session_id": "session_uuid",
"code": "auth_xyz789...",
"state": "scell_session_state_random"
}'Reponse :
{
"success": true,
"authorization_code": "scell_auth_code_xyz",
"tenant": {
"id": "9f3a1b2c-...",
"name": "Acme SAS",
"siret": "12345678901234",
"environment": "production"
}
}Le backend echange le code contre les tokens OAuth2 SuperPDP, cree le Tenant Scell.io avec les tokens stockes et retourne un authorization_code Scell que le partenaire echange ensuite contre la cle API operationnelle via POST /onboarding/exchange.
Widget d'onboarding
Scell.io fournit un widget JavaScript standalone qui implemente ce flux en 3 etapes :
<script src="https://cdn.scell.io/widget/v1/onboarding.js"></script>
<scell-onboarding
publishable-key="pk_live_..."
locale="fr"
></scell-onboarding>
<script>
document.querySelector('scell-onboarding')
.addEventListener('onboarding:completed', (e) => {
console.log('Tenant cree:', e.detail.tenant.id);
console.log('Code:', e.detail.authorization_code);
});
</script>L'environnement (sandbox/production) est determine automatiquement par le prefixe de la cle publishable (pk_test_* -> sandbox, pk_live_* -> production). Le widget est distribue via CDN (cdn.scell.io) et disponible en tant que package ESM (onboarding.esm.js). Le code source est dans frontend/packages/onboarding-widget/.
Etapes du widget :
- Connect : Bouton "Se connecter via SuperPDP" qui declenche le flux OAuth2
- Redirect : Ouverture du popup SuperPDP (inscription + KYB + identite)
- Complete : Finalisation et reception des credentials
4. KYB (Know Your Business)
Verification via SuperPDP
Depuis la v2 du widget d'onboarding, le KYB est integralement pris en charge par SuperPDP au moment de l'autorisation OAuth2. L'utilisateur realise son inscription, la verification KYB (Kbis, identite du representant legal, RIB) et la verification biometrique directement dans l'interface SuperPDP. Scell.io n'intervient plus dans la collecte ni la validation des documents KYB lors de l'onboarding.
La table KybDocument et les endpoints /v1/tenant/kyb/documents restent disponibles pour les cas de mise a jour post-onboarding (renouvellement de Kbis, changement de representant legal, etc.).
Statuts de verification
stateDiagram-v2
[*] --> pending: Session creee
pending --> documents_submitted: Documents uploades
documents_submitted --> under_review: Revue admin lancee
under_review --> verified: Approuve
under_review --> rejected: Refuse
rejected --> documents_submitted: Nouveaux documents
verified --> [*]| Statut | Description |
|---|---|
pending | En attente de documents |
documents_submitted | Documents recus, en attente de revue |
under_review | Revue en cours par l'equipe Scell.io |
verified | KYB valide, acces production autorise |
rejected | KYB refuse (raison fournie) |
API KYB (cote tenant)
| Endpoint | Methode | Description |
|---|---|---|
/v1/tenant/kyb/status | GET | Statut actuel du KYB |
/v1/tenant/kyb/documents | GET | Lister les documents |
/v1/tenant/kyb/documents | POST | Uploader un document |
/v1/tenant/kyb/documents/{id} | GET | Detail d'un document |
/v1/tenant/kyb/documents/{id} | DELETE | Supprimer un document |
/v1/tenant/kyb/documents/{id}/download | GET | Telecharger |
API KYB (cote admin)
| Endpoint | Methode | Description |
|---|---|---|
/v1/admin/kyb/pending | GET | Documents en attente de revue |
/v1/admin/kyb/documents/{id}/review | POST | Approuver ou refuser |
/v1/admin/kyb/documents/{id}/download | GET | Telecharger |
Impact du KYB sur les operations
| KYB Status | Sandbox (sk_test_*, pk_test_*) | Production (sk_live_*, pk_live_*) |
|---|---|---|
pending | Acces complet | Acces refuse (403) |
documents_submitted | Acces complet | Acces refuse (403) |
under_review | Acces complet | Acces refuse (403) |
verified | Acces complet | Acces complet |
rejected | Acces complet | Acces refuse (403) |
5. Isolation Tenant
L'isolation des donnees entre tenants est garantie a quatre niveaux.
Niveau 1 : Middleware TenantApiKeyMiddleware
Le middleware intercepte chaque requete tenant et :
- Valide la cle API (format
sk_live_*ousk_test_*) - Charge le tenant depuis la base (hash de la cle)
- Verifie que le tenant est actif
- Verifie le KYB pour les cles de production
- Injecte le tenant dans la requete (
$request->attributes->set('tenant', $tenant))
// Les controllers accedent au tenant via :
$tenant = $request->attributes->get('tenant');
$tenantId = $request->attributes->get('tenant_id');
$environment = $request->attributes->get('tenant_environment');Niveau 2 : Trait HasTenantScope
Le trait HasTenantScope est applique aux modeles fiscaux (FiscalEntry, FiscalClosing, FiscalAttestation, etc.) et ajoute un Global Scope Eloquent automatique :
// Applique automatiquement WHERE tenant_id = ? a toutes les requetes
static::addGlobalScope('tenant', function (Builder $builder) {
$request = request();
if ($request && $request->has('tenant_id')) {
$builder->where('tenant_id', $request->get('tenant_id'));
}
});Ce scope garantit qu'un tenant ne peut jamais acceder aux donnees d'un autre tenant, meme en cas d'erreur dans le code du controller.
Methodes de scope additionnelles :
// Filtrage explicite par tenant
FiscalEntry::forTenant($tenantId)->get();
// Filtrage par sub-tenant
Invoice::forSubTenant($subTenantId)->get();
// Desactiver temporairement le scope (admin uniquement)
FiscalEntry::withoutTenantScope()->get();Niveau 3 : Middleware EnsureTenantIsolation
Ce middleware configure une variable de session PostgreSQL pour le Row Level Security (RLS) :
// Au debut de la requete
DB::statement("SET app.current_tenant_id = ?", [$tenantId]);
// A la fin de la requete
DB::statement("RESET app.current_tenant_id");Cette variable peut etre utilisee par des policies PostgreSQL pour une isolation au niveau base de donnees, independante de l'application.
Niveau 4 : Middleware SubTenantMiddleware
Pour les routes impliquant un sub-tenant (ex: /tenant/sub-tenants/{subTenantId}/invoices), ce middleware verifie que le sub-tenant appartient bien au tenant authentifie :
$subTenant = SubTenant::where('id', $subTenantId)
->where('tenant_id', $tenant->id)
->firstOrFail();Middlewares appliques aux routes tenant
| Middleware | Route | Role |
|---|---|---|
tenant.key | Toutes les routes /tenant/* | Authentification par cle API |
tenant.rate-limit | Toutes les routes /tenant/* | Rate limiting (100 req/min) |
tenant.balance:invoice | Creation de factures | Verification du solde |
tenant.balance:credit_note | Creation d'avoirs | Verification du solde |
tenant.isolation | Routes critiques | RLS PostgreSQL |
sub-tenant | Routes /sub-tenants/{id}/* | Validation sub-tenant |
fiscal.kill-switch | Creation factures/avoirs | Kill switch fiscal |
fiscal.period-guard | Operations fiscales | Periode fiscale valide |
fiscal.scope | Routes fiscales | Verification des permissions |
6. Billing Tenant
Systeme de credits
Scell.io utilise un systeme de credits prepaye. Chaque tenant dispose d'un solde en EUR :
| Operation | Cout unitaire |
|---|---|
| Facture electronique | 0.04 EUR |
| Avoir electronique | 0.04 EUR |
| Signature electronique | 1.20 EUR |
| Facture entrante (reception) | Gratuit |
Remises volume
Le TenantBillingService applique des remises progressives basees sur la consommation mensuelle de factures :
| Seuil mensuel | Remise |
|---|---|
| > 1 000 factures | -10% |
| > 5 000 factures | -20% |
| > 10 000 factures | -30% |
Facturation mensuelle
Le 1er de chaque mois, la commande GenerateMonthlyInvoices genere une facture consolidee pour chaque tenant :
php artisan billing:generate-monthly-invoicesLa facture inclut :
| Ligne | Calcul |
|---|---|
| Factures electroniques | nombre * 0.04 EUR |
| Signatures electroniques | nombre * 1.20 EUR |
| Avoirs electroniques | nombre * 0.04 EUR |
| Sous-total HT | Somme des lignes |
| Remise volume | -X% si seuil atteint |
| Total HT | Apres remise |
| TVA (20%) | total_ht * 0.20 |
| Total TTC | total_ht + tva |
Recharge de credits (Top-Up)
Les tenants peuvent recharger leur solde via Stripe :
# Demander un top-up
curl -X POST https://api.scell.io/api/v1/tenant/billing/top-up \
-H "X-API-Key: sk_live_..." \
-d '{ "amount": 100.00 }'
# Reponse : URL de paiement Stripe
{
"checkout_url": "https://checkout.stripe.com/...",
"amount": 100.00,
"currency": "EUR"
}
# Confirmer apres paiement
curl -X POST https://api.scell.io/api/v1/tenant/billing/top-up/confirm \
-H "X-API-Key: sk_live_..." \
-d '{ "payment_intent_id": "pi_..." }'Transactions
Chaque debit/credit est enregistre dans TenantTransaction :
| Type | Description |
|---|---|
invoice_created | Debit 0.04 EUR |
credit_note_created | Debit 0.04 EUR |
signature_created | Debit 1.20 EUR |
balance_topup | Credit N EUR |
balance_debit | Debit manuel (admin) |
API Billing
| Endpoint | Methode | Description |
|---|---|---|
/v1/tenant/billing/invoices | GET | Factures de facturation |
/v1/tenant/billing/invoices/{id} | GET | Detail d'une facture |
/v1/tenant/billing/invoices/{id}/download | GET | Telecharger PDF |
/v1/tenant/billing/usage | GET | Consommation du mois |
/v1/tenant/billing/top-up | POST | Demander une recharge |
/v1/tenant/billing/top-up/confirm | POST | Confirmer la recharge |
/v1/tenant/billing/transactions | GET | Historique des transactions |
7. API Tenant
Authentification
Deux systemes d'authentification existent :
1. Authentification User (Dashboard Sanctum)
Pour le dashboard administrateur :
curl -H "Authorization: Bearer {sanctum_token}" \
https://api.scell.io/api/v1/admin/companies2. Authentification Tenant (Dual Key System)
Scell.io utilise un systeme de deux types de cles pour securiser l'integration :
Secret Keys (sk_*) - Cotes serveur uniquement
Les cles secretes sont utilisees pour les operations sensibles via le header X-API-Key. Elles ne doivent JAMAIS etre exposees en frontend :
curl -H "X-API-Key: sk_live_YOUR_KEY" \
https://api.scell.io/api/v1/tenant/meFormat : sk_live_ ou sk_test_ + 32 caracteres alphanumeriques
Publishable Keys (pk_*) - Cotes client (frontend)
Les cles publiables sont utilisees dans les widgets et composants frontend via le header X-Publishable-Key. Elles sont sans risque d'exposition :
import { ScellOnboarding } from '@scell/onboarding-widget';
const widget = new ScellOnboarding({
publishableKey: 'pk_live_xYz123...', // Safe to expose in frontend
mode: 'embedded'
});Format : pk_live_ ou pk_test_ + 32 caracteres alphanumeriques
Comparaison des deux systemes :
| Type | Format | Localisation | Securite | Usages |
|---|---|---|---|---|
| Secret Key | sk_* | Backend/serveur | Haute - confidentielle | Factures, avoirs, factures entrantes, admin |
| Publishable Key | pk_* | Frontend/widget | Publique - safe | Onboarding, signatures, widgets |
Gestion du profil
# Recuperer le profil
GET /v1/tenant/me
# Mettre a jour
PUT /v1/tenant/me
{
"billing_email": "nouveau@email.fr",
"webhook_url": "https://nouveau-webhook.com/scell"
}
# Recuperer le solde
GET /v1/tenant/balance
# Statistiques globales
GET /v1/tenant/stats
# Regenerer la cle API
POST /v1/tenant/regenerate-keyRate Limiting
Les routes tenant sont limitees a 100 requetes par minute par cle API. Ce seuil est configurable via le modele RateLimitOverride.
Reponse en cas de depassement :
{
"error": "Trop de requetes",
"code": "RATE_LIMIT_EXCEEDED",
"retry_after": 30
}Headers de rate limiting :
| Header | Description |
|---|---|
X-RateLimit-Limit | Limite par minute |
X-RateLimit-Remaining | Requetes restantes |
X-RateLimit-Reset | Timestamp de reinitialisation |
8. Sub-Tenants
Principe
Les sub-tenants permettent a un tenant de segmenter ses operations par client, filiale ou departement. Chaque sub-tenant est un espace logique avec :
- Son propre
external_id(mappable avec le SI du tenant) - Ses propres factures et avoirs
- Ses propres statistiques
- Sa propre sequence de numerotation
API Sub-Tenants
# Lister les sub-tenants
GET /v1/tenant/sub-tenants
# Creer un sub-tenant
POST /v1/tenant/sub-tenants
{
"external_id": "CLI-042",
"name": "ACME Filiale Nord",
"email": "nord@acme.fr",
"siret": "98765432100001",
"vat_number": "FR98765432100"
}
# Recuperer par ID
GET /v1/tenant/sub-tenants/{id}
# Recuperer par external_id
GET /v1/tenant/sub-tenants/by-external-id/{externalId}
# Mettre a jour
PUT /v1/tenant/sub-tenants/{id}
# Supprimer
DELETE /v1/tenant/sub-tenants/{id}Scoping par sub-tenant
Les factures et avoirs sont automatiquement scopes par sub-tenant :
# Factures d'un sub-tenant specifique
POST /v1/tenant/sub-tenants/{subTenantId}/invoices
GET /v1/tenant/sub-tenants/{subTenantId}/invoices
# Avoirs d'un sub-tenant
POST /v1/tenant/sub-tenants/{subTenantId}/credit-notes
GET /v1/tenant/sub-tenants/{subTenantId}/credit-notes
# Statistiques par sub-tenant
GET /v1/tenant/sub-tenants/{subTenantId}/stats/overview9. Factures et Avoirs Tenant
Creation de factures
Deux modes sont disponibles :
Mode sub-tenant (factures associees a un sous-tenant) :
POST /v1/tenant/sub-tenants/{subTenantId}/invoicesMode direct (factures sans sous-tenant) :
POST /v1/tenant/invoicesCorps de la requete
{
"invoice_number": "FA-2026-001",
"issue_date": "2026-03-03",
"due_date": "2026-04-02",
"seller_name": "ACME SAS",
"seller_siret": "12345678900001",
"seller_address": { "line1": "10 rue de la Paix", "city": "Paris", "postal_code": "75002" },
"buyer_name": "Client SARL",
"buyer_siret": "98765432100001",
"buyer_address": { "line1": "5 avenue Victor Hugo", "city": "Lyon", "postal_code": "69002" },
"lines": [
{ "description": "Prestation de conseil", "quantity": 5, "unit_price": 100, "tax_rate": 20 }
],
"output_format": "facturx",
"metadata": { "reference_interne": "CMD-042" }
}Operations en masse (bulk)
Pour les volumes importants, des endpoints bulk sont disponibles :
# Creation en masse (max 100 factures)
POST /v1/tenant/invoices/bulk
{
"invoices": [
{ "invoice_number": "FA-001", ... },
{ "invoice_number": "FA-002", ... }
]
}
# Soumission en masse au SuperPDP
POST /v1/tenant/invoices/bulk-submit
{
"invoice_ids": ["uuid-1", "uuid-2", "uuid-3"]
}
# Statut en masse
POST /v1/tenant/invoices/bulk-status
{
"invoice_ids": ["uuid-1", "uuid-2", "uuid-3"]
}Cycle de vie d'une facture tenant
stateDiagram-v2
[*] --> draft: Creee
draft --> draft: Modifiee
draft --> submitted: Soumise au SuperPDP
submitted --> processing: En cours de traitement
processing --> completed: Transmise
processing --> failed: Erreur
failed --> submitted: Re-soumise
completed --> paid: Paiement recu
draft --> deleted: Supprimee (draft uniquement)
deleted --> [*]Avoirs tenant
Meme structure que les factures, avec les particularites :
- Toujours lies a une facture d'origine (
invoice_id) - Montant maximum = montant restant creditable de la facture
- Meme protection d'immutabilite fiscale
10. Factures Entrantes
Principe
Scell.io recoit les factures des fournisseurs via le SuperPDP. Ces factures entrantes sont synchronisees automatiquement et disponibles dans l'espace tenant.
Synchronisation
La synchronisation s'effectue via le SyncIncomingInvoicesJob, declenche quotidiennement ou par webhook SuperPDP :
graph LR
SPDP["SuperPDP<br/>(Portail)"] -->|"Webhook"| WH["SuperPDPWebhookController"]
WH -->|"Job async"| JOB["ProcessIncomingInvoiceWebhook"]
JOB -->|"Cree"| INV["Invoice<br/>(direction: incoming)"]
SPDP -->|"Sync quotidien"| SYNC["SyncIncomingInvoicesJob"]
SYNC -->|"Cursor pagination"| INVAPI Factures entrantes
| Endpoint | Methode | Description |
|---|---|---|
/v1/tenant/sub-tenants/{id}/invoices/incoming | POST | Enregistrer manuellement |
/v1/tenant/sub-tenants/{id}/invoices/incoming | GET | Lister |
/v1/tenant/invoices/incoming/{id} | GET | Detail |
/v1/tenant/invoices/incoming/{id}/accept | POST | Accepter |
/v1/tenant/invoices/incoming/{id}/reject | POST | Rejeter (raison requise) |
/v1/tenant/invoices/incoming/{id}/mark-paid | POST | Marquer comme payee |
Les factures entrantes sont gratuites (pas de debit du solde).
Statuts des factures entrantes
| Statut | Description |
|---|---|
received | Recue du SuperPDP |
accepted | Acceptee par le tenant |
rejected | Rejetee par le tenant |
disputed | En litige |
paid | Payee |