146 lines
4.5 KiB
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});
|
|
}
|