welloo_sdk 0.0.54
welloo_sdk: ^0.0.54 copied to clipboard
Package de transaction Welloo
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:welloo_sdk/welloo_sdk.dart';
import 'package:welloo_sdk/src/shared/config/transaction_verification_config.dart';
import 'package:welloo_sdk/src/shared/services/logger_service.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Load environment variables
try {
await dotenv.load(fileName: '.env');
} catch (e) {
debugPrint('⚠️ .env file not found. Please create one from .env.example');
}
// Configure logger
LoggerService().configure(
minLevel: LogLevel.debug,
enabledInProduction: true,
);
// Initialize Welloo SDK
await WellooSdk.init();
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welloo SDK - Nouveau Système',
theme: ThemeData(
colorSchemeSeed: Colors.blue,
useMaterial3: true,
),
home: const DemoWello(),
);
}
}
class DemoWello extends StatefulWidget {
const DemoWello({super.key});
@override
State<DemoWello> createState() => _DemoWelloState();
}
class _DemoWelloState extends State<DemoWello> with WidgetsBindingObserver {
String? _lastTransactionRef;
final _sdk = WellooSdk();
// Configurations disponibles
TransactionVerificationConfig _currentConfig = TransactionVerificationConfig.waveConfig;
String _configName = 'Wave Config (2s, 150x)';
// Status en temps réel
String _currentStatus = 'Aucune transaction en cours';
bool _isRegistered = false;
String? _currentUserName;
String get _accessToken => dotenv.env['ACCESS_TOKEN'] ?? '';
String get _refreshToken => dotenv.env['REFRESH_TOKEN'] ?? '';
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_printWelcomeMessage();
_checkSession();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
// Le SDK gère automatiquement le cycle de vie
// Les tokens restent en cache même en arrière-plan
debugPrint('📱 App lifecycle state: ${state.name}');
}
Future<void> _checkSession() async {
// Vérifier si le SDK est initialisé
if (!WellooSdk.isInitialized) {
setState(() {
_currentStatus = '❌ SDK non initialisé';
});
return;
}
// Vérifier si des tokens valides sont présents
final hasTokens = await WellooSdk.hasValidTokens();
if (hasTokens) {
final result = await _sdk.getCurrentClient();
if (result.isSuccess && result.data != null) {
final client = result.data!;
setState(() {
_isRegistered = true;
_currentUserName = client.nom;
_currentStatus = '✅ Session active: ${client.nom}';
});
debugPrint('✅ Session restaurée: ${client.nom}');
} else {
setState(() {
_isRegistered = false;
_currentStatus = '⚠️ Session expirée';
});
debugPrint('⚠️ Session expirée: ${result.error}');
}
} else {
setState(() {
_isRegistered = false;
_currentStatus = '📝 Aucune session trouvée';
});
debugPrint('📝 Aucune session trouvée');
}
}
Future<void> _registerClient() async {
if (_accessToken.isEmpty || _refreshToken.isEmpty) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please configure ACCESS_TOKEN and REFRESH_TOKEN in .env file',
),
backgroundColor: Colors.red,
duration: Duration(seconds: 3),
),
);
return;
}
debugPrint('\n📝 Enregistrement du client...');
final result = await _sdk.registerClient(
accessToken: _accessToken,
refreshToken: _refreshToken,
);
if (result.isSuccess && result.data != null) {
final client = result.data!;
setState(() {
_isRegistered = true;
_currentUserName = client.nom;
_currentStatus = '✅ Client enregistré: ${client.nom}';
});
debugPrint('✅ Client enregistré: ${client.nom}');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Client enregistré: ${client.nom}'),
backgroundColor: Colors.green,
),
);
} else {
setState(() {
_currentStatus = '❌ Erreur: ${result.error}';
});
debugPrint('❌ Erreur: ${result.error}');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur: ${result.error}'),
backgroundColor: Colors.red,
),
);
}
}
Future<void> _logout() async {
debugPrint('\n👋 Déconnexion...');
final result = await _sdk.logout();
if (result.isSuccess) {
setState(() {
_isRegistered = false;
_currentUserName = null;
_currentStatus = '👋 Déconnecté';
_lastTransactionRef = null;
});
debugPrint('✅ Déconnexion réussie');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Déconnexion réussie'),
backgroundColor: Colors.green,
),
);
} else {
debugPrint('❌ Erreur lors de la déconnexion: ${result.error}');
}
}
void _printWelcomeMessage() {
debugPrint('\n${'=' * 60}');
debugPrint('🚀 WELLOO SDK - NOUVEAU SYSTÈME DE VÉRIFICATION');
debugPrint('=' * 60);
debugPrint('Configuration: $_configName');
debugPrint('Stratégie: ${_currentConfig.strategy.name}');
debugPrint('Polling: ${_currentConfig.pollingConfig.maxAttempts}x @ ${_currentConfig.pollingConfig.interval.inSeconds}s');
debugPrint('Deeplink: ${_currentConfig.deeplinkConfig.enabled ? "Activé" : "Désactivé"}');
debugPrint('Circuit Breaker: ${_currentConfig.enableCircuitBreaker ? "Activé" : "Désactivé"}');
debugPrint('Retry Policy: Max ${_currentConfig.maxRetries} tentatives');
debugPrint('=' * 60);
debugPrint('\n');
}
Future<void> _handleDeposit() async {
// Check if tokens are configured
if (_accessToken.isEmpty || _refreshToken.isEmpty) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Please configure ACCESS_TOKEN and REFRESH_TOKEN in .env file',
),
backgroundColor: Colors.red,
duration: Duration(seconds: 3),
),
);
return;
}
debugPrint('\n${'=' * 60}');
debugPrint('📦 INITIATION DÉPÔT');
debugPrint('=' * 60);
Navigator.of(context).push<TransactionResult>(
MaterialPageRoute(
builder: (_) => WellooDeposit(
packageName: 'com.example.welloo',
accessToken: _accessToken,
refreshToken: _refreshToken,
waitResponse: (response) {
_handleTransactionResponse(Map<String, dynamic>.from(response));
},
onError: (error) {
_handleTransactionError(Map<String, dynamic>.from(error));
},
),
),
);
}
void _handleTransactionResponse(Map<String, dynamic> response) {
debugPrint('\n✅ RÉPONSE TRANSACTION:');
debugPrint('Status: ${response['status']}');
debugPrint('Reference: ${response['reference_transaction']}');
debugPrint('Data: $response');
if (response['reference_transaction'] != null) {
setState(() {
_lastTransactionRef = response['reference_transaction'];
_currentStatus = 'Transaction ${response['status']}';
});
}
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Transaction ${response['status']}'),
backgroundColor: response['status'] == 'SUCCEEDED' ? Colors.green : Colors.orange,
),
);
}
void _handleTransactionError(Map<String, dynamic> error) {
debugPrint('\n❌ ERREUR TRANSACTION:');
debugPrint('Description: ${error['description']}');
debugPrint('Details: $error');
setState(() {
_currentStatus = 'Erreur: ${error['description']}';
});
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Erreur: ${error['description']}'),
backgroundColor: Colors.red,
),
);
}
Future<void> _checkTransactionStatus() async {
if (_lastTransactionRef == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No transaction to check')),
);
return;
}
debugPrint('\n🔍 VÉRIFICATION MANUELLE: $_lastTransactionRef');
try {
final result = await _sdk.checkDepositStatus(
reference: _lastTransactionRef!,
);
if (!mounted) return;
if (result.isSuccess && result.data != null) {
final transaction = result.data!;
debugPrint('Status: ${transaction.status}');
debugPrint('Reference: ${transaction.reference}');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Status: ${transaction.status}\nReference: ${transaction.reference}',
),
backgroundColor:
transaction.status == 'success' ? Colors.green : Colors.orange,
),
);
} else {
debugPrint('❌ Error: ${result.error}');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error: ${result.error ?? "Unknown error"}'),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
debugPrint('❌ Exception: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error: $e')),
);
}
}
void _changeConfiguration() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Choisir une Configuration'),
content: SizedBox(
width: double.maxFinite,
child: ListView(
shrinkWrap: true,
children: [
_buildConfigOption('Wave Config (2s, 150x)', TransactionVerificationConfig.waveConfig),
_buildConfigOption('Wello Config (3s, 60x)', TransactionVerificationConfig.welloConfig),
_buildConfigOption('Production (5s, 60x)', TransactionVerificationConfig.productionConfig),
_buildConfigOption('Development (1s, 30x)', TransactionVerificationConfig.developmentConfig),
_buildConfigOption('Polling Only', TransactionVerificationConfig.pollingOnlyConfig),
_buildConfigOption('Deeplink Only', TransactionVerificationConfig.deeplinkOnlyConfig),
],
),
),
),
);
}
Widget _buildConfigOption(String name, TransactionVerificationConfig config) {
return ListTile(
title: Text(name),
subtitle: Text(
'Stratégie: ${config.strategy.name}\n'
'Polling: ${config.pollingConfig.maxAttempts}x @ ${config.pollingConfig.interval.inSeconds}s',
),
selected: _configName == name,
onTap: () {
setState(() {
_configName = name;
_currentConfig = config;
});
Navigator.pop(context);
_printWelcomeMessage();
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Welloo SDK - Nouveau Système'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: _changeConfiguration,
tooltip: 'Changer la configuration',
),
],
),
backgroundColor: Colors.white,
body: Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Configuration actuelle
Card(
color: Colors.blue[50],
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
const Icon(Icons.settings, size: 40, color: Colors.blue),
const SizedBox(height: 8),
Text(
_configName,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 4),
Text(
'Stratégie: ${_currentConfig.strategy.name}',
style: TextStyle(fontSize: 12, color: Colors.grey[700]),
),
Text(
'Polling: ${_currentConfig.pollingConfig.maxAttempts}x @ ${_currentConfig.pollingConfig.interval.inSeconds}s',
style: TextStyle(fontSize: 12, color: Colors.grey[700]),
),
],
),
),
),
const SizedBox(height: 20),
// Status actuel
Card(
color: Colors.grey[50],
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
const Icon(Icons.info_outline, color: Colors.blue),
const SizedBox(width: 12),
Expanded(
child: Text(
_currentStatus,
style: const TextStyle(fontSize: 14),
),
),
],
),
),
),
const SizedBox(height: 20),
// Bouton principal
ElevatedButton.icon(
onPressed: _handleDeposit,
icon: const Icon(Icons.account_balance_wallet),
label: const Text("Initier un Dépôt"),
style: ElevatedButton.styleFrom(
minimumSize: const Size(250, 50),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
),
const SizedBox(height: 20),
if (_lastTransactionRef != null) ...[
// Dernière transaction
Card(
color: Colors.orange[50],
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
Text(
'Dernière transaction:',
style: TextStyle(
fontSize: 11,
color: Colors.grey[600],
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 4),
SelectableText(
_lastTransactionRef!,
style: TextStyle(
fontSize: 12,
color: Colors.grey[800],
fontFamily: 'monospace',
),
textAlign: TextAlign.center,
),
],
),
),
),
const SizedBox(height: 10),
// Vérification manuelle
ElevatedButton.icon(
onPressed: _checkTransactionStatus,
icon: const Icon(Icons.refresh),
label: const Text("Vérifier le Statut"),
style: ElevatedButton.styleFrom(
minimumSize: const Size(250, 50),
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
),
),
const SizedBox(height: 8),
Text(
'Utilisez ce bouton si le deeplink ne fonctionne pas\nou pour vérifier manuellement',
style: TextStyle(
fontSize: 11,
color: Colors.grey[500],
fontStyle: FontStyle.italic,
),
textAlign: TextAlign.center,
),
],
const SizedBox(height: 30),
// Aide
Card(
color: Colors.green[50],
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
const Icon(Icons.help_outline, color: Colors.green, size: 30),
const SizedBox(height: 8),
Text(
'Nouveau Système de Vérification',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.bold,
color: Colors.green[900],
),
),
const SizedBox(height: 8),
Text(
'✓ 3 stratégies: Polling, Deeplink, Hybrid\n'
'✓ 8 configurations prédéfinies\n'
'✓ Circuit Breaker & Retry Policy\n'
'✓ Métriques & Events détaillés\n'
'✓ Logs structurés en console',
style: TextStyle(
fontSize: 11,
color: Colors.green[800],
),
textAlign: TextAlign.left,
),
],
),
),
),
],
),
),
),
),
);
}
}