amoriai/lib/services/fonnte_service.dart

146 lines
4.5 KiB
Dart

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<String, _OTPData> _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<bool> 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});
}