import 'dart:developer'; import 'package:flutter/cupertino.dart'; import 'package:mc_common_app/config/routes.dart'; import 'package:mc_common_app/extensions/string_extensions.dart'; import 'package:mc_common_app/generated/locale_keys.g.dart'; import 'package:mc_common_app/repositories/payments_repo.dart'; import 'package:mc_common_app/services/payments_service.dart'; import 'package:mc_common_app/utils/enums.dart'; import 'package:mc_common_app/utils/utils.dart'; import 'package:mc_common_app/view_models/ad_view_model.dart'; import 'package:provider/provider.dart'; import 'package:easy_localization/easy_localization.dart'; class PaymentVM extends ChangeNotifier { final PaymentService paymentService; final PaymentsRepo paymentRepo; PaymentVM({required this.paymentService, required this.paymentRepo}); PaymentMethods selectedPaymentMethod = PaymentMethods.mada; PaymentTypes currentPaymentType = PaymentTypes.ads; // Store AdVM reference for updating local state after payment AdVM? _cachedAdVM; int currentAdId = -1; void updateCurrentAdId({required int id}) { currentAdId = id; } int requestId = -1; void updateRequestId({required int id}) { requestId = id; } int orderProviderSubscriptionId = -1; void updateOrderProviderSubscriptionId({required int id}) { orderProviderSubscriptionId = id; } List appointmentIdsForPayment = []; void updateAppointmentIdsForPayment({required List ids}) { appointmentIdsForPayment = ids; log("appointmentIdsForPayment: ${appointmentIdsForPayment}"); } updateSelectedPaymentMethod(PaymentMethods selectedMethod) { selectedPaymentMethod = selectedMethod; notifyListeners(); } Future onContinuePressed(BuildContext context, {required PaymentTypes paymentType}) async { // Cache AdVM reference for updating local state after payment try { _cachedAdVM = context.read(); } catch (e) { log("Could not get AdVM reference: $e"); } switch (selectedPaymentMethod) { case PaymentMethods.mada: break; case PaymentMethods.visa: await onVisaCardSelected(context, paymentType); break; case PaymentMethods.applePay: break; case PaymentMethods.masterCard: break; case PaymentMethods.tamara: break; } return; } Future verifyPayments({ required BuildContext context, required int id, List? appointmentIds, required int paymentTypeId, required Function() onVerified, bool isForAppointment = false, }) async { try { // Utils.showLoading(context); // Commented out: causes BuildContext error after screen is popped bool isPaid = await paymentRepo.verifyPayment( paymentTypeId: paymentTypeId, id: id, isForAppointments: isForAppointment, appointmentIds: appointmentIds, ); // Utils.hideLoading(context); // Commented out: matching pair with showLoading log("isPaid: $isPaid"); if (!isPaid) { // Utils.showToast(LocaleKeys.paymentFailed.tr()); // pop(context); return; } else { Utils.showToast(LocaleKeys.paymentSuccessful.tr()); } // Call onVerified callback - success methods handle their own context checks onVerified(); } catch (e) { log("verifyPayments error: ${e.toString()}"); Utils.showToast(e.toString()); return; } } int getIdTypeByPaymentType(PaymentTypes paymentTypes) { switch (paymentTypes) { case PaymentTypes.subscription: return orderProviderSubscriptionId; case PaymentTypes.appointment: case PaymentTypes.partialAppointment: return -1; case PaymentTypes.request: return requestId; case PaymentTypes.adReserve: case PaymentTypes.ads: case PaymentTypes.extendAds: return currentAdId; } } Future placeThePayment({required PaymentTypes paymentTypeEnum, required BuildContext context, Function()? onExit}) async { await paymentService.placePayment( id: getIdTypeByPaymentType(paymentTypeEnum), // This will be request ID for request payment || ad ID for Ad Related Payment || OrderProviderSubscriptionID for SubscriptionPayment appointmentIds: appointmentIdsForPayment, paymentType: paymentTypeEnum, onExit: onExit, onFailure: () { Utils.showToast(LocaleKeys.paymentFailed.tr()); switch (paymentTypeEnum) { case PaymentTypes.subscription: case PaymentTypes.appointment: case PaymentTypes.adReserve: case PaymentTypes.ads: case PaymentTypes.request: case PaymentTypes.extendAds: case PaymentTypes.partialAppointment: break; } }, onSuccess: () async { // Utils.showToast("Verifying Payment.."); switch (paymentTypeEnum) { case PaymentTypes.subscription: await verifyPayments( context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), id: orderProviderSubscriptionId, onVerified: () => onSubscriptionPaymentSuccess(), ); break; case PaymentTypes.appointment: case PaymentTypes.partialAppointment: await verifyPayments( id: 0, isForAppointment: true, appointmentIds: appointmentIdsForPayment, context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), onVerified: () => onAppointmentSuccess(context), ); break; case PaymentTypes.request: await verifyPayments( context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), id: requestId, onVerified: () => onRequestPaymentSuccess(context), ); break; case PaymentTypes.adReserve: await verifyPayments( context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), id: currentAdId, onVerified: () => onAdReservePaymentSuccess(context), ); break; case PaymentTypes.ads: case PaymentTypes.extendAds: await verifyPayments( context: context, paymentTypeId: paymentTypeEnum.getIdFromPaymentTypesEnum(), id: currentAdId, onVerified: () => onAdsPaymentSuccess(context), ); } }, ); } void onRequestPaymentSuccess(BuildContext context) { log("🔵 onRequestPaymentSuccess called"); // Wait a bit for browser to close and context to be ready Future.delayed(const Duration(milliseconds: 500), () { log("Attempting navigation after delay..."); final nav = navigatorKey.currentState; if (nav == null) { log("❌ navigatorKey.currentState is still null after delay"); return; } nav.pushNamedAndRemoveUntil( AppRoutes.dashboard, (route) => false, arguments: DashboardRouteEnum.fromRequestPayment, ); log("✅ Navigation executed successfully"); }); } void onAdsPaymentSuccess(BuildContext context) { log("🔵 onAdsPaymentSuccess called"); // Update local ad state immediately so UI reflects payment success if (_cachedAdVM != null && currentAdId != -1) { try { // Find and update the ad in myAds list var adIndex = _cachedAdVM!.myAds.indexWhere((ad) => ad.id == currentAdId); if (adIndex != -1) { _cachedAdVM!.myAds[adIndex].adPostStatus = AdPostStatus.active; _cachedAdVM!.notifyListeners(); log("✅ Local ad state updated - status changed to active"); } else { log("⚠️ Ad not found in myAds list"); } } catch (e) { log("⚠️ Could not update local ad state: $e"); } } // Wait a bit for browser to close and context to be ready Future.delayed(const Duration(milliseconds: 500), () { log("Attempting navigation after delay..."); final nav = navigatorKey.currentState; if (nav == null) { log("❌ navigatorKey.currentState is still null after delay"); return; } nav.pushNamedAndRemoveUntil( AppRoutes.dashboard, (route) => false, arguments: DashboardRouteEnum.fromAdsPayment, ); log("✅ Navigation executed successfully"); }); } void onAdReservePaymentSuccess(BuildContext context) { log("🔵 onAdReservePaymentSuccess called"); // Wait a bit for browser to close and context to be ready Future.delayed(const Duration(milliseconds: 500), () { log("Attempting navigation after delay..."); final nav = navigatorKey.currentState; if (nav == null) { log("❌ navigatorKey.currentState is still null after delay"); return; } nav.pushNamedAndRemoveUntil( AppRoutes.dashboard, (route) => false, arguments: DashboardRouteEnum.fromReserveAdsPayment, ); log("✅ Navigation executed successfully"); }); } void onSubscriptionPaymentSuccess() { log("🔵 onSubscriptionPaymentSuccess called"); // Wait a bit for browser to close and context to be ready Future.delayed(const Duration(milliseconds: 500), () { log("Attempting navigation after delay..."); final nav = navigatorKey.currentState; if (nav == null) { log("❌ navigatorKey.currentState is still null after delay"); return; } nav.pushNamedAndRemoveUntil( AppRoutes.dashboard, (route) => false, ); log("✅ Navigation executed successfully"); }); } void onAppointmentSuccess(BuildContext context) { log("🔵 onAppointmentSuccess called"); // Wait a bit for browser to close and context to be ready Future.delayed(const Duration(milliseconds: 500), () { log("Attempting navigation after delay..."); final nav = navigatorKey.currentState; if (nav == null) { log("❌ navigatorKey.currentState is still null after delay"); return; } nav.pushNamedAndRemoveUntil( AppRoutes.dashboard, (route) => false, arguments: DashboardRouteEnum.fromAppointmentPayment, ); log("✅ Navigation executed successfully"); }); } Future onVisaCardSelected(BuildContext context, PaymentTypes paymentType, {Function()? onExit}) async { currentPaymentType = paymentType; switch (currentPaymentType) { case PaymentTypes.appointment: case PaymentTypes.partialAppointment: if (appointmentIdsForPayment.isEmpty) return; await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; case PaymentTypes.request: if (requestId == -1) return; await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; case PaymentTypes.subscription: await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; case PaymentTypes.ads: case PaymentTypes.adReserve: case PaymentTypes.extendAds: if (currentAdId == -1) return; await placeThePayment(context: context, paymentTypeEnum: paymentType, onExit: onExit); break; } } }