import 'dart:convert'; import 'dart:math'; import 'package:http/http.dart' as http; import 'package:flutter/foundation.dart'; /// Service for sending OTP via WhatsApp using Fonnte API class FonnteService { // Fonnte Configuration static const String _apiUrl = 'https://api.fonnte.com/send'; static const String _token = 'bXMkTXeLhVbmXmh6mYG5'; static const String _countryCode = '62'; // Indonesia // Store OTP codes temporarily (in production, use backend) static final Map _otpStore = {}; // Store last generated OTP for dev mode display static String? lastGeneratedOTP; /// Generate a random 5-digit OTP static String generateOTP() { final random = Random(); return (10000 + random.nextInt(90000)).toString(); } /// Format phone number for Fonnte API /// Converts 08xx to 628xx format static String formatPhoneNumber(String phone) { phone = phone.replaceAll(RegExp(r'[^0-9]'), ''); // Remove non-digits if (phone.startsWith('0')) { phone = '62${phone.substring(1)}'; } else if (!phone.startsWith('62')) { phone = '62$phone'; } return phone; } /// Send OTP to WhatsApp number via Fonnte static Future sendOTP(String phoneNumber) async { try { final otp = generateOTP(); final expiresAt = DateTime.now().add(const Duration(minutes: 5)); final formattedPhone = formatPhoneNumber(phoneNumber); // Store OTP locally (for verification) _otpStore[formattedPhone] = _OTPData(otp: otp, expiresAt: expiresAt); // Also store with original number for flexible verification _otpStore[phoneNumber] = _OTPData(otp: otp, expiresAt: expiresAt); lastGeneratedOTP = otp; // Log for debugging purposes debugPrint('📤 Sending WhatsApp OTP to $formattedPhone via Fonnte...'); // Send WhatsApp message via Fonnte API final message = '🔐 *Kode Verifikasi AMORI Assistant*\n\n' 'Kode OTP Anda: *$otp*\n\n' 'Kode ini berlaku selama 5 menit.\n' 'Jangan bagikan kode ini kepada siapapun.'; final response = await http.post( Uri.parse(_apiUrl), headers: {'Authorization': _token}, body: { 'target': formattedPhone, 'message': message, 'countryCode': _countryCode, }, ); debugPrint('📥 Fonnte Response: ${response.statusCode} ${response.body}'); final responseData = jsonDecode(response.body); if (responseData['status'] == true) { debugPrint('✅ WhatsApp OTP sent successfully to $formattedPhone'); return true; } else { debugPrint( '❌ Fonnte API error: ${responseData['reason'] ?? responseData['detail']}', ); return false; } } catch (e) { debugPrint('❌ Error sending WhatsApp OTP: $e'); return false; } } /// Verify OTP code static bool verifyOTP(String phoneNumber, String code) { final formattedPhone = formatPhoneNumber(phoneNumber); // Try both formatted and original number final otpData = _otpStore[formattedPhone] ?? _otpStore[phoneNumber]; if (otpData == null) { debugPrint('❌ No OTP found for $phoneNumber'); return false; } if (DateTime.now().isAfter(otpData.expiresAt)) { debugPrint('❌ OTP expired for $phoneNumber'); _otpStore.remove(formattedPhone); _otpStore.remove(phoneNumber); return false; } if (otpData.otp != code) { debugPrint( '❌ Invalid OTP for $phoneNumber (expected: ${otpData.otp}, got: $code)', ); return false; } debugPrint('✅ WhatsApp OTP verified for $phoneNumber'); _otpStore.remove(formattedPhone); _otpStore.remove(phoneNumber); return true; } /// Check if OTP exists and is valid (not expired) static bool hasValidOTP(String phoneNumber) { final formattedPhone = formatPhoneNumber(phoneNumber); final otpData = _otpStore[formattedPhone] ?? _otpStore[phoneNumber]; if (otpData == null) return false; return DateTime.now().isBefore(otpData.expiresAt); } /// Clear OTP for phone static void clearOTP(String phoneNumber) { final formattedPhone = formatPhoneNumber(phoneNumber); _otpStore.remove(formattedPhone); _otpStore.remove(phoneNumber); } /// Get last generated OTP (for dev mode display) static String? getLastOTP() => lastGeneratedOTP; } class _OTPData { final String otp; final DateTime expiresAt; _OTPData({required this.otp, required this.expiresAt}); }