import 'dart:convert'; import 'package:dio/dio.dart'; import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; import 'package:qpay/abstracts/AbstractController.dart'; import 'package:qpay/http/http.dart'; import 'package:qpay/models/api_response.dart'; import 'package:qpay/screens/onboarding/onboarding_controller.dart'; class VerifyModel { String? code; bool isLoading = false; String? errorMessage; String? status = 'failed'; } class VerifyController extends AbstractController { late OnboardingController onboardingController; final Http http = Http(); BuildContext context; VerifyModel model = VerifyModel(); VerifyController(this.context) { onboardingController = Provider.of( context, listen: false, ); } void updateCode(String code) { model.code = code; notifyListeners(); } /// Extracts a human-readable error message from any exception thrown during /// an HTTP call. Supports: /// * [DioException] – the response body, request body, and a top-level /// `message` field are inspected. /// * Dart [Exception] / [Error] whose stringified form is a JSON object /// (e.g. `{state: failed, status: manual, body: {...}, message: ...}`). /// * Any other throwable – falls back to its `toString()`. String _extractErrorMessage(Object error) { try { if (error is DioException) { final data = error.response?.data; if (data is Map) { final message = data['message']; if (message is String && message.isNotEmpty) return message; } if (data is String && data.isNotEmpty) { final parsed = _tryParseJson(data); if (parsed is Map) { final message = parsed['message']; if (message is String && message.isNotEmpty) return message; } return data; } if (error.type == DioExceptionType.connectionTimeout || error.type == DioExceptionType.receiveTimeout || error.type == DioExceptionType.sendTimeout) { return 'The request timed out. Please check your connection and try again.'; } if (error.type == DioExceptionType.connectionError) { return 'No internet connection. Please check your network and try again.'; } return error.message ?? 'Network error. Please try again.'; } final raw = error.toString(); // The server sometimes throws a stringified JSON object as an exception. final parsed = _tryParseJson(raw); if (parsed is Map) { final message = parsed['message']; if (message is String && message.isNotEmpty) return message; } return raw; } catch (_) { return 'An unexpected error occurred. Please try again.'; } } dynamic _tryParseJson(String input) { try { return jsonDecode(input); } catch (_) { return null; } } Future>> verifyOtp() async { Map user = { "username": onboardingController.model.email, "otpCode": model.code, "otpType": "REGISTRATION", "workflowId": onboardingController.model.workflowId, }; try { model.isLoading = true; model.errorMessage = null; notifyListeners(); Map response = await http.post('/auth/verify', user); model.status = response['state']; model.isLoading = false; notifyListeners(); if (response['state'] == 'failed') { final message = response['message']?.toString() ?? "Verification failed"; model.errorMessage = message; return ApiResponse.failure(message); } return ApiResponse.success(Map.from(response)); } catch (e) { logger.e(e); final message = _extractErrorMessage(e); model.errorMessage = message; model.isLoading = false; notifyListeners(); return ApiResponse.failure(message); } } Future>> resendOtp() async { Map user = { "username": onboardingController.model.email, "phone": onboardingController.model.phone, "workflowId": onboardingController.model.workflowId, }; try { model.isLoading = true; model.errorMessage = null; notifyListeners(); Map response = await http.post('/auth/resend', user); model.status = response['state']; model.isLoading = false; notifyListeners(); if (response['state'] == 'failed') { final message = response['message']?.toString() ?? "Resend failed"; model.errorMessage = message; return ApiResponse.failure(message); } return ApiResponse.success(Map.from(response)); } catch (e) { logger.e(e); final message = _extractErrorMessage(e); model.errorMessage = message; model.isLoading = false; notifyListeners(); return ApiResponse.failure(message); } } }