From cdc82c83f8d359666d4f5dc6287c88fa34718124 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Tue, 12 May 2026 22:09:55 +0300 Subject: [PATCH] Paytabs implemented in Wallet payment --- assets/langs/ar-SA.json | 2 +- .../habib_wallet/habib_wallet_view_model.dart | 7 +- .../paytabs_transaction_response_model.dart | 4 +- lib/features/paytabs/paytabs_view_model.dart | 30 ++-- lib/main.dart | 3 + .../wallet_payment_confirm_page.dart | 153 ++++++++++++++++-- .../my_family/widget/family_cards.dart | 3 +- lib/widgets/app_language_change.dart | 7 + 8 files changed, 177 insertions(+), 32 deletions(-) diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 3c54f367..e0faf4e5 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -220,7 +220,7 @@ "request": "الطلبات", "memberName": "اسم العضو", "switchLogin": "عرض الملف", - "removeMember": "إزالة", + "removeMember": "حذف", "allowView": "السماح", "rejectView": "رفض", "deleteView": "حذف", diff --git a/lib/features/habib_wallet/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart index 16997369..94570a08 100644 --- a/lib/features/habib_wallet/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -280,7 +280,12 @@ class HabibWalletViewModel extends ChangeNotifier { paymentMethodName: paymentMethodName, paidAmount: paidAmount, paymentReference: paymentReference, patientID: patientID, projectID: projectID, depositorName: depositorName); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + if(onError != null) { + onError(failure.message); + } + }, (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); diff --git a/lib/features/paytabs/models/paytabs_transaction_response_model.dart b/lib/features/paytabs/models/paytabs_transaction_response_model.dart index d007176c..57d23349 100644 --- a/lib/features/paytabs/models/paytabs_transaction_response_model.dart +++ b/lib/features/paytabs/models/paytabs_transaction_response_model.dart @@ -12,7 +12,7 @@ class PaytabsTransactionResponseModel { String? transactionType; bool? isAuthorized; String? trace; - int? cartAmount; + dynamic cartAmount; int? merchantId; int? profileId; bool? isProcessed; @@ -62,7 +62,7 @@ class PaytabsTransactionResponseModel { isProcessed = json['isProcessed']; serviceId = json['serviceId']; paymentInfo = json['paymentInfo'] != null - ? new PaymentInfo.fromJson(json['paymentInfo']) + ? PaymentInfo.fromJson(Map.from(json['paymentInfo'] as Map)) : null; isSuccess = json['isSuccess']; } diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart index ab81d5ba..2893e72a 100644 --- a/lib/features/paytabs/paytabs_view_model.dart +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -32,6 +32,7 @@ class PayTabsViewModel extends ChangeNotifier { 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, screentTitle: paymentDescription, @@ -40,9 +41,10 @@ class PayTabsViewModel extends ChangeNotifier { forceShippingInfo: false, currencyCode: PaymentSdkDefaultConfig.defaultCurrency, merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode, - billingDetails: BillingDetails(appState.getAuthenticatedUser()!.firstName ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + 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 ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com", + 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, @@ -52,14 +54,14 @@ class PayTabsViewModel extends ChangeNotifier { // logoImage: "assets/images/png/hmg_logo.png", backgroundColor: "ffffff", backgroundColorDark: "ffffff", - secondaryColor: "ED1C2B", - secondaryColorDark: "ED1C2B", + secondaryColor: "191919", + secondaryColorDark: "191919", primaryColor: "ffffff", primaryColorDark: "ffffff", - primaryFont: "Poppins", + // primaryFont: "Poppins", titleFontColor: "2E3039", titleFontColorDark: "2E3039", - titleFont: "Poppins", + // titleFont: "Poppins", primaryFontColor: "2E3039", primaryFontColorDark: "2E3039", inputFieldBackgroundColor: "ffffff", @@ -68,25 +70,27 @@ class PayTabsViewModel extends ChangeNotifier { placeholderColorDark: "2E3039", buttonColor: "ED1C2B", buttonColorDark: "ED1C2B", - buttonFont: "Poppins", + // buttonFont: "Poppins", ); paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.MERCHANT_MANDATORY; notifyListeners(); } - startCardPayment({Function(dynamic)? onSuccess, Function(String)? onError}) { + startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) { - final transactionDetails = event["data"]; + final transactionDetails = event["data"] as Map?; if (event["status"] == "success") { - paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(event["data"]); + // 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.isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason"; + 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") { diff --git a/lib/main.dart b/lib/main.dart index ffe2043e..1ebcf0d6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart'; @@ -262,6 +263,8 @@ void main() async { create: (_) => getIt.get(), ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), // Wrap your app diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index 3bd95a98..b6810c27 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -16,6 +16,8 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; @@ -35,6 +37,7 @@ class WalletPaymentConfirmPage extends StatefulWidget { class _WalletPaymentConfirmPageState extends State { late PayfortViewModel payfortViewModel; + late PayTabsViewModel paytabsViewModel; late AppState appState; late HabibWalletViewModel habibWalletVM; @@ -56,6 +59,7 @@ class _WalletPaymentConfirmPageState extends State { appState = getIt.get(); habibWalletVM = Provider.of(context, listen: false); payfortViewModel = Provider.of(context, listen: false); + paytabsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -102,7 +106,66 @@ class _WalletPaymentConfirmPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "MADA"; - openPaymentURL("mada"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(); + await habibWalletVM.HISCreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paidAmount: habibWalletVM.walletRechargeAmount, + paymentReference: transactionData.transactionReference!, + patientID: habibWalletVM.fileNumber, + projectID: habibWalletVM.selectedHospital!.iD!, + depositorName: habibWalletVM.depositorName, + onSuccess: (value) async { + await habibWalletVM.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), + callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("mada"); + } }), SizedBox(height: 16.h), Container( @@ -144,7 +207,66 @@ class _WalletPaymentConfirmPageState extends State { ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "VISA"; - openPaymentURL("visa"); + if (appState.isPaytabsEnabled) { + paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); + paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { + LoaderBottomSheet.showLoader(); + await habibWalletVM.HISCreateAdvancePayment( + paymentMethodName: selectedPaymentMethod, + paidAmount: habibWalletVM.walletRechargeAmount, + paymentReference: transactionData.transactionReference!, + patientID: habibWalletVM.fileNumber, + projectID: habibWalletVM.selectedHospital!.iD!, + depositorName: habibWalletVM.depositorName, + onSuccess: (value) async { + await habibWalletVM.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: transactionData.transactionReference!, + onSuccess: (value) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), + callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + }, onError: (err) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + openPaymentURL("visa"); + } }), ], ), @@ -197,9 +319,7 @@ class _WalletPaymentConfirmPageState extends State { ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 12.h), Platform.isIOS - ? Utils.buildSvgWithAssets( - icon: AppAssets.apple_pay_button, - width: 200.h, height: 56.h, fit: BoxFit.contain, applyThemeColor: false).paddingSymmetrical(24.h, 0.h).onPress(() { + ? Utils.buildSvgWithAssets(icon: AppAssets.apple_pay_button, width: 200.h, height: 56.h, fit: BoxFit.contain, applyThemeColor: false).paddingSymmetrical(24.h, 0.h).onPress(() { if (Utils.havePrivilege(103)) { startApplePay(); } else { @@ -321,16 +441,12 @@ class _WalletPaymentConfirmPageState extends State { onSuccess: (value) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(getIt.get().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"), - callBackFunc: () { - habibWalletVM.initHabibWalletProvider(); - habibWalletVM.getPatientBalanceAmount(); + callBackFunc: () { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); Navigator.of(getIt.get().navigatorKey.currentContext!).pop(); - }, - isFullScreen: false, - isCloseButtonVisible: true, - isAutoDismiss: true - ); + }, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true); }, onError: (err) { LoaderBottomSheet.hideLoader(); @@ -343,7 +459,16 @@ class _WalletPaymentConfirmPageState extends State { ); }); }, - onError: (err) {}); + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); } else { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( diff --git a/lib/presentation/my_family/widget/family_cards.dart b/lib/presentation/my_family/widget/family_cards.dart index 1497609c..d2706732 100644 --- a/lib/presentation/my_family/widget/family_cards.dart +++ b/lib/presentation/my_family/widget/family_cards.dart @@ -432,13 +432,14 @@ class _FamilyCardsState extends State { // ), // SizedBox(height: 24.h), CustomExpandableList( - expansionMode: ExpansionMode.exactlyOne, + expansionMode: ExpansionMode.multiple, dividerColor: AppColors.dividerColor, itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h), items: [ ExpandableListItem( title: LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText18(isBold: true), expandedBackgroundColor: Colors.transparent, + initiallyExpanded: true, children: [ SizedBox(height: 10.h), manageFamily() diff --git a/lib/widgets/app_language_change.dart b/lib/widgets/app_language_change.dart index f041cd85..b084870b 100644 --- a/lib/widgets/app_language_change.dart +++ b/lib/widgets/app_language_change.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; @@ -29,6 +30,7 @@ class _AppLanguageChangeState extends State { late ImmediateLiveCareViewModel immediateLiveCareViewModel; late EmergencyServicesViewModel emergencyServicesViewModel; late TodoSectionViewModel todoSectionViewModel; + late HabibWalletViewModel habibWalletVM; @override void initState() { @@ -48,6 +50,7 @@ class _AppLanguageChangeState extends State { immediateLiveCareViewModel = Provider.of(context, listen: false); emergencyServicesViewModel = Provider.of(context, listen: false); todoSectionViewModel = Provider.of(context, listen: false); + habibWalletVM = context.read(); return Column( spacing: 24.h, @@ -72,6 +75,10 @@ class _AppLanguageChangeState extends State { myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + habibWalletVM.getLakumAccountInformation(); + //Reload Immediate LiveCare Data immediateLiveCareViewModel.initImmediateLiveCare(); immediateLiveCareViewModel.getPatientLiveCareHistory();