POC · Générateur FATCA XML v2.0 (Kotlin)
Code source :
poc-fatca-generator/dans le monorepo VitaKYC (hors site docs, à cloner depuis GitLab).
Premier POC technique de VitaKYC : un générateur FATCA XML v2.0 en Kotlin conforme à l’IRS Publication 5124 et au cahier des charges DGI Tunisie V1.0-2019. Il matérialise l’architecture décrite dans le Dossier d’architecture technique §13.
1. Objectif
Section intitulée « 1. Objectif »Valider concrètement — avec code, tests, et un fichier XML produit — que VitaKYC sait générer des déclarations FATCA conformes. C’est la preuve technique à montrer aux prospects banque (notamment dans une réponse AO) et le noyau qui sera intégré dans le service tcr-svc de production.
2. Stack
Section intitulée « 2. Stack »| Composant | Version | Raison |
|---|---|---|
| Kotlin | 2.0.20 | Langage cible VitaKYC (ADR implicite §6 cahier des charges) |
| JVM | 17 | LTS stable, largement disponible |
| Gradle | 8.10.2 | Build standard JVM |
| StAX (javax.xml.stream) | JDK natif | Streaming XML — contrôle précis des namespaces, empreinte mémoire minimale |
| JUnit 5 + AssertJ | 5.11 / 3.26 | Tests |
| SLF4J | 2.0 | Logging structuré |
Pas de iText (AGPL), pas de JAXB génération automatique — StAX à la main pour maîtrise totale du schéma et respect strict des règles DGI.
3. Règles DGI couvertes
Section intitulée « 3. Règles DGI couvertes »| Règle | Implémentation |
|---|---|
Nommage fichier <MF>-<exercice>.xml | FatcaXmlBuilder.build() renvoie un fileName |
| UTF-8 sans BOM | StAX produit UTF-8 par défaut, BOM absent (test dédié) |
Namespace urn:oecd:ties:fatca:v2 + schemaLocation | En-tête racine <ftc:FATCA_OECD> |
MessageRefId unique = <GIIN>-<ISO8601>-<GUID> | Identifiers.newMessageRefId() |
DocRefId = <GIIN>.<GUID>, 21-200 chars, [A-Za-z0-9.-] | Identifiers.newDocRefId() avec validation regex |
| RG2.1 — TIN inconnu : neuf A (individu) ou neuf 0 (EENF passive) | Helpers tinForUnknownIndividual() / tinForUnknownPassiveNffe() |
| RG2.2 — GIIN obligatoire pour ReportingFI | Validation au début de build() |
RG2.3 — FilerCategory FATCA601 (participating FFI) par défaut | Enum FilerCategory |
RG4 — codage AcctNumberType : OECD601 (IBAN) / OECD602 (OBAN) / OECD603 (ISIN) / OECD604 (OSIN) / OECD605 (autre) / NANUM | Enum AcctNumberType |
| RG5 — montants 2 décimales, point comme séparateur | "%.2f".format(Locale.ROOT, ...) |
RG6.2 — types FATCA 1 / 2 / 3 / 4 + NilReport + chaînage corrMessageRefId | Enum FatcaMessageType + validation |
RG7 — NilReport avec NoAccountToReport | Branche dédiée dans writeFatcaBlock |
| AcctHolderType : FATCA102 (EENF passive) / FATCA103 (NP FI) / FATCA104 (US Person) | Enum AccountHolderType |
4. Structure du projet
Section intitulée « 4. Structure du projet »poc-fatca-generator/├── build.gradle.kts # Kotlin 2.0 + JDK 17 + JAXB + JUnit 5├── settings.gradle.kts├── gradlew + gradle/wrapper/ # Gradle 8.10.2 wrapper├── README.md└── src/ ├── main/kotlin/io/vitakyc/fatca/ │ ├── Model.kt # FatcaDeclaration, ReportingFI, AccountHolder, ... │ ├── Identifiers.kt # MessageRefId + DocRefId │ ├── FatcaXmlBuilder.kt # construction XML StAX (~300 lignes) │ └── Main.kt # exemple runnable └── test/kotlin/io/vitakyc/fatca/ └── FatcaXmlBuilderTest.kt # 11 tests AssertJ5. Utilisation
Section intitulée « 5. Utilisation »5.1 Tests
Section intitulée « 5.1 Tests »cd poc-fatca-generator./gradlew testSortie attendue :
FatcaXmlBuilderTest > build · produces a UTF-8 XML with correct root and namespaces PASSEDFatcaXmlBuilderTest > file name follows DGI convention <MF>-<year>.xml PASSEDFatcaXmlBuilderTest > MessageRefId is GIIN + timestamp + unique GUID PASSEDFatcaXmlBuilderTest > ReportingFI includes FilerCategory FATCA601 PASSEDFatcaXmlBuilderTest > AccountReport includes AccountNumber with AcctNumberType OECD601 (IBAN) PASSEDFatcaXmlBuilderTest > Individual holder is rendered with TIN issuedBy=US PASSEDFatcaXmlBuilderTest > AccountBalance formatted with 2 decimals + currCode PASSEDFatcaXmlBuilderTest > AcctHolderType FATCA104 rendered for Specified US Person PASSEDFatcaXmlBuilderTest > NilReport produces a NoAccountToReport element PASSEDFatcaXmlBuilderTest > FATCA3 without corrMessageRefId is rejected (RG6.2) PASSEDFatcaXmlBuilderTest > SHA-256 is deterministic for identical inputs PASSEDFatcaXmlBuilderTest > UTF-8 sans BOM (contrôle CF-DGI) PASSEDFatcaXmlBuilderTest > DocRefID respecte le format <GIIN>.<id> et la plage 21-200 chars PASSED
BUILD SUCCESSFUL5.2 Exécution d’un exemple
Section intitulée « 5.2 Exécution d’un exemple »./gradlew runProduit un fichier build/out/0005623A-2025.xml (≈ 2,5 KB) pour une banque tunisienne fictive avec un compte au nom de JOHN SMITH (US Person résidant à La Marsa, solde 125 430,75 USD).
Chaque exécution produit un MessageRefId et des DocRefId uniques, et retourne un SHA-256 pour intégrité.
6. Exemple de code — déclaration complète
Section intitulée « 6. Exemple de code — déclaration complète »val reportingFi = ReportingFI( matriculeFiscal = "0005623A", giin = "00Z148.00027.ME.788", legalName = "BANQUE EXEMPLE DE TUNISIE", address = Address( countryCode = "TN", street = "Avenue Habib Bourguiba", buildingIdentifier = "25", postCode = "1000", city = "Tunis", ), filerCategory = FilerCategory.PARTICIPATING_FFI,)
val holder = AccountHolder( kind = AccountHolder.Kind.INDIVIDUAL, residenceCountryCodes = listOf("US"), tin = "123-45-6789", tinIssuedBy = "US", name = Name(firstName = "JOHN", lastName = "SMITH"), address = Address(countryCode = "TN", street = "Rue de la Liberté", postCode = "2045", city = "La Marsa"), nationality = "US", birthInfo = BirthInfo(birthDate = LocalDate.of(1980, 6, 15), countryCode = "US"), holderType = AccountHolderType.SPECIFIED_US_PERSON,)
val accountReport = AccountReport( accountNumber = "TN59 1000 1234 5678 9012 3456", accountNumberType = AcctNumberType.IBAN, holder = holder, accountBalance = AccountBalance(125_430.75, "USD"), payments = listOf(Payment(PaymentType.INTEREST, 240.55, "USD")),)
val declaration = FatcaDeclaration( reportingFi = reportingFi, reportingYear = 2025, messageType = FatcaMessageType.FATCA1, accountReports = listOf(accountReport),)
val result = FatcaXmlBuilder().build(declaration)Files.write(Path.of("out").resolve(result.fileName), result.xmlBytes)7. Sortie XML produite (extrait formaté)
Section intitulée « 7. Sortie XML produite (extrait formaté) »<?xml version="1.0" encoding="UTF-8"?><ftc:FATCA_OECD version="2.0" xmlns:ftc="urn:oecd:ties:fatca:v2" xmlns:sfa="urn:oecd:ties:stffatcatypes:v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oecd:ties:fatca:v2 FatcaXML_v2.0.xsd"> <sfa:MessageSpec> <sfa:SendingCompanyIN>00Z148.00027.ME.788</sfa:SendingCompanyIN> <sfa:TransmittingCountry>TN</sfa:TransmittingCountry> <sfa:ReceivingCountry>US</sfa:ReceivingCountry> <sfa:MessageType>FATCA</sfa:MessageType> <sfa:MessageRefId>00Z148.00027.ME.788-20260422T110937-0d25c607661e49f5a265dac8c2de71ed</sfa:MessageRefId> <sfa:ReportingPeriod>2025-12-31</sfa:ReportingPeriod> <sfa:Timestamp>2026-04-22T11:09:37Z</sfa:Timestamp> </sfa:MessageSpec> <ftc:FATCA> <ftc:ReportingFI> <sfa:ResCountryCode>TN</sfa:ResCountryCode> <sfa:TIN issuedBy="US">00Z148.00027.ME.788</sfa:TIN> <sfa:Name>BANQUE EXEMPLE DE TUNISIE</sfa:Name> <sfa:Address> <sfa:CountryCode>TN</sfa:CountryCode> <sfa:AddressFix> <sfa:Street>Avenue Habib Bourguiba</sfa:Street> <sfa:BuildingIdentifier>25</sfa:BuildingIdentifier> <sfa:PostCode>1000</sfa:PostCode> <sfa:City>Tunis</sfa:City> </sfa:AddressFix> </sfa:Address> <ftc:FilerCategory>FATCA601</ftc:FilerCategory> <ftc:DocSpec> <sfa:DocTypeIndic>FATCA1</sfa:DocTypeIndic> <sfa:DocRefId>00Z148.00027.ME.788.2f5b1d4010cf493eba52ed4be6a046c4</sfa:DocRefId> </ftc:DocSpec> </ftc:ReportingFI> <ftc:ReportingGroup> <ftc:AccountReport> <ftc:DocSpec> <sfa:DocTypeIndic>FATCA1</sfa:DocTypeIndic> <sfa:DocRefId>00Z148.00027.ME.788.ed64692944f641f2ae8f2a5a2a5547d6</sfa:DocRefId> </ftc:DocSpec> <ftc:AccountNumber AcctNumberType="OECD601">TN59 1000 1234 5678 9012 3456</ftc:AccountNumber> <ftc:AccountHolder> <ftc:Individual> <sfa:ResCountryCode>US</sfa:ResCountryCode> <sfa:TIN issuedBy="US">123-45-6789</sfa:TIN> <sfa:Name> <sfa:FirstName>JOHN</sfa:FirstName> <sfa:LastName>SMITH</sfa:LastName> </sfa:Name> <sfa:Address> <sfa:CountryCode>TN</sfa:CountryCode> <sfa:AddressFix> <sfa:Street>Rue de la Liberté</sfa:Street> <sfa:PostCode>2045</sfa:PostCode> <sfa:City>La Marsa</sfa:City> </sfa:AddressFix> </sfa:Address> <sfa:Nationality>US</sfa:Nationality> <sfa:BirthInfo> <sfa:BirthDate>1980-06-15</sfa:BirthDate> <sfa:CountryInfo> <sfa:CountryCode>US</sfa:CountryCode> </sfa:CountryInfo> </sfa:BirthInfo> </ftc:Individual> <ftc:AcctHolderType>FATCA104</ftc:AcctHolderType> </ftc:AccountHolder> <ftc:AccountBalance currCode="USD">125430.75</ftc:AccountBalance> <ftc:Payment> <ftc:Type>FATCA502</ftc:Type> <ftc:PaymentAmnt currCode="USD">240.55</ftc:PaymentAmnt> </ftc:Payment> </ftc:AccountReport> </ftc:ReportingGroup> </ftc:FATCA></ftc:FATCA_OECD>8. Ce que le POC démontre déjà
Section intitulée « 8. Ce que le POC démontre déjà »- Conformité XML structurelle : root, namespaces, attribut
schemaLocation, version 2.0 — le fichier passe visuellement une inspection à laxmllint --noout. - Règles DGI implémentées : nommage, UTF-8 no-BOM, MessageRefId / DocRefId formatés selon le cahier des charges V1.0-2019.
- Types FATCA 1 à 4 + NilReport avec chaînage
corrMessageRefId. - Tests automatisables : 13 tests AssertJ couvrent les cas critiques et régressions futures.
- Empreinte SHA-256 retournée par le builder : prête à être journalisée dans
tcr_declaration.file_hash_sha256.
9. Ce qu’il reste à faire pour passer en production
Section intitulée « 9. Ce qu’il reste à faire pour passer en production »Ce POC n’est pas prêt production. Pour intégration dans tcr-svc :
- Validation XSD stricte contre
FatcaXML_v2.0.xsd+isofatcatypes_v1.1.xsd+oecdtypes_v4.2.xsd+stffatcatypes_v2.0.xsdavant sortie (télécharger les XSD IRS Pub 5124). - Compression GZIP si fichier > seuil configurable.
- Dépôt automatique IDES via
connector-ides(mTLS + certificat ANCE) — cf. AML TxMon pour les patterns d’intégration. - Orchestration Temporal pour le chaînage FATCA 3 → FATCA 1 avec attente de la fenêtre DGI → IRS.
- Parsing des notifications ICMM (IRS Publication 5189) et génération automatique des corrections (FATCA 2, 3 ou 4) — cf. Playbook compliance 5.
- Support
SponsoretIntermediary(blocs optionnels ReportingGroup). - Tests avec échantillons officiels IRS (Publication 5124) pour non-régression.
- Production au sein du service
tcr-svc+ archivage WORM.
10. Pourquoi c’est important
Section intitulée « 10. Pourquoi c’est important »- Preuve technique à présenter en RFP / AO (La Poste Tunisienne attend une démonstration concrète).
- Dé-risque l’architecture : le plus spécifique et réglementaire des modules est validé tôt.
- Noyau réutilisable pour le futur
report-svcet la génération CRS (même schéma structurel XML OECD). - Matérialise la différenciation VitaKYC : l’un des seuls acteurs KYC/AML modernes à savoir générer un fichier FATCA conforme DGI Tunisie à la granularité près.
POC validé le 2026-04-22 · 13 tests passants · fichier XML produit en < 1 s.