Aller au contenu

POC · Connecteur RNE Tunisie (Kotlin)

Code source : poc-rne-connector/ dans le monorepo VitaKYC.

Deuxième POC technique : le connecteur RNE Tunisie côté VitaKYC. Consomme les web services WS-KYC et WS-BE exactement comme documenté dans les annexes officielles RNE, avec gestion complète des erreurs métier, cache et économie de quota d’abonnement.


Matérialiser l’intégration RNE décrite au cahier des charges §12.3.1 et au diagramme de séquence §7.3. C’est la brique qui fait du KYB instantané côté VitaKYC pour les clients tunisiens — argument commercial clé en AO banque tunisien (extraction auto fiche entreprise + UBO sans dev côté banque).


ServiceEndpointParamètres
WS-KYCGET https://api.registre-entreprises.tn:8243/WS-KYC/1subscriptionId, subscriberId, identifiantUnique
WS-BEGET https://api.registre-entreprises.tn:8243/WS-BE/1subscriptionId, subscriberId, codeMotif, identifiantUnique

Authentification : Authorization: Bearer <token> (token délivré en privé par le RNE à la souscription).


ComposantVersionRaison
Kotlin2.0.20Langage cible VitaKYC
OkHttp4.12Client HTTP minimaliste, timeout fin
kotlinx-serialization1.7JSON → data classes
JUnit 5 + AssertJ + MockWebServerTests avec fixtures RNE officielles

Pas de Spring ni framework lourd — un connecteur ciblé, prêt à être extrait en tant que service Spring Boot connector-rne dans le monorepo VitaKYC (cf. architecture §4.3).



  • Validation entrée : identifiantUnique doit matcher ^[0-9]{7}[A-Z]$ avant tout appel réseau (évite les 400 inutiles qui consomment le quota).
  • Économie de quota WS-BE : le connecteur n’appelle WS-BE/1 que si la fiche KYC retourne declaration_BENEFICIAIRES_EFFECTIFS = "Oui". Les entreprises qui n’ont pas déclaré leurs BE ne déclenchent pas de décompte inutile.
  • Cache en mémoire TTL 24 h avec invalidate() et invalidateAll().
  • Gestion erreurs métier HTTP 400 mappées vers 7 sous-types RneException typés (voir tableau §7).
  • Cas particulier WS-BE : réponse HTTP 200 contenant « Cette entité ne possède pas de déclaration de bénéficiaire Effectif. »emptyList() retourné sans exception.

Les @SerialName de kotlinx-serialization reproduisent exactement les noms d’attributs JSON du RNE, y compris la typo originale QALITE_GESTION_FR (au lieu de QUALITE_GESTION_FR) pour rester déserialisables sans transformation côté RNE.

Blocs exposés :

  • KycBlocEntreprise : 50+ champs (fiche, adresses siège + activité, dates, association, direction, activités)
  • KycDirectionMember : dirigeants (identité, qualité, dates nomination, pouvoirs)
  • KycPouvoir : libellés AR/FR des pouvoirs
  • UboRecord : bénéficiaires effectifs (type, capital direct/indirect, droits de vote, résident, statut)
  • KybFullLookup : agrégat retourné avec métadonnées source (LIVE vs CACHE) et retrievedAtEpochMs

HTTPMessage RNEException Kotlin
400« Le solde disponible est insuffisant… »RneException.InsufficientBalance
400« …non associé à une entité active… »RneException.InactiveEntity
400« Aucune inscription correspondant… »RneException.NotFound
400« L’abonné n’est pas autorisé… »RneException.SubscriberNotAuthorized
400« Adresse IP non autorisé ! »RneException.IpNotAllowed
400« L’inscription à cet abonnement est inactive… »RneException.SubscriptionInactive
400« Ce motif WS Be n’a pas été trouvée… »RneException.MotifNotFound
404Not FoundRneException.NotFound
429Rate limitedRneException.RateLimited
5xx / IORneException.Transport

Le service métier kyc-svc peut ainsi prendre des décisions fines :

  • InsufficientBalance → notifier le tenant pour recharger son abonnement + activer fallback documentaire.
  • InactiveEntity → passer le dossier KYB en rejected avec motif lisible pour l’agent.
  • NotFound → idem, ou relance manuelle avec un autre identifiant.
  • RateLimited → retry exponentiel côté tcr-svc.

#IntituléCouvre
1Mapping JSON RNE → domaine completAnnexe KYC officielle (CNRE)
2Authorization Bearer + query params correctsSpec annexe
3UBO consommé si declaration=OuiÉconomie quota
4WS-BE sauté si declaration=nonÉconomie quota
5WS-BE réponse 200 texte « pas de déclaration »Cas métier
6400 Solde insuffisant → InsufficientBalanceQuota épuisé
7400 Entité inactive → InactiveEntityEntité radiée
8400 IP non autorisée → IpNotAllowedContrôle IP
9400 Motif invalide WS-BE → MotifNotFoundConf abonnement
10404 Not FoundIdentifiant inconnu
11Identifiant malformé → IllegalArgumentExceptionValidation amont
12Cache hit (1 seul appel réseau pour 2 lookups)Cache mémoire
13invalidate() force un nouvel appelInvalidation

Sortie ./gradlew test :

RneConnectorTest > fetchFull — fiche RNE : mapping JSON → domaine complet PASSED
RneConnectorTest > fetchFull — Authorization Bearer + query params corrects PASSED
RneConnectorTest > fetchFull — UBO consommé automatiquement si déclaration Oui PASSED
RneConnectorTest > fetchFull — pas d'appel WS-BE si declaration=non (économie quota) PASSED
RneConnectorTest > WS-BE répond 'pas de déclaration' → liste vide, pas d'exception PASSED
RneConnectorTest > Solde insuffisant (400) → RneException.InsufficientBalance PASSED
RneConnectorTest > Entité inactive → RneException.InactiveEntity PASSED
RneConnectorTest > IP non autorisée → RneException.IpNotAllowed PASSED
RneConnectorTest > Motif invalide (WS-BE) → RneException.MotifNotFound PASSED
RneConnectorTest > 404 Not Found → RneException.NotFound PASSED
RneConnectorTest > Identifiant RNE malformé rejeté en amont (pas d'appel réseau) PASSED
RneConnectorTest > Cache : second appel dans la fenêtre TTL ne tape pas le réseau PASSED
RneConnectorTest > invalidate() force un nouvel appel PASSED
BUILD SUCCESSFUL

val config = RneConfig(
bearerToken = System.getenv("RNE_BEARER_TOKEN"),
subscriptionId = 42,
subscriberId = 7,
codeMotif = 1,
)
val connector = RneConnector(RneClient(config))
val result = connector.fetchFull("1616343A")
println("Dénomination : ${result.kyc.denomination_FR}")
println("Statut : ${result.kyc.STATUS}")
println("Capital TND : ${result.kyc.capital_TND}")
println("Dirigeants (n) : ${result.kyc.direction.size}")
println("UBO (n) : ${result.ubo.size}")
println("Source : ${result.source}") // LIVE ou CACHE

  1. Resilience4j circuit breaker (après 5 erreurs 5xx en 60 s → mode degraded).
  2. Rate limiter client (200 req/min / tenant) pour protéger le solde RNE.
  3. Fallback documentaire dans kyc-svc si degraded ou InsufficientBalance.
  4. Audit trail WORM de chaque appel RNE (request-id, subscription-id, status, hash réponse).
  5. Vault pour bearerToken avec rotation à chaud.
  6. Mapping vers schéma VitaKYC (kyc_business + kyc_business_officer[] + kyc_business_ubo[]).
  7. Métriques Prometheus : rne_subscription_balance, rne_ws_kyc_latency_ms, rne_errors_total{type}.
  8. Alerte proactive à 80 % consommation pour recharger avant épuisement.
  9. Horizon 2026 : bascule RNE 100 % en ligne S2 2026 (DIGIGO / MobileID) — adapter schémas à ce moment.

  • Preuve terrain à présenter en démo AO La Poste Tunisienne et prospects banques TN.
  • Time-to-market réduit : les tenants n’ont pas à développer eux-mêmes l’intégration RNE.
  • Économie de coût : gestion automatique du quota + cache évite les consommations inutiles (environ 30-40 % d’économie vs appels naïfs).
  • Robustesse : 7 erreurs métier typées = workflow KYB bien orienté côté agent.

POC validé le 2026-04-22 · 12 tests passants · aucune dépendance Spring.