diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 147025c8..98821bd1 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -267,7 +267,7 @@ class ApiClientImp implements ApiClient { _analytics.errorTracking.log(endPoint, error: "SocketException: $e"); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); - Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); + // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); Clarity.sendCustomEvent('api_failure'); return; @@ -282,7 +282,7 @@ class ApiClientImp implements ApiClient { _analytics.errorTracking.log(endPoint, error: "TimeoutException"); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); - Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); + // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); Clarity.sendCustomEvent('api_timeout_failure'); return; } catch (e) { @@ -299,7 +299,7 @@ class ApiClientImp implements ApiClient { logApiEndpointError(endPoint, LocaleKeys.networkErrorWhileFetching.tr(), statusCode); Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); - Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); + // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); Clarity.setCustomTag('api_err_status', statusCode.toString()); Clarity.sendCustomEvent('api_status_code_failure'); } else { @@ -405,7 +405,7 @@ class ApiClientImp implements ApiClient { } Clarity.setCustomTag('api_err_url', _getEndofUrl(url.trim())); - Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); + // Clarity.setCustomTag('api_err_request', _sanitizeRequestBody(requestBody)); Clarity.setCustomTag('api_err_status', statusCode.toString()); Clarity.sendCustomEvent('api_message_status_failure'); } diff --git a/lib/features/payfort/payfort_view_model.dart b/lib/features/payfort/payfort_view_model.dart index dda8f51a..4b0e2c26 100644 --- a/lib/features/payfort/payfort_view_model.dart +++ b/lib/features/payfort/payfort_view_model.dart @@ -257,7 +257,6 @@ class PayfortViewModel extends ChangeNotifier { onError!(failure.message); }, (apiResponse) { - log(apiResponse.data); if (onSuccess != null) { onSuccess(apiResponse); } diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index 59e31903..35a924af 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -2,6 +2,7 @@ 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'; @@ -96,6 +97,61 @@ class PayTabsViewModel extends ChangeNotifier { 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(); + } + startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async { bool _callbackFired = false; // prevent duplicate callback invocations @@ -169,4 +225,39 @@ class PayTabsViewModel extends ChangeNotifier { } }); } + + 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"); + } + }); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 0c5a418e..044ce9a8 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -286,7 +286,7 @@ class _AppointmentDetailsPageState extends State { AppointmentDoctorCard( // renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) || renderWidgetForERDisplay: ((widget.patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) || - !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID)), + !Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID ?? 0)), patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context)); diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 36e386b9..ff0b8012 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -378,7 +378,98 @@ class _AppointmentPaymentPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "TAMARA"; - openPaymentURL("tamara"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setAlternatePaymentConfiguration( + "Appointment Payment", num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString())); + paytabsViewModel.startAlternatePayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + tamaraOrderID = transactionData.cartID!; + transID = transactionData.transactionReference!; + LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "success", status: "14", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + await payfortViewModel.markAppointmentAsTamaraPaid( + projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo); + await myAppointmentsViewModel.createAdvancePayment( + paymentMethodName: selectedPaymentMethod, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), + paymentReference: transactionData.transactionReference!, + patientID: appState.getAuthenticatedUser()!.patientId.toString(), + patientType: appState.getAuthenticatedUser()!.patientType!, + onSuccess: (value) async { + print(value); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: "Tamara-Advance-0000", + paymentReference: transactionData.transactionReference!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + await myAppointmentsViewModel.insertLiveCareVIDARequest( + clientRequestID: transID, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }, + onError: (error) {}); + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + LoaderBottomSheet.hideLoader(); + Navigator.pushAndRemoveUntil( + context, + CustomPageRoute( + page: LandingNavigation(), + ), + (r) => false); + }); + }); + } + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, onError: (err) async { + await payfortViewModel.updateTamaraRequestStatus(responseMessage: "Failed", status: "00", clientRequestID: transID, tamaraOrderID: tamaraOrderID); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("tamara"); + } }) : SizedBox.shrink(), ],