import 'package:flutter/material.dart'; import 'package:flutter_paytabs_bridge/BaseBillingShippingInfo.dart'; import 'package:flutter_paytabs_bridge/IOSThemeConfiguration.dart'; import 'package:flutter_paytabs_bridge/PaymentSDKNetworks.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkApms.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkConfigurationDetails.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkLocale.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkTokenFormat.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkTokeniseType.dart'; import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart'; import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/paytabs_config.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; class PayTabsViewModel extends ChangeNotifier { late PaymentSdkConfigurationDetails paymentConfiguration; final AppState appState; final PayfortRepo payfortRepo; final ErrorHandlerService errorHandlerService; late PaytabsTransactionResponseModel paytabsTransactionResponseModel; PayTabsViewModel({ required this.appState, required this.payfortRepo, required this.errorHandlerService, }); setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) { String cartID = DateTime.now().millisecondsSinceEpoch.toString(); paymentConfiguration = PaymentSdkConfigurationDetails( profileId: PaymentSdkDefaultConfig.profileID, serverKey: PaymentSdkDefaultConfig.serverKey, clientKey: PaymentSdkDefaultConfig.clientKey, transactionType: PaymentSdkTransactionType.SALE, cartId: cartID.substring((cartID.length - 5), cartID.length), cartDescription: paymentDescription, merchantName: PaymentSdkDefaultConfig.defaultMerchantName, locale: appState.isArabic() ? PaymentSdkLocale.AR : PaymentSdkLocale.EN, merchantApplePayIndentifier: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? PaymentSdkDefaultConfig.defaultMerchantAppleBundleIDUAT : PaymentSdkDefaultConfig.defaultMerchantAppleBundleID, // merchantApplePayIndentifier: PaymentSdkDefaultConfig.defaultMerchantAppleBundleIDUAT, screentTitle: paymentDescription, amount: amount.toDouble(), showBillingInfo: false, forceShippingInfo: false, currencyCode: PaymentSdkDefaultConfig.defaultCurrency, merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode, billingDetails: BillingDetails( "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), shippingDetails: ShippingDetails( "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), alternativePaymentMethods: [], linkBillingNameWithCardHolderName: true, simplifyApplePayValidation: true, ); paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations( // logoImage: "assets/images/png/hmg_logo.png", backgroundColor: "ffffff", backgroundColorDark: "ffffff", secondaryColor: "191919", secondaryColorDark: "191919", primaryColor: "ffffff", primaryColorDark: "ffffff", // primaryFont: "Poppins", titleFontColor: "2E3039", titleFontColorDark: "2E3039", // titleFont: "Poppins", primaryFontColor: "2E3039", primaryFontColorDark: "2E3039", inputFieldBackgroundColor: "ffffff", inputFieldBackgroundColorDark: "ffffff", placeholderColor: "2E3039", placeholderColorDark: "2E3039", buttonColor: "ED1C2B", buttonColorDark: "ED1C2B", inputsCornerRadius: 12 // buttonFont: "Poppins", ); paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.USER_OPTIONAL_DEFAULT_ON; paymentConfiguration.tokenFormat = PaymentSdkTokenFormat.Hex32Format; notifyListeners(); } setAlternatePaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) { String cartID = DateTime.now().millisecondsSinceEpoch.toString(); paymentConfiguration = PaymentSdkConfigurationDetails( profileId: PaymentSdkDefaultConfig.profileID, serverKey: PaymentSdkDefaultConfig.serverKey, clientKey: PaymentSdkDefaultConfig.clientKey, transactionType: PaymentSdkTransactionType.SALE, cartId: cartID.substring((cartID.length - 5), cartID.length), cartDescription: paymentDescription, merchantName: PaymentSdkDefaultConfig.defaultMerchantName, locale: appState.isArabic() ? PaymentSdkLocale.AR : PaymentSdkLocale.EN, screentTitle: paymentDescription, amount: amount.toDouble(), showBillingInfo: false, forceShippingInfo: false, currencyCode: PaymentSdkDefaultConfig.defaultCurrency, merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode, billingDetails: BillingDetails("${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), shippingDetails: ShippingDetails("${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"), alternativePaymentMethods: [PaymentSdkAPms.TAMARA], linkBillingNameWithCardHolderName: false, simplifyApplePayValidation: true, ); paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations( // logoImage: "assets/images/png/hmg_logo.png", backgroundColor: "ffffff", backgroundColorDark: "ffffff", secondaryColor: "191919", secondaryColorDark: "191919", primaryColor: "ffffff", primaryColorDark: "ffffff", // primaryFont: "Poppins", titleFontColor: "2E3039", titleFontColorDark: "2E3039", // titleFont: "Poppins", primaryFontColor: "2E3039", primaryFontColorDark: "2E3039", inputFieldBackgroundColor: "ffffff", inputFieldBackgroundColorDark: "ffffff", placeholderColor: "2E3039", placeholderColorDark: "2E3039", buttonColor: "ED1C2B", buttonColorDark: "ED1C2B", inputsCornerRadius: 12 // buttonFont: "Poppins", ); paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.USER_OPTIONAL_DEFAULT_ON; paymentConfiguration.tokenFormat = PaymentSdkTokenFormat.Hex32Format; notifyListeners(); } Future paytabsRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); } } }, ); } Future paytabsResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); } } }, ); } startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async { bool _callbackFired = false; // prevent duplicate callback invocations await FlutterPaytabsBridge.startApplePayPayment(paymentConfiguration, (event) { if (_callbackFired) return; // ignore any subsequent callback calls final transactionDetails = event["data"] as Map?; if (event["status"] == "success") { _callbackFired = true; // Cast Map to Map final data = Map.from(event["data"] as Map); paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); if (paytabsTransactionResponseModel.isSuccess!) { onSuccess!(paytabsTransactionResponseModel); } else { // Transaction was processed but failed final reason = transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error"; final responseCode = transactionDetails?["responseCode"] ?? ""; final errorMessage = responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason"; onError!(errorMessage); } } else if (event["status"] == "error") { _callbackFired = true; final errorMessage = event["message"] ?? "An error occurred"; debugPrint("Error occurred in transaction: $errorMessage"); debugPrint("Full error event: $event"); onError!(errorMessage); } else if (event["status"] == "event") { // Intermediate SDK events – do NOT mark _callbackFired, just log final eventMessage = event["message"] ?? "Event occurred"; debugPrint("Event occurred: $eventMessage"); } }); } startCardPayment({ required String paymentOption, required String liveServiceID, required String isScheduleLiveCare, required String appointmentDate, required String appointmentNo, required String doctorID, required String transactionID, required String amount, required String fileNumber, required String orderDescription, required String serviceID, required String projectID, Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError, }) async { bool _callbackFired = false; // prevent duplicate callback invocations try { LoaderBottomSheet.showLoader(); // Create PayfortRequestInsertModel (same as Payfort implementation) PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel(); payfortRequestInsertModel.commandType = "SALE"; payfortRequestInsertModel.amount = num.parse(amount); payfortRequestInsertModel.customerEmail = getIt.get().getAuthenticatedUser()!.emailAddress; payfortRequestInsertModel.orderDescription = orderDescription; payfortRequestInsertModel.serviceID = serviceID; payfortRequestInsertModel.paymentOption = paymentOption; payfortRequestInsertModel.clientRequestID = transactionID; payfortRequestInsertModel.projectID = projectID; payfortRequestInsertModel.fileNumber = getIt.get().getAuthenticatedUser()!.patientId.toString(); payfortRequestInsertModel.customerName = "${getIt.get().getAuthenticatedUser()!.firstName} ${getIt.get().getAuthenticatedUser()!.lastName}"; payfortRequestInsertModel.currency = getIt.get().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED"; payfortRequestInsertModel.liveServiceID = liveServiceID; payfortRequestInsertModel.latitude = getIt.get().userLat.toString(); payfortRequestInsertModel.longitude = getIt.get().userLong.toString(); payfortRequestInsertModel.merchantReference = transactionID; payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken); payfortRequestInsertModel.language = getIt.get().isArabic() ? "ar" : "en"; payfortRequestInsertModel.integrationId = "2"; // PayTabs integration ID payfortRequestInsertModel.isSchedule = isScheduleLiveCare; payfortRequestInsertModel.appointmentDate = appointmentDate; payfortRequestInsertModel.appointmentNo = appointmentNo; payfortRequestInsertModel.doctorID = doctorID; // Call request insert API before payment await paytabsRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel); LoaderBottomSheet.hideLoader(); // Start PayTabs payment FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) async { if (_callbackFired) return; // ignore any subsequent callback calls final transactionDetails = event["data"] as Map?; if (event["status"] == "success") { _callbackFired = true; LoaderBottomSheet.showLoader(); // Cast Map to Map final data = Map.from(event["data"] as Map); paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); // Create PayfortResponseInsertModel (same as Payfort implementation) PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel(); payfortResponseInsertModel.fortId = paytabsTransactionResponseModel.transactionReference ?? ""; payfortResponseInsertModel.commandType = paytabsTransactionResponseModel.transactionType ?? "SALE"; // payfortResponseInsertModel.amount = paytabsTransactionResponseModel.tranTotal ?? 0; payfortResponseInsertModel.amount = num.parse(amount); payfortResponseInsertModel.sadadOLP = ""; payfortResponseInsertModel.paymentOption = paytabsTransactionResponseModel.paymentInfo?.paymentMethod ?? ""; payfortResponseInsertModel.customerIP = ""; payfortResponseInsertModel.eCI = ""; payfortResponseInsertModel.responseMessage = paytabsTransactionResponseModel.payResponseReturn ?? ""; payfortResponseInsertModel.cardNumber = paytabsTransactionResponseModel.paymentInfo!.paymentDescription; payfortResponseInsertModel.cardBin = ""; payfortResponseInsertModel.installmentsNumber = 0; payfortResponseInsertModel.status = paytabsTransactionResponseModel.isSuccess! ? "success" : "failed"; payfortResponseInsertModel.merchantRef = transactionID; payfortResponseInsertModel.isRefund = false; payfortResponseInsertModel.remmeberMe = true; payfortResponseInsertModel.reconciliationReference = ""; // Call response insert API after payment await paytabsResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel); LoaderBottomSheet.hideLoader(); if (paytabsTransactionResponseModel.isSuccess!) { if (onSuccess != null) { onSuccess(paytabsTransactionResponseModel); } } else { // Transaction was processed but failed final reason = transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error"; final responseCode = transactionDetails?["responseCode"] ?? ""; final errorMessage = responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason"; if (onError != null) { onError(errorMessage); } } } else if (event["status"] == "error") { _callbackFired = true; final errorMessage = event["message"] ?? "An error occurred"; debugPrint("Error occurred in transaction: $errorMessage"); debugPrint("Full error event: $event"); if (onError != null) { onError(errorMessage); } } else if (event["status"] == "event") { // Intermediate SDK events – do NOT mark _callbackFired, just log final eventMessage = event["message"] ?? "Event occurred"; debugPrint("Event occurred: $eventMessage"); } }); } catch (error) { debugPrint("PayTabs Payment Error: $error"); LoaderBottomSheet.hideLoader(); if (onError != null) { onError("Payment initialization failed: $error"); } } } startAlternatePayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { bool _callbackFired = false; // prevent duplicate callback invocations FlutterPaytabsBridge.startAlternativePaymentMethod(paymentConfiguration, (event) { if (_callbackFired) return; // ignore any subsequent callback calls final transactionDetails = event["data"] as Map?; if (event["status"] == "success") { _callbackFired = true; // Cast Map to Map final data = Map.from(event["data"] as Map); paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); if (paytabsTransactionResponseModel.isSuccess!) { onSuccess!(paytabsTransactionResponseModel); } else { // Transaction was processed but failed final reason = transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error"; final responseCode = transactionDetails?["responseCode"] ?? ""; final errorMessage = responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason"; onError!(errorMessage); } } else if (event["status"] == "error") { _callbackFired = true; final errorMessage = event["message"] ?? "An error occurred"; debugPrint("Error occurred in transaction: $errorMessage"); debugPrint("Full error event: $event"); onError!(errorMessage); } else if (event["status"] == "event") { // Intermediate SDK events – do NOT mark _callbackFired, just log final eventMessage = event["message"] ?? "Event occurred"; debugPrint("Event occurred: $eventMessage"); } }); } }