Onboarding & KYC
Le processus d'onboarding YaniPay combine une verification d'identite en deux niveaux : KYC Niveau 1 (YaniPay) et KYC Niveau 2 (BaaS provider), conformement aux exigences PSD2 et LCB-FT.
Onboarding obligatoire
Vue d'ensemble
YaniPay implemente un processus de verification d'identite en deux niveaux pour garantir la conformite reglementaire tout en offrant une experience utilisateur fluide. Le KYC Niveau 1 est gere directement par YaniPay via Onfido, tandis que le KYC Niveau 2 est delegue au BaaS provider (Swan ou Treezor) qui dispose de l'agrement bancaire.
Ce systeme a deux niveaux permet a YaniPay de pre-valider les utilisateurs avant de transmettre leur dossier au BaaS provider, reduisant ainsi le taux de rejet et accelerant le processus global d'ouverture de compte.
KYC 2 niveaux
Chaque niveau de verification a un perimetre distinct et des responsabilites clairement definies entre YaniPay et le BaaS provider.
KYC Niveau 1 — YaniPay
Verification initiale geree par YaniPay via Onfido. Effectuee lors de l'inscription sur l'application mobile.
- Verification email
- Verification telephone (OTP)
- Document d'identite (CNI, passeport)
- Selfie avec liveness detection (Onfido)
KYC Niveau 2 — Swan / Treezor
Verification approfondie effectuee par le BaaS provider pour l'ouverture du compte bancaire et l'emission de l'IBAN.
- Justificatif de domicile (moins de 3 mois)
- Verification bancaire (origine des fonds)
- Screening AML / LCB-FT (listes de sanctions)
- Conformite PSD2 / SCA
Repartition des responsabilites
Flux d'onboarding complet
Le diagramme ci-dessous illustre le flux complet d'onboarding, de l'inscription initiale de l'utilisateur jusqu'a l'approbation finale par le BaaS provider. Les etapes asynchrones (webhooks) sont indiquees separement.
Inscription
Email + mot de passe
KYC L1
Onfido (identite)
KYC L2
BaaS (conformite)
Approuve
Compte actif
Operationnel
IBAN + Carte
Interface OnboardUserInput
L'interface OnboardUserInputdefinit les donnees necessaires pour initier l'onboarding d'un utilisateur aupres du BaaS provider. Cette interface est commune a Swan et Treezor grace a la couche d'abstraction BaaS.
1 interface OnboardUserInput { 2 platformUserId: string; 3 email: string; 4 firstName: string; 5 lastName: string; 6 birthDate?: string; 7 country?: string; // ISO 3166-1 alpha-2 8 companyName?: string; // Business onboarding 9 companyRegistrationNumber?: string; 10 } 11 12 // Usage 13 const result = await baas.onboardUser({ 14 platformUserId: 'usr_abc123', 15 email: 'user@example.com', 16 firstName: 'Marie', 17 lastName: 'Dupont', 18 birthDate: '1990-05-15', 19 country: 'FR', 20 }); 21 // result.onboardingUrl -> redirect user here
La methode onboardUser()retourne un objet contenant l'URL d'onboarding heberge et l'identifiant unique attribue par le BaaS provider. L'URL doit etre ouverte dans un navigateur in-app pour que l'utilisateur complete le KYC Niveau 2.
1 interface OnboardUserResult { 2 onboardingUrl: string; // URL hosted onboarding (Swan/Treezor) 3 providerUserId: string; // ID utilisateur chez le BaaS provider 4 status: 'PENDING' | 'IN_PROGRESS' | 'APPROVED' | 'REJECTED'; 5 expiresAt?: string; // Expiration de l'URL d'onboarding 6 }
Swan Onboarding (hosted)
Swan propose un onboarding heberge (hosted onboarding) qui gere automatiquement la collecte de documents, la verification d'identite et le screening AML. L'utilisateur est redirige vers une page Swan securisee ou il complete les etapes de verification.
Onboarding heberge simplifie
Le flux Swan utilise un navigateur in-app (via @swan-io/react-native-browser) pour afficher la page d'onboarding dans l'application mobile sans redirection externe. A la fin du processus, Swan envoie un webhook a YaniPay pour confirmer le statut de l'onboarding.
Duree moyenne
5-10 minutes pour l'utilisateur. Validation en 24-48h par Swan.
Pays supportes
Tous les pays de l'Espace Economique Europeen (EEE) + DOM-TOM francais.
Conformite
PSD2, LCB-FT, RGPD. Swan est agree par la Banque de France (ACPR).
1 import { swanClient } from './client'; 2 3 export async function initiateSwanOnboarding(input: OnboardUserInput) { 4 const mutation = ` 5 mutation OnboardIndividualAccountHolder($input: OnboardIndividualAccountHolderInput!) { 6 onboardIndividualAccountHolder(input: $input) { 7 ... on OnboardIndividualAccountHolderSuccessPayload { 8 onboarding { 9 id 10 onboardingUrl 11 statusInfo { 12 status 13 } 14 } 15 } 16 ... on ValidationRejection { 17 message 18 fields { path message } 19 } 20 } 21 } 22 `; 23 24 const variables = { 25 input: { 26 email: input.email, 27 language: 'fr', 28 redirectUrl: `${process.env.APP_URL}/banking/onboarding/callback`, 29 accountName: `${input.firstName} ${input.lastName}`, 30 accountCountry: input.country || 'FRA', 31 }, 32 }; 33 34 const result = await swanClient.request(mutation, variables); 35 return { 36 onboardingUrl: result.onboarding.onboardingUrl, 37 providerUserId: result.onboarding.id, 38 status: 'PENDING' as const, 39 }; 40 }
Integration mobile
L'application mobile React Native utilise un navigateur in-app pour afficher la page d'onboarding heberge par Swan. Cela permet a l'utilisateur de completer le KYC Niveau 2 sans quitter l'application.
1 import { useState } from 'react'; 2 import { View, Text, ActivityIndicator } from 'react-native'; 3 import { openBrowserAsync } from '@swan-io/react-native-browser'; 4 import { useAuth } from '@/src/hooks/useAuth'; 5 import { api } from '@/src/services/api'; 6 7 export default function BankingOnboardingScreen() { 8 const { token } = useAuth(); 9 const [loading, setLoading] = useState(false); 10 const [status, setStatus] = useState<string | null>(null); 11 12 const startOnboarding = async () => { 13 setLoading(true); 14 try { 15 const { data } = await api.post('/wallet/onboarding', { 16 kycLevel: 2, 17 }); 18 19 if (data.onboardingUrl) { 20 await openBrowserAsync(data.onboardingUrl, { 21 dismissButtonStyle: 'close', 22 onClose: () => refreshOnboardingStatus(), 23 }); 24 } 25 } catch (error) { 26 console.error('Onboarding failed:', error); 27 } finally { 28 setLoading(false); 29 } 30 }; 31 32 const refreshOnboardingStatus = async () => { 33 const { data } = await api.get('/wallet/onboarding/status'); 34 setStatus(data.status); 35 }; 36 37 return ( 38 <View style={{ flex: 1, padding: 16 }}> 39 <Text style={{ fontSize: 24, fontWeight: 'bold' }}> 40 Verification bancaire 41 </Text> 42 <Text style={{ marginTop: 8, color: '#666' }}> 43 Completez la verification pour activer votre compte IBAN. 44 </Text> 45 {/* Onboarding button and status display */} 46 </View> 47 ); 48 }
Callback apres onboarding
refreshOnboardingStatus()est appelee pour mettre a jour le statut d'onboarding. Le statut final (APPROVED ou REJECTED) arrive via webhook asynchrone et est affiche lors de la prochaine ouverture de l'application.Endpoints API
Les endpoints suivants permettent de gerer le cycle de vie complet de l'onboarding depuis l'application mobile.
| Methode | Endpoint | Description |
|---|---|---|
| POST | /api/v1/mobile/wallet/onboarding | Demarre l'onboarding BaaS pour l'utilisateur authentifie |
| GET | /api/v1/mobile/wallet/onboarding/status | Recupere le statut d'onboarding (PENDING, IN_PROGRESS, APPROVED, REJECTED) |
| POST | /api/v1/mobile/wallet/onboarding/retry | Relance l'onboarding apres un echec (document expire, erreur technique) |
1 // POST /api/v1/mobile/wallet/onboarding 2 const response = await fetch('/api/v1/mobile/wallet/onboarding', { 3 method: 'POST', 4 headers: { 5 'Content-Type': 'application/json', 6 'Authorization': `Bearer ${token}`, 7 }, 8 body: JSON.stringify({ kycLevel: 2 }), 9 }); 10 11 const { onboardingUrl, status } = await response.json(); 12 // onboardingUrl: "https://identity.swan.io/onboarding/..." 13 // status: "PENDING"
1 // GET /api/v1/mobile/wallet/onboarding/status 2 const response = await fetch('/api/v1/mobile/wallet/onboarding/status', { 3 headers: { 4 'Authorization': `Bearer ${token}`, 5 }, 6 }); 7 8 const { status, updatedAt, details } = await response.json(); 9 // status: "APPROVED" | "PENDING" | "IN_PROGRESS" | "REJECTED" 10 // updatedAt: "2025-03-15T14:30:00Z" 11 // details: { kycLevel1: "APPROVED", kycLevel2: "APPROVED" }
Gestion des erreurs
Plusieurs types d'erreurs peuvent survenir lors du processus d'onboarding. Chaque erreur est identifiee par un code unique et doit etre traitee de maniere appropriee.
IDENTITY_VERIFICATION_FAILEDLa verification d'identite Onfido a echoue. Le document est illisible, expire ou ne correspond pas au selfie.
Action : Demander a l'utilisateur de re-soumettre ses documents avec de meilleures photos.
DOCUMENT_EXPIREDLe document d'identite soumis a expire. Passeport ou carte d'identite non valide.
Action : Inviter l'utilisateur a fournir un document en cours de validite.
AML_SCREENING_FAILEDL'utilisateur a ete detecte dans les listes de sanctions (LCB-FT). Le BaaS provider refuse l'onboarding.
Action : Bloquer l'acces aux services bancaires. Remonter le cas au service conformite YaniPay.
1 export enum OnboardingErrorCode { 2 IDENTITY_VERIFICATION_FAILED = 'IDENTITY_VERIFICATION_FAILED', 3 DOCUMENT_EXPIRED = 'DOCUMENT_EXPIRED', 4 AML_SCREENING_FAILED = 'AML_SCREENING_FAILED', 5 PROVIDER_UNAVAILABLE = 'PROVIDER_UNAVAILABLE', 6 ONBOARDING_EXPIRED = 'ONBOARDING_EXPIRED', 7 DUPLICATE_ACCOUNT = 'DUPLICATE_ACCOUNT', 8 } 9 10 export function handleOnboardingError(code: OnboardingErrorCode): { 11 retryable: boolean; 12 userMessage: string; 13 } { 14 switch (code) { 15 case OnboardingErrorCode.IDENTITY_VERIFICATION_FAILED: 16 return { 17 retryable: true, 18 userMessage: 'La verification a echoue. Veuillez re-soumettre vos documents.', 19 }; 20 case OnboardingErrorCode.DOCUMENT_EXPIRED: 21 return { 22 retryable: true, 23 userMessage: 'Votre document est expire. Fournissez un document valide.', 24 }; 25 case OnboardingErrorCode.AML_SCREENING_FAILED: 26 return { 27 retryable: false, 28 userMessage: 'Votre compte ne peut pas etre ouvert. Contactez le support.', 29 }; 30 case OnboardingErrorCode.PROVIDER_UNAVAILABLE: 31 return { 32 retryable: true, 33 userMessage: 'Service temporairement indisponible. Reessayez plus tard.', 34 }; 35 case OnboardingErrorCode.ONBOARDING_EXPIRED: 36 return { 37 retryable: true, 38 userMessage: 'Votre session a expire. Veuillez recommencer l\'onboarding.', 39 }; 40 case OnboardingErrorCode.DUPLICATE_ACCOUNT: 41 return { 42 retryable: false, 43 userMessage: 'Un compte existe deja avec ces informations.', 44 }; 45 } 46 }