import React, { useState, useEffect } from 'react'; import { View, Text, StyleSheet, ScrollView, Alert, Vibration, Platform, } from 'react-native'; import { Button, Card, TextInput, ActivityIndicator, Divider, Chip, } from 'react-native-paper'; import { RouteProp } from '@react-navigation/native'; import { StackNavigationProp } from '@react-navigation/stack'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { Formik } from 'formik'; import * as Yup from 'yup'; import Animated, { useSharedValue, useAnimatedStyle, withSpring, withSequence, withTiming, } from 'react-native-reanimated'; import { theme } from '../../theme/theme'; import { RootStackParamList } from '../../navigation/AppNavigator'; import { useWavePayment } from '../../contexts/WavePaymentContext'; import { WavePaymentService } from '../../services/WavePaymentService'; import { AnalyticsService } from '../../services/AnalyticsService'; import { HapticService } from '../../services/HapticService'; type WavePaymentScreenRouteProp = RouteProp; type WavePaymentScreenNavigationProp = StackNavigationProp; interface Props { route: WavePaymentScreenRouteProp; navigation: WavePaymentScreenNavigationProp; } /** * Écran de paiement Wave Money ultra moderne * * Interface intuitive et sécurisée pour les paiements Wave Money en Côte d'Ivoire : * - Design moderne avec animations fluides * - Validation en temps réel des numéros Wave * - Calcul automatique des frais * - Feedback visuel et haptique * - Gestion des erreurs élégante * - Support hors ligne avec synchronisation * * @author Lions Dev Team * @version 2.0.0 * @since 2025-01-16 */ const WavePaymentScreen: React.FC = ({ route, navigation }) => { const { type, amount, description, metadata } = route.params; const { initiatePayment, isLoading } = useWavePayment(); const [fees, setFees] = useState<{ base: string; fees: string; total: string } | null>(null); const [isCalculatingFees, setIsCalculatingFees] = useState(false); const [paymentStatus, setPaymentStatus] = useState<'idle' | 'processing' | 'success' | 'error'>('idle'); // Animations const cardScale = useSharedValue(1); const waveIconRotation = useSharedValue(0); const successScale = useSharedValue(0); useEffect(() => { // Animation d'entrée cardScale.value = withSpring(1, { damping: 15 }); // Calculer les frais automatiquement if (amount) { calculateFees(amount); } // Analytics AnalyticsService.trackEvent('wave_payment_screen_opened', { type, amount, }); }, [amount]); /** * Schéma de validation Yup pour le formulaire */ const validationSchema = Yup.object().shape({ phoneNumber: Yup.string() .required('Le numéro de téléphone est obligatoire') .matches(/^\+225[0-9]{8}$/, 'Format invalide. Utilisez +225XXXXXXXX') .test('wave-number', 'Ce numéro ne semble pas être un numéro Wave valide', (value) => { // Validation basique des numéros Wave CI if (!value) return false; const number = value.replace('+225', ''); // Les numéros Wave commencent généralement par 01, 05, 07 return /^(01|05|07)[0-9]{6}$/.test(number); }), amount: Yup.string() .required('Le montant est obligatoire') .test('min-amount', 'Montant minimum : 100 FCFA', (value) => { return value ? parseFloat(value) >= 100 : false; }) .test('max-amount', 'Montant maximum : 1,000,000 FCFA', (value) => { return value ? parseFloat(value) <= 1000000 : false; }), }); /** * Calcule les frais Wave Money */ const calculateFees = async (amount: string) => { setIsCalculatingFees(true); try { const result = await WavePaymentService.calculateFees(amount); setFees(result); // Animation de l'icône Wave waveIconRotation.value = withSequence( withTiming(360, { duration: 500 }), withTiming(0, { duration: 0 }) ); } catch (error) { console.error('Erreur calcul frais:', error); } finally { setIsCalculatingFees(false); } }; /** * Traite le paiement Wave Money */ const handlePayment = async (values: { phoneNumber: string; amount: string }) => { try { setPaymentStatus('processing'); HapticService.impact('medium'); // Animation de traitement cardScale.value = withSpring(0.95); const paymentRequest = { type, amount: values.amount, phoneNumber: values.phoneNumber, description, metadata, }; const result = await initiatePayment(paymentRequest); if (result.success) { setPaymentStatus('success'); successScale.value = withSpring(1); HapticService.success(); // Vibration de succès if (Platform.OS === 'android') { Vibration.vibrate([100, 200, 100]); } AnalyticsService.trackEvent('wave_payment_success', { type, amount: values.amount, transactionId: result.transactionId, }); Alert.alert( 'Paiement initié', `Votre paiement de ${values.amount} FCFA a été initié avec succès.\n\nTransaction ID: ${result.transactionId}`, [ { text: 'OK', onPress: () => navigation.goBack(), }, ] ); } else { throw new Error(result.error || 'Erreur lors du paiement'); } } catch (error) { setPaymentStatus('error'); HapticService.error(); AnalyticsService.trackEvent('wave_payment_error', { type, amount: values.amount, error: error.message, }); Alert.alert( 'Erreur de paiement', error.message || 'Une erreur est survenue lors du paiement. Veuillez réessayer.', [{ text: 'OK' }] ); } finally { cardScale.value = withSpring(1); } }; /** * Formate un numéro de téléphone Wave */ const formatPhoneNumber = (text: string) => { // Supprimer tous les caractères non numériques sauf + let cleaned = text.replace(/[^\d+]/g, ''); // Ajouter +225 si pas présent if (!cleaned.startsWith('+225')) { if (cleaned.startsWith('225')) { cleaned = '+' + cleaned; } else if (cleaned.startsWith('0')) { cleaned = '+225' + cleaned.substring(1); } else if (cleaned.length > 0 && !cleaned.startsWith('+')) { cleaned = '+225' + cleaned; } } // Limiter à +225 + 8 chiffres if (cleaned.length > 12) { cleaned = cleaned.substring(0, 12); } return cleaned; }; const cardAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: cardScale.value }], })); const waveIconAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${waveIconRotation.value}deg` }], })); const successAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ scale: successScale.value }], opacity: successScale.value, })); return ( {/* En-tête Wave Money */} Wave Money Paiement mobile sécurisé Côte d'Ivoire {/* Détails du paiement */} Détails du paiement Type : {getPaymentTypeLabel(type)} Description : {description} {/* Calcul des frais */} {fees && ( Montant : {fees.base} Frais Wave : {fees.fees} Total à payer : {fees.total} )} {isCalculatingFees && ( Calcul des frais... )} {/* Formulaire de paiement */} {({ handleChange, handleBlur, handleSubmit, values, errors, touched, setFieldValue }) => ( Informations de paiement { const formatted = formatPhoneNumber(text); setFieldValue('phoneNumber', formatted); }} onBlur={handleBlur('phoneNumber')} error={touched.phoneNumber && !!errors.phoneNumber} mode="outlined" style={styles.input} left={} placeholder="+225XXXXXXXX" keyboardType="phone-pad" maxLength={12} /> {touched.phoneNumber && errors.phoneNumber && ( {errors.phoneNumber} )} { handleChange('amount')(text); if (text && parseFloat(text) >= 100) { calculateFees(text); } }} onBlur={handleBlur('amount')} error={touched.amount && !!errors.amount} mode="outlined" style={styles.input} left={} placeholder="0" keyboardType="numeric" editable={!amount} // Désactiver si montant prédéfini /> {touched.amount && errors.amount && ( {errors.amount} )} {/* Bouton de paiement */} )} {/* Indicateur de succès */} {paymentStatus === 'success' && ( Paiement initié avec succès ! )} {/* Informations de sécurité */} Paiement sécurisé Vos informations sont protégées par un chiffrement de niveau bancaire. Wave Money est agréé par la BCEAO pour les services de paiement mobile. ); }; /** * Retourne le libellé du type de paiement */ const getPaymentTypeLabel = (type: string): string => { switch (type) { case 'cotisation': return 'Cotisation'; case 'adhesion': return 'Adhésion'; case 'aide': return 'Aide mutuelle'; case 'evenement': return 'Événement'; default: return 'Paiement'; } }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: theme.colors.background, padding: theme.custom.spacing.md, }, waveCard: { backgroundColor: theme.custom.colors.wave, marginBottom: theme.custom.spacing.md, borderRadius: theme.custom.dimensions.borderRadius.lg, }, waveHeader: { flexDirection: 'row', alignItems: 'center', padding: theme.custom.spacing.lg, }, waveInfo: { flex: 1, marginLeft: theme.custom.spacing.md, }, waveTitle: { fontSize: theme.custom.typography.sizes.xl, fontWeight: theme.custom.typography.weights.bold, color: theme.custom.colors.textOnPrimary, }, waveSubtitle: { fontSize: theme.custom.typography.sizes.sm, color: theme.custom.colors.textOnPrimary, opacity: 0.8, }, chip: { backgroundColor: 'rgba(255, 255, 255, 0.2)', }, chipText: { color: theme.custom.colors.textOnPrimary, fontSize: theme.custom.typography.sizes.xs, }, detailsCard: { marginBottom: theme.custom.spacing.md, padding: theme.custom.spacing.lg, }, formCard: { marginBottom: theme.custom.spacing.md, padding: theme.custom.spacing.lg, }, sectionTitle: { fontSize: theme.custom.typography.sizes.lg, fontWeight: theme.custom.typography.weights.semibold, color: theme.custom.colors.text, marginBottom: theme.custom.spacing.md, }, detailRow: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: theme.custom.spacing.sm, }, detailLabel: { fontSize: theme.custom.typography.sizes.base, color: theme.custom.colors.textSecondary, }, detailValue: { fontSize: theme.custom.typography.sizes.base, fontWeight: theme.custom.typography.weights.medium, color: theme.custom.colors.text, }, feesContainer: { marginTop: theme.custom.spacing.sm, }, feesValue: { fontSize: theme.custom.typography.sizes.base, fontWeight: theme.custom.typography.weights.medium, color: theme.custom.colors.warning, }, totalLabel: { fontSize: theme.custom.typography.sizes.lg, fontWeight: theme.custom.typography.weights.semibold, color: theme.custom.colors.text, }, totalValue: { fontSize: theme.custom.typography.sizes.lg, fontWeight: theme.custom.typography.weights.bold, color: theme.custom.colors.wave, }, divider: { marginVertical: theme.custom.spacing.sm, }, loadingContainer: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center', padding: theme.custom.spacing.md, }, loadingText: { marginLeft: theme.custom.spacing.sm, color: theme.custom.colors.textSecondary, }, input: { marginBottom: theme.custom.spacing.md, }, errorText: { color: theme.custom.colors.error, fontSize: theme.custom.typography.sizes.sm, marginTop: -theme.custom.spacing.sm, marginBottom: theme.custom.spacing.sm, }, payButton: { backgroundColor: theme.custom.colors.wave, marginTop: theme.custom.spacing.md, borderRadius: theme.custom.dimensions.borderRadius.md, }, payButtonContent: { height: theme.custom.dimensions.buttonHeights.lg, }, payButtonLabel: { fontSize: theme.custom.typography.sizes.lg, fontWeight: theme.custom.typography.weights.semibold, }, successContainer: { alignItems: 'center', padding: theme.custom.spacing.xl, }, successText: { fontSize: theme.custom.typography.sizes.lg, fontWeight: theme.custom.typography.weights.semibold, color: theme.custom.colors.success, marginTop: theme.custom.spacing.md, textAlign: 'center', }, securityCard: { backgroundColor: theme.custom.colors.success + '10', padding: theme.custom.spacing.lg, marginTop: theme.custom.spacing.md, }, securityHeader: { flexDirection: 'row', alignItems: 'center', marginBottom: theme.custom.spacing.sm, }, securityTitle: { fontSize: theme.custom.typography.sizes.base, fontWeight: theme.custom.typography.weights.semibold, color: theme.custom.colors.success, marginLeft: theme.custom.spacing.sm, }, securityText: { fontSize: theme.custom.typography.sizes.sm, color: theme.custom.colors.textSecondary, lineHeight: theme.custom.typography.lineHeights.relaxed * theme.custom.typography.sizes.sm, }, }); export default WavePaymentScreen;