diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 21f93eed..fce61fc7 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -44,6 +44,8 @@ class AppState { AuthenticatedUser? _authenticatedRootUser; AuthenticatedUser? _authenticatedChildUser; + bool isPaytabsEnabled = true; + int? _superUserID; bool isChildLoggedIn = false; bool isGMSAvailable = true; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 1de302b7..0c014232 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -48,6 +48,7 @@ import 'package:hmg_patient_app_new/features/notifications/notifications_repo.da import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_repo.dart'; @@ -327,5 +328,7 @@ class AppDependencies { getIt.registerLazySingleton(() => ServicesPriceListViewModel(servicesPriceListRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt())); + + getIt.registerLazySingleton(() => PayTabsViewModel(appState: getIt())); } } diff --git a/lib/core/paytabs_config.dart b/lib/core/paytabs_config.dart new file mode 100644 index 00000000..4d90ee8f --- /dev/null +++ b/lib/core/paytabs_config.dart @@ -0,0 +1,12 @@ +/// Default configuration values for PayTabs Payment SDK +class PaymentSdkDefaultConfig { + static const String profileID = "119730"; + static const String serverKey = "SWJ9LD22MT-JLJZLBKTLK-WGWTLWGTWR"; + static const String clientKey = "C7K2DQ-PDDN6B-69B2K7-P9GB9T"; + static const String defaultCurrency = "SAR"; + static const double defaultAmount = 10.0; + static const String defaultMerchantCountryCode = "SA"; + static const String defaultMerchantName = "Sulaiman Al Habib Medical Group"; + static const String defaultMerchantAppleBundleID = "merchant.com.hmgwebservices"; + static const String defaultMerchantAppleBundleIDUAT = "merchant.com.hmgwebservices.uat"; +} diff --git a/lib/features/paytabs/models/paytabs_transaction_response_model.dart b/lib/features/paytabs/models/paytabs_transaction_response_model.dart new file mode 100644 index 00000000..d007176c --- /dev/null +++ b/lib/features/paytabs/models/paytabs_transaction_response_model.dart @@ -0,0 +1,133 @@ +class PaytabsTransactionResponseModel { + int? tranTotal; + String? transactionReference; + String? cartCurrency; + String? cartDescription; + String? cartID; + String? tranCurrency; + String? payResponseReturn; + bool? isPending; + bool? isOnHold; + String? token; + String? transactionType; + bool? isAuthorized; + String? trace; + int? cartAmount; + int? merchantId; + int? profileId; + bool? isProcessed; + int? serviceId; + PaymentInfo? paymentInfo; + bool? isSuccess; + + PaytabsTransactionResponseModel( + {this.tranTotal, + this.transactionReference, + this.cartCurrency, + this.cartDescription, + this.cartID, + this.tranCurrency, + this.payResponseReturn, + this.isPending, + this.isOnHold, + this.token, + this.transactionType, + this.isAuthorized, + this.trace, + this.cartAmount, + this.merchantId, + this.profileId, + this.isProcessed, + this.serviceId, + this.paymentInfo, + this.isSuccess}); + + PaytabsTransactionResponseModel.fromJson(Map json) { + tranTotal = json['tran_total']; + transactionReference = json['transactionReference']; + cartCurrency = json['cartCurrency']; + cartDescription = json['cartDescription']; + cartID = json['cartID']; + tranCurrency = json['tran_currency']; + payResponseReturn = json['payResponseReturn']; + isPending = json['isPending']; + isOnHold = json['isOnHold']; + token = json['token']; + transactionType = json['transactionType']; + isAuthorized = json['isAuthorized']; + trace = json['trace']; + cartAmount = json['cartAmount']; + merchantId = json['merchantId']; + profileId = json['profileId']; + isProcessed = json['isProcessed']; + serviceId = json['serviceId']; + paymentInfo = json['paymentInfo'] != null + ? new PaymentInfo.fromJson(json['paymentInfo']) + : null; + isSuccess = json['isSuccess']; + } + + Map toJson() { + final Map data = new Map(); + data['tran_total'] = this.tranTotal; + data['transactionReference'] = this.transactionReference; + data['cartCurrency'] = this.cartCurrency; + data['cartDescription'] = this.cartDescription; + data['cartID'] = this.cartID; + data['tran_currency'] = this.tranCurrency; + data['payResponseReturn'] = this.payResponseReturn; + data['isPending'] = this.isPending; + data['isOnHold'] = this.isOnHold; + data['token'] = this.token; + data['transactionType'] = this.transactionType; + data['isAuthorized'] = this.isAuthorized; + data['trace'] = this.trace; + data['cartAmount'] = this.cartAmount; + data['merchantId'] = this.merchantId; + data['profileId'] = this.profileId; + data['isProcessed'] = this.isProcessed; + data['serviceId'] = this.serviceId; + if (this.paymentInfo != null) { + data['paymentInfo'] = this.paymentInfo!.toJson(); + } + data['isSuccess'] = this.isSuccess; + return data; + } +} + +class PaymentInfo { + String? cardScheme; + String? cardType; + int? expiryMonth; + int? expiryYear; + String? paymentDescription; + String? paymentMethod; + + PaymentInfo( + {this.cardScheme, + this.cardType, + this.expiryMonth, + this.expiryYear, + this.paymentDescription, + this.paymentMethod}); + + PaymentInfo.fromJson(Map json) { + cardScheme = json['cardScheme']; + cardType = json['cardType']; + expiryMonth = json['expiryMonth']; + expiryYear = json['expiryYear']; + paymentDescription = json['paymentDescription']; + paymentMethod = json['payment_method']; + } + + Map toJson() { + final Map data = new Map(); + data['cardScheme'] = this.cardScheme; + data['cardType'] = this.cardType; + data['expiryMonth'] = this.expiryMonth; + data['expiryYear'] = this.expiryYear; + data['paymentDescription'] = this.paymentDescription; + data['payment_method'] = this.paymentMethod; + return data; + } +} diff --git a/lib/features/paytabs/paytabs_service.dart b/lib/features/paytabs/paytabs_service.dart new file mode 100644 index 00000000..6877389f --- /dev/null +++ b/lib/features/paytabs/paytabs_service.dart @@ -0,0 +1,23 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; + +abstract class PaytabsService { + Future> getAskDoctorAppointmentsList(); + + Future> getDoctorResponse(); +} + +class PaytabsServiceImp implements PaytabsService { + @override + Future> getAskDoctorAppointmentsList() { + // TODO: implement getAskDoctorAppointmentsList + throw UnimplementedError(); + } + + @override + Future> getDoctorResponse() { + // TODO: implement getDoctorResponse + throw UnimplementedError(); + } + +} diff --git a/lib/features/paytabs/paytabs_view_model.dart b/lib/features/paytabs/paytabs_view_model.dart new file mode 100644 index 00000000..ab81d5ba --- /dev/null +++ b/lib/features/paytabs/paytabs_view_model.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_paytabs_bridge/BaseBillingShippingInfo.dart'; +import 'package:flutter_paytabs_bridge/IOSThemeConfiguration.dart'; +import 'package:flutter_paytabs_bridge/PaymentSdkConfigurationDetails.dart'; +import 'package:flutter_paytabs_bridge/PaymentSdkLocale.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/enums.dart'; +import 'package:hmg_patient_app_new/core/paytabs_config.dart'; +import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class PayTabsViewModel extends ChangeNotifier { + late PaymentSdkConfigurationDetails paymentConfiguration; + final AppState appState; + late PaytabsTransactionResponseModel paytabsTransactionResponseModel; + + PayTabsViewModel({ + required this.appState, + }); + + setPaymentConfiguration(String paymentDescription, num amount) { + 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, + merchantApplePayIndentifier: + ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? PaymentSdkDefaultConfig.defaultMerchantAppleBundleIDUAT : PaymentSdkDefaultConfig.defaultMerchantAppleBundleID, + screentTitle: paymentDescription, + amount: amount.toDouble(), + showBillingInfo: false, + forceShippingInfo: false, + currencyCode: PaymentSdkDefaultConfig.defaultCurrency, + merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode, + billingDetails: BillingDetails(appState.getAuthenticatedUser()!.firstName ?? "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", + 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: "ED1C2B", + secondaryColorDark: "ED1C2B", + 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", + buttonFont: "Poppins", + ); + paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.MERCHANT_MANDATORY; + + notifyListeners(); + } + + startCardPayment({Function(dynamic)? onSuccess, Function(String)? onError}) { + FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) { + final transactionDetails = event["data"]; + if (event["status"] == "success") { + paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(event["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"; + onError!(errorMessage); + } + } else if (event["status"] == "error") { + 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") { + final eventMessage = event["message"] ?? "Event occurred"; + debugPrint("Event occurred: $eventMessage"); + } + }); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 51e34d7e..f31a04cc 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/appointment_rating_ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart'; +import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart'; import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart'; @@ -413,6 +414,9 @@ class _LandingPageState extends State { myAppointmentsViewModel.onTabChange(0); myAppointmentsViewModel.updateListWRTTab(0); Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage())); + + // getIt.get().setPaymentConfiguration("Appointment Payment", 15.5); + // getIt.get().startCardPayment(); }), Consumer3( builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) { diff --git a/pubspec.yaml b/pubspec.yaml index c17249b1..3d087f6f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,11 +101,11 @@ dependencies: flutter_widget_from_html: ^0.17.1 huawei_map: ^6.12.0+301 flutter_image_compress: ^2.3.0 - scrollable_positioned_list: ^0.3.8 - in_app_review: ^2.0.11 + flutter_paytabs_bridge: ^2.7.13 + dev_dependencies: flutter_test: sdk: flutter