first commit

This commit is contained in:
DahoudG
2025-08-20 21:00:35 +00:00
commit b2a23bdf89
583 changed files with 243074 additions and 0 deletions

View File

@@ -0,0 +1,433 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';
import { ApiService } from './ApiService';
import { AnalyticsService } from './AnalyticsService';
/**
* Service de paiement Wave Money pour l'application mobile UnionFlow
*
* Ce service gère toutes les interactions avec l'API Wave Money :
* - Initiation des paiements
* - Vérification du statut des transactions
* - Calcul des frais
* - Gestion hors ligne avec synchronisation
* - Cache des données pour performance
*
* @author Lions Dev Team
* @version 2.0.0
* @since 2025-01-16
*/
export interface WavePaymentRequest {
type: 'cotisation' | 'adhesion' | 'aide' | 'evenement';
amount: string;
phoneNumber: string;
description: string;
metadata?: any;
}
export interface WavePaymentResult {
success: boolean;
transactionId?: string;
waveTransactionId?: string;
status?: 'SUCCES' | 'EN_ATTENTE' | 'ECHEC';
message?: string;
error?: string;
}
export interface WaveTransactionStatus {
transactionId: string;
status: string;
message: string;
timestamp: string;
}
export interface WaveFees {
base: string;
fees: string;
total: string;
}
class WavePaymentServiceClass {
private readonly CACHE_KEY = 'wave_payment_cache';
private readonly PENDING_PAYMENTS_KEY = 'pending_wave_payments';
private readonly FEES_CACHE_KEY = 'wave_fees_cache';
private readonly CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
/**
* Initie un paiement Wave Money
*/
async initiatePayment(request: WavePaymentRequest): Promise<WavePaymentResult> {
try {
// Vérifier la connectivité
const netInfo = await NetInfo.fetch();
if (!netInfo.isConnected) {
// Mode hors ligne - sauvegarder pour synchronisation ultérieure
await this.savePendingPayment(request);
return {
success: false,
error: 'Pas de connexion internet. Le paiement sera traité dès que la connexion sera rétablie.',
};
}
// Valider la demande
this.validatePaymentRequest(request);
// Préparer les données pour l'API
const apiRequest = this.prepareApiRequest(request);
// Appeler l'API selon le type de paiement
let response;
switch (request.type) {
case 'cotisation':
response = await ApiService.post('/api/v1/payments/wave/cotisation', apiRequest);
break;
case 'adhesion':
response = await ApiService.post('/api/v1/payments/wave/adhesion', apiRequest);
break;
case 'aide':
response = await ApiService.post('/api/v1/payments/wave/aide-mutuelle', apiRequest);
break;
case 'evenement':
response = await ApiService.post('/api/v1/payments/wave/evenement', apiRequest);
break;
default:
throw new Error('Type de paiement non supporté');
}
// Traiter la réponse
const result = this.processPaymentResponse(response);
// Sauvegarder en cache pour consultation ultérieure
await this.cachePaymentResult(request, result);
// Analytics
AnalyticsService.trackEvent('wave_payment_initiated', {
type: request.type,
amount: request.amount,
success: result.success,
});
return result;
} catch (error) {
console.error('Erreur initiation paiement Wave:', error);
AnalyticsService.trackEvent('wave_payment_error', {
type: request.type,
amount: request.amount,
error: error.message,
});
return {
success: false,
error: this.getErrorMessage(error),
};
}
}
/**
* Vérifie le statut d'une transaction Wave
*/
async checkTransactionStatus(transactionId: string): Promise<WaveTransactionStatus> {
try {
const response = await ApiService.get(`/api/v1/payments/wave/status/${transactionId}`);
return {
transactionId: response.transactionId,
status: response.status,
message: response.message,
timestamp: response.timestamp,
};
} catch (error) {
console.error('Erreur vérification statut:', error);
throw new Error('Impossible de vérifier le statut de la transaction');
}
}
/**
* Calcule les frais Wave Money pour un montant donné
*/
async calculateFees(amount: string): Promise<WaveFees> {
try {
// Vérifier le cache d'abord
const cachedFees = await this.getCachedFees(amount);
if (cachedFees) {
return cachedFees;
}
const response = await ApiService.get(`/api/v1/payments/wave/fees?montant=${amount}`);
const fees: WaveFees = {
base: response.montantBase,
fees: response.frais,
total: response.montantTotal,
};
// Mettre en cache
await this.cacheFees(amount, fees);
return fees;
} catch (error) {
console.error('Erreur calcul frais:', error);
// Calcul local en cas d'erreur API
return this.calculateFeesLocally(amount);
}
}
/**
* Synchronise les paiements en attente
*/
async syncPendingPayments(): Promise<void> {
try {
const pendingPayments = await this.getPendingPayments();
if (pendingPayments.length === 0) {
return;
}
console.log(`Synchronisation de ${pendingPayments.length} paiements en attente`);
for (const payment of pendingPayments) {
try {
const result = await this.initiatePayment(payment.request);
if (result.success) {
// Supprimer de la liste des paiements en attente
await this.removePendingPayment(payment.id);
// Notifier l'utilisateur du succès
// NotificationService.showLocalNotification({
// title: 'Paiement synchronisé',
// body: `Votre paiement de ${payment.request.amount} FCFA a été traité avec succès.`,
// });
}
} catch (error) {
console.error('Erreur synchronisation paiement:', error);
}
}
} catch (error) {
console.error('Erreur synchronisation générale:', error);
}
}
/**
* Retourne l'historique des paiements Wave
*/
async getPaymentHistory(): Promise<any[]> {
try {
const cached = await AsyncStorage.getItem(this.CACHE_KEY);
if (cached) {
const data = JSON.parse(cached);
return data.payments || [];
}
return [];
} catch (error) {
console.error('Erreur récupération historique:', error);
return [];
}
}
// ==================== MÉTHODES PRIVÉES ====================
private validatePaymentRequest(request: WavePaymentRequest): void {
if (!request.amount || parseFloat(request.amount) <= 0) {
throw new Error('Montant invalide');
}
if (parseFloat(request.amount) < 100) {
throw new Error('Montant minimum : 100 FCFA');
}
if (parseFloat(request.amount) > 1000000) {
throw new Error('Montant maximum : 1,000,000 FCFA');
}
if (!request.phoneNumber || !this.isValidWaveNumber(request.phoneNumber)) {
throw new Error('Numéro Wave invalide');
}
if (!request.description) {
throw new Error('Description obligatoire');
}
}
private isValidWaveNumber(phoneNumber: string): boolean {
// Validation des numéros Wave CI : +225 suivi de 8 chiffres
const wavePattern = /^\+225[0-9]{8}$/;
return wavePattern.test(phoneNumber);
}
private prepareApiRequest(request: WavePaymentRequest): any {
return {
montant: request.amount,
numeroTelephone: request.phoneNumber,
description: request.description,
metadata: {
...request.metadata,
source: 'mobile_app',
version: '2.0.0',
},
};
}
private processPaymentResponse(response: any): WavePaymentResult {
if (response.transactionId) {
return {
success: true,
transactionId: response.transactionId,
waveTransactionId: response.waveTransactionId,
status: response.statut,
message: response.message,
};
} else {
return {
success: false,
error: response.message || 'Erreur lors du paiement',
};
}
}
private async savePendingPayment(request: WavePaymentRequest): Promise<void> {
try {
const pendingPayments = await this.getPendingPayments();
const newPayment = {
id: Date.now().toString(),
request,
timestamp: new Date().toISOString(),
};
pendingPayments.push(newPayment);
await AsyncStorage.setItem(
this.PENDING_PAYMENTS_KEY,
JSON.stringify(pendingPayments)
);
} catch (error) {
console.error('Erreur sauvegarde paiement en attente:', error);
}
}
private async getPendingPayments(): Promise<any[]> {
try {
const stored = await AsyncStorage.getItem(this.PENDING_PAYMENTS_KEY);
return stored ? JSON.parse(stored) : [];
} catch (error) {
console.error('Erreur récupération paiements en attente:', error);
return [];
}
}
private async removePendingPayment(paymentId: string): Promise<void> {
try {
const pendingPayments = await this.getPendingPayments();
const filtered = pendingPayments.filter(p => p.id !== paymentId);
await AsyncStorage.setItem(
this.PENDING_PAYMENTS_KEY,
JSON.stringify(filtered)
);
} catch (error) {
console.error('Erreur suppression paiement en attente:', error);
}
}
private async cachePaymentResult(request: WavePaymentRequest, result: WavePaymentResult): Promise<void> {
try {
const cached = await AsyncStorage.getItem(this.CACHE_KEY);
const data = cached ? JSON.parse(cached) : { payments: [] };
data.payments.unshift({
request,
result,
timestamp: new Date().toISOString(),
});
// Garder seulement les 50 derniers paiements
data.payments = data.payments.slice(0, 50);
await AsyncStorage.setItem(this.CACHE_KEY, JSON.stringify(data));
} catch (error) {
console.error('Erreur cache paiement:', error);
}
}
private async getCachedFees(amount: string): Promise<WaveFees | null> {
try {
const cached = await AsyncStorage.getItem(this.FEES_CACHE_KEY);
if (!cached) return null;
const data = JSON.parse(cached);
const entry = data[amount];
if (entry && Date.now() - entry.timestamp < this.CACHE_DURATION) {
return entry.fees;
}
return null;
} catch (error) {
console.error('Erreur récupération cache frais:', error);
return null;
}
}
private async cacheFees(amount: string, fees: WaveFees): Promise<void> {
try {
const cached = await AsyncStorage.getItem(this.FEES_CACHE_KEY);
const data = cached ? JSON.parse(cached) : {};
data[amount] = {
fees,
timestamp: Date.now(),
};
await AsyncStorage.setItem(this.FEES_CACHE_KEY, JSON.stringify(data));
} catch (error) {
console.error('Erreur cache frais:', error);
}
}
private calculateFeesLocally(amount: string): WaveFees {
const montant = parseFloat(amount);
let frais: number;
// Barème Wave Money Côte d'Ivoire
if (montant <= 1000) {
frais = 25;
} else if (montant <= 5000) {
frais = 50;
} else if (montant <= 25000) {
frais = montant * 0.01; // 1%
} else {
frais = 250; // Plafond
}
const total = montant + frais;
return {
base: `${montant.toLocaleString()} FCFA`,
fees: `${frais.toLocaleString()} FCFA`,
total: `${total.toLocaleString()} FCFA`,
};
}
private getErrorMessage(error: any): string {
if (error.response?.data?.message) {
return error.response.data.message;
}
if (error.message) {
return error.message;
}
return 'Une erreur inattendue est survenue';
}
}
export const WavePaymentService = new WavePaymentServiceClass();