Error Handling
Guide complet de gestion des erreurs API : codes HTTP, erreurs métier, network errors et bonnes pratiques.
Overview
L'API mobile YaniPay utilise des codes HTTP standards combinés à des codes d'erreur métier pour une gestion précise des erreurs. Chaque réponse d'erreur inclut un code, un message et éventuellement des détails additionnels.
Client Errors
4xx - Erreurs de requête client
Server Errors
5xx - Erreurs côté serveur
Network Errors
Erreurs de connectivité
Error Response Format
Format standard des réponses d'erreur.
// Structure d'erreur standard
interface APIError {
error: {
code: string; // Code d'erreur métier
message: string; // Message human-readable
details?: { // Détails additionnels (validation, etc.)
field?: string;
reason?: string;
[key: string]: unknown;
}[];
timestamp: string; // ISO 8601
requestId: string; // Pour le debugging/support
};
}
// Exemple: Erreur de validation
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Les données fournies sont invalides",
"details": [
{ "field": "email", "reason": "Format email invalide" },
{ "field": "amount", "reason": "Le montant doit être positif" }
],
"timestamp": "2026-02-08T14:30:00Z",
"requestId": "req_abc123"
}
}
// Exemple: Erreur métier
{
"error": {
"code": "INSUFFICIENT_FUNDS",
"message": "Solde insuffisant pour effectuer cette opération",
"details": [
{ "currentBalance": 50.00, "requestedAmount": 100.00 }
],
"timestamp": "2026-02-08T14:30:00Z",
"requestId": "req_def456"
}
}Error Handling
Implémentation centralisée de la gestion des erreurs.
// Types d'erreurs
type ErrorType =
| 'NETWORK_ERROR'
| 'TIMEOUT'
| 'AUTH_ERROR'
| 'VALIDATION_ERROR'
| 'BUSINESS_ERROR'
| 'SERVER_ERROR'
| 'UNKNOWN_ERROR';
interface AppError {
type: ErrorType;
code: string;
message: string;
details?: unknown;
originalError?: unknown;
isRetryable: boolean;
}
// Handler centralisé dans l'interceptor Axios
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const appError = transformToAppError(error);
// Log pour debugging
console.error('[API Error]', {
type: appError.type,
code: appError.code,
message: appError.message,
url: error.config?.url,
});
// Gestion spécifique par type
switch (appError.type) {
case 'AUTH_ERROR':
if (error.response?.status === 401) {
// Tente refresh token
try {
await authStore.refreshSession();
// Retry la requête originale
return apiClient(error.config);
} catch (refreshError) {
// Redirect to login
await authStore.logout();
router.replace('/login');
}
}
break;
case 'NETWORK_ERROR':
// Ajoute à la queue offline si applicable
if (isOfflineCapableRequest(error.config)) {
await offlineQueue.enqueue({
type: getOperationType(error.config),
payload: error.config.data,
priority: 'normal',
maxRetries: 5,
});
return { data: { queued: true } };
}
break;
}
throw appError;
}
);
// Transformation de l'erreur Axios en AppError
function transformToAppError(error: AxiosError): AppError {
// Network error (pas de réponse)
if (!error.response) {
if (error.code === 'ECONNABORTED') {
return {
type: 'TIMEOUT',
code: 'REQUEST_TIMEOUT',
message: 'La requête a expiré. Vérifiez votre connexion.',
isRetryable: true,
originalError: error,
};
}
return {
type: 'NETWORK_ERROR',
code: 'NETWORK_UNAVAILABLE',
message: 'Impossible de contacter le serveur.',
isRetryable: true,
originalError: error,
};
}
const { status, data } = error.response;
const apiError = data?.error;
// Auth errors
if (status === 401 || status === 403) {
return {
type: 'AUTH_ERROR',
code: apiError?.code || 'AUTH_FAILED',
message: apiError?.message || 'Authentification requise',
isRetryable: status === 401,
originalError: error,
};
}
// Validation errors
if (status === 400 || status === 422) {
return {
type: 'VALIDATION_ERROR',
code: apiError?.code || 'VALIDATION_FAILED',
message: apiError?.message || 'Données invalides',
details: apiError?.details,
isRetryable: false,
originalError: error,
};
}
// Server errors
if (status >= 500) {
return {
type: 'SERVER_ERROR',
code: apiError?.code || 'SERVER_ERROR',
message: 'Une erreur serveur est survenue. Réessayez plus tard.',
isRetryable: true,
originalError: error,
};
}
// Business errors (autres 4xx)
return {
type: 'BUSINESS_ERROR',
code: apiError?.code || 'UNKNOWN',
message: apiError?.message || 'Une erreur est survenue',
details: apiError?.details,
isRetryable: false,
originalError: error,
};
}Client Errors (4xx)
Erreurs causées par la requête client. Ces erreurs ne doivent généralement pas être retryées (sauf 401 et 429).
| Code | Nom | Description | Action | Retry |
|---|---|---|---|---|
| 400 | Bad Request | Requête mal formée ou paramètres invalides | Afficher les erreurs de validation | |
| 401 | Unauthorized | Token absent ou invalide | Tenter refresh token, sinon rediriger vers login | |
| 403 | Forbidden | Accès refusé (KYC insuffisant, permissions) | Afficher message explicatif, proposer KYC | |
| 404 | Not Found | Ressource inexistante | Afficher écran "non trouvé" | |
| 409 | Conflict | Conflit de données (ex: email déjà utilisé) | Afficher le conflit spécifique | |
| 422 | Validation Error | Données invalides (format, règles métier) | Afficher erreurs champ par champ | |
| 429 | Rate Limited | Trop de requêtes | Attendre avec backoff, afficher message d'attente |
Business Error Codes
// Codes d'erreur métier courants
const BUSINESS_ERRORS = {
// Wallet & Payments
INSUFFICIENT_FUNDS: 'Solde insuffisant',
DAILY_LIMIT_EXCEEDED: 'Limite journalière dépassée',
INVALID_BENEFICIARY: 'Bénéficiaire invalide',
PAYMENT_REJECTED: 'Paiement refusé',
CARD_FROZEN: 'Carte gelée',
CARD_EXPIRED: 'Carte expirée',
// KYC & Identity
KYC_REQUIRED: 'Vérification d\'identité requise',
KYC_PENDING: 'Vérification en cours',
KYC_REJECTED: 'Vérification refusée',
DOCUMENT_INVALID: 'Document invalide ou illisible',
// Account
ACCOUNT_LOCKED: 'Compte verrouillé',
ACCOUNT_SUSPENDED: 'Compte suspendu',
EMAIL_ALREADY_EXISTS: 'Email déjà utilisé',
PHONE_ALREADY_EXISTS: 'Téléphone déjà utilisé',
// Auth
INVALID_CREDENTIALS: 'Identifiants incorrects',
TOO_MANY_ATTEMPTS: 'Trop de tentatives',
SESSION_EXPIRED: 'Session expirée',
DEVICE_NOT_TRUSTED: 'Appareil non reconnu',
};Server Errors (5xx)
Erreurs côté serveur. Ces erreurs sont généralement retryables avec un backoff exponentiel.
| Code | Nom | Description | Action |
|---|---|---|---|
| 500 | Internal Server Error | Erreur serveur inattendue | Retry avec backoff, afficher erreur générique |
| 502 | Bad Gateway | Erreur de proxy/load balancer | Retry automatique |
| 503 | Service Unavailable | Service en maintenance ou surchargé | Retry avec backoff, afficher message d'indisponibilité |
| 504 | Gateway Timeout | Timeout du proxy | Retry automatique |
Network Errors
Erreurs de connectivité sans réponse HTTP.
// Détection et gestion des erreurs réseau
import NetInfo from '@react-native-community/netinfo';
// Hook pour surveiller la connexion
function useNetworkStatus() {
const [isConnected, setIsConnected] = useState(true);
const [connectionType, setConnectionType] = useState<string | null>(null);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected ?? false);
setConnectionType(state.type);
// Si reconnecté, déclenche la sync
if (state.isConnected) {
offlineQueue.attemptSync();
}
});
return () => unsubscribe();
}, []);
return { isConnected, connectionType };
}
// UI component pour afficher l'état offline
function OfflineBanner() {
const { isConnected } = useNetworkStatus();
if (isConnected) return null;
return (
<View style={styles.banner}>
<WifiOff size={16} color="#fff" />
<Text style={styles.text}>
Mode hors ligne - Les modifications seront synchronisées
</Text>
</View>
);
}
// Error boundary pour les erreurs réseau
function handleNetworkError(error: AppError) {
if (error.type === 'NETWORK_ERROR') {
showToast({
type: 'warning',
message: 'Connexion perdue. Vos actions sont sauvegardées.',
duration: 5000,
});
} else if (error.type === 'TIMEOUT') {
showToast({
type: 'error',
message: 'La requête a pris trop de temps. Réessayez.',
action: { label: 'Réessayer', onPress: () => retry() },
});
}
}Best Practices
Recommandations pour une gestion optimale des erreurs.
Retry avec Exponential Backoff
Toujours utiliser un backoff exponentiel pour les retries afin d'éviter la surcharge serveur.
Messages Utilisateur Clairs
Ne jamais afficher de messages techniques. Traduire en messages compréhensibles pour l'utilisateur.
Ne Pas Exposer les Détails d'Erreur
Logger les détails techniques mais ne pas les afficher à l'utilisateur (sécurité).
Gérer les Erreurs 401 Silencieusement
Tenter le refresh token automatiquement avant d'afficher une erreur d'auth.
// Mapping des codes d'erreur vers des messages utilisateur
const userFriendlyMessages: Record<string, string> = {
INSUFFICIENT_FUNDS: 'Votre solde est insuffisant pour cette opération.',
KYC_REQUIRED: 'Veuillez vérifier votre identité pour continuer.',
CARD_FROZEN: 'Votre carte est temporairement gelée. Dégélez-la dans les paramètres.',
TOO_MANY_ATTEMPTS: 'Trop de tentatives. Réessayez dans quelques minutes.',
NETWORK_ERROR: 'Vérifiez votre connexion internet et réessayez.',
SERVER_ERROR: 'Un problème technique est survenu. Nous travaillons à le résoudre.',
};
function getDisplayMessage(error: AppError): string {
return userFriendlyMessages[error.code] || error.message || 'Une erreur est survenue.';
}