import 'dart:async'; import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.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/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.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/payfort/payfort_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import 'package:smooth_corner/smooth_corner.dart'; // Added imports required by this file import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; /// A reusable payment screen for COVID-related payments. /// /// This screen re-uses the same UI pattern and payment flow used by /// `AppointmentPaymentPage` (in-app browser, Apple Pay and payfort status checks), /// but it keeps the post-payment handling generic (shows success / failure) /// so it can be safely used for COVID test purchases without appointment-specific APIs. class CovidPaymentScreen extends StatefulWidget { final double amount; final int projectID; final int clinicID; final String procedureId; final double taxAmount; final String title; const CovidPaymentScreen({ super.key, required this.amount, required this.projectID, required this.clinicID, required this.procedureId, this.taxAmount = 0.0, this.title = "COVID Payment", }); @override State createState() => _CovidPaymentScreenState(); } class _CovidPaymentScreenState extends State { late PayfortViewModel payfortViewModel; late AppState appState; MyInAppBrowser? browser; String selectedPaymentMethod = ""; String transID = ""; bool isShowTamara = false; // placeholder: could be enabled based on remote config @override void initState() { super.initState(); // initialize payfort view model when the widget is ready scheduleMicrotask(() { payfortViewModel = Provider.of(context, listen: false); payfortViewModel.initPayfortViewModel(); payfortViewModel.setIsApplePayConfigurationLoading(false); // Optionally compute if Tamara should be shown by calling a remote config API. // For now keep it false (unless the app provides an API for it). // Enable Tamara payment option for COVID screen setState(() { isShowTamara = true; }); }); } @override Widget build(BuildContext context) { appState = getIt.get(); payfortViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: widget.title, bottomChild: Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: false, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), LocaleKeys.totalAmountToPay.tr(context: context).toText18(isBold: true).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.amountBeforeTax.tr(context: context).toText14(isBold: true), Utils.getPaymentAmountWithSymbol(( (widget.amount - widget.taxAmount).toString()).toText16(isBold: true), AppColors.blackColor, 13, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ LocaleKeys.vat15.tr(context: context).toText14(isBold: true, color: AppColors.greyTextColor), // Show VAT amount passed from review screen Utils.getPaymentAmountWithSymbol((widget.taxAmount.toString()).toText14(isBold: true, color: AppColors.greyTextColor), AppColors.greyTextColor, 13, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), SizedBox(height: 17.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ "".toText14(isBold: true), Utils.getPaymentAmountWithSymbol(widget.amount.toString().toText24(isBold: true), AppColors.blackColor, 17, isSaudiCurrency: true), ], ).paddingSymmetrical(24.h, 0.h), // Apple Pay (iOS) Platform.isIOS ? Utils.buildSvgWithAssets( icon: AppAssets.apple_pay_button, width: 200.h, height: 80.h, fit: BoxFit.contain, ).paddingSymmetrical(24.h, 0.h).onPress(() { if (Utils.havePrivilege(103)) { startApplePay(); } else { openPaymentURL("ApplePay"); } }) : SizedBox(height: 12.h), SizedBox(height: 12.h), // Action buttons: Cancel + Next (Next opens default payment flow - e.g. Visa) // Padding( // padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 24.h), // child: Row( // children: [ // Expanded( // child: CustomButton( // height: 56.h, // text: LocaleKeys.cancel.tr(), // onPressed: () { // Navigator.of(context).pop(); // }, // backgroundColor: AppColors.secondaryLightRedColor, // borderColor: AppColors.secondaryLightRedColor, // textColor: AppColors.primaryRedColor, // icon: AppAssets.cancel, // iconColor: AppColors.primaryRedColor, // borderRadius: 12.r, // ), // ), // SizedBox(width: 8.h), // Expanded( // child: CustomButton( // height: 56.h, // text: "Next".needTranslation, // onPressed: () { // // Default to Visa for Next // selectedPaymentMethod = "VISA"; // openPaymentURL("visa"); // }, // backgroundColor: AppColors.primaryRedColor, // borderColor: AppColors.primaryRedColor, // textColor: AppColors.whiteColor, // fontSize: 16.f, // fontWeight: FontWeight.w500, // borderRadius: 12.r, // padding: EdgeInsets.symmetric(horizontal: 10.w), // icon: AppAssets.add_icon, // iconColor: AppColors.whiteColor, // iconSize: 18.h, // ), // ), // ], // ), // ), ], ), ).paddingSymmetrical(0.h, 0.h), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 24.h), // MADA tile Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, ), child: Row( mainAxisSize: MainAxisSize.max, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.asset(AppAssets.mada, width: 72.h, height: 25.h), SizedBox(height: 16.h), LocaleKeys.mada.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), const Spacer(), Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, fit: BoxFit.contain, ), ), ], ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "MADA"; openPaymentURL("MADA"); }), SizedBox(height: 16.h), // Visa / Mastercard tile Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, ), child: Row( mainAxisSize: MainAxisSize.max, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Image.asset(AppAssets.visa, width: 50.h, height: 50.h), SizedBox(width: 8.h), Image.asset(AppAssets.mastercard, width: 40.h, height: 40.h), ], ), SizedBox(height: 16.h), LocaleKeys.visaOrMastercard.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), const Spacer(), Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, fit: BoxFit.contain, ), ), ], ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "VISA"; openPaymentURL("VISA"); }), SizedBox(height: 16.h), // Optional Tamara tile (shown only if enabled) isShowTamara ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, ), child: Row( mainAxisSize: MainAxisSize.max, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Image.asset( AppAssets.tamaraEng, width: 72.h, height: 25.h, fit: BoxFit.contain, // If PNG fails to load for any reason, log and fallback to SVG asset errorBuilder: (context, error, stackTrace) { debugPrint('Failed to load Tamara PNG asset: $error'); return Utils.buildSvgWithAssets( icon: AppAssets.tamara, width: 72.h, height: 25.h, fit: BoxFit.contain, ); }, ), SizedBox(height: 16.h), LocaleKeys.tamara.tr(context: context).toText16(isBold: true), ], ), SizedBox(width: 8.h), const Spacer(), Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, fit: BoxFit.contain, ), ), ], ).paddingSymmetrical(16.h, 16.h), ).paddingSymmetrical(24.h, 0.h).onPress(() { selectedPaymentMethod = "TAMARA"; openPaymentURL("TAMARA"); }) : SizedBox.shrink(), SizedBox(height: 24.h), // Bottom payment summary SizedBox(height: 24.h), ], ), ), ), ); } void onBrowserLoadStart(String url) { // Generic loader hook: detect success / error patterns from the in-app browser. // Keep parsing defensive: use Uri.parse where possible. try { final uri = Uri.tryParse(url); if (selectedPaymentMethod == "TAMARA" && uri != null) { // tamara returns different query param names depending on platform; defensive checks final params = uri.queryParameters; if (params.isNotEmpty) { // example keys: 'status', 'AuthorizePaymentId' (android) or 'paymentStatus', 'orderId' (iOS) } } } catch (e) { debugPrint('onBrowserLoadStart parse error: $e'); } MyInAppBrowser.successURLS.forEach((element) { if (url.contains(element)) { browser?.close(); MyInAppBrowser.isPaymentDone = true; return; } }); MyInAppBrowser.errorURLS.forEach((element) { if (url.contains(element)) { browser?.close(); MyInAppBrowser.isPaymentDone = false; return; } }); } void onBrowserExit(bool isPaymentMade) async { // When browser closes, check payment status using payfort view model await checkPaymentStatus(); } Future checkPaymentStatus() async { LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); try { await payfortViewModel.checkPaymentStatus(transactionID: transID, onSuccess: (apiResponse) async { // treat any successful responseMessage as success; otherwise show generic error final success = payfortViewModel.payfortCheckPaymentStatusResponseModel?.responseMessage?.toLowerCase() == 'success'; LoaderBottomSheet.hideLoader(); if (success) { showCommonBottomSheetWithoutHeight( context, child: Utils.getSuccessWidget(loadingText: "Payment successful"), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } else { showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } }); } catch (err) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: err.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } } void openPaymentURL(String paymentMethod) { browser = MyInAppBrowser(onExitCallback: onBrowserExit, onLoadStartCallback: onBrowserLoadStart, context: context); transID = Utils.getAppointmentTransID(widget.projectID, widget.clinicID, DateTime.now().millisecondsSinceEpoch); // Open payment browser with essential parameters; many fields are simplified here browser?.openPaymentBrowser( widget.amount, "COVID Test Payment", transID, widget.projectID.toString(), "CustID_${appState.getAuthenticatedUser()?.patientId ?? ''}@HMG.com", selectedPaymentMethod, appState.getAuthenticatedUser()?.patientType.toString() ?? "", appState.getAuthenticatedUser() != null ? "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" : "", appState.getAuthenticatedUser()?.patientId.toString() ?? "", appState.getAuthenticatedUser() ?? (null as dynamic), browser!, false, "2", "", context, DateTime.now().toString(), "", 0, 0, "3", ); } void startApplePay() async { showCommonBottomSheet( context, child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false, ); transID = Utils.getAppointmentTransID(widget.projectID, widget.clinicID, DateTime.now().millisecondsSinceEpoch); // Prepare a minimal apple pay request using payfortViewModel's configuration try { await payfortViewModel.getPayfortConfigurations(serviceId: 0, projectId: widget.projectID, integrationId: 2); // Build minimal apple pay request (model omitted here to keep things generic) ApplePayInsertRequest applePayInsertRequest = ApplePayInsertRequest( clientRequestID: transID, clinicID: widget.clinicID, currency: appState.getAuthenticatedUser() != null && appState.getAuthenticatedUser()!.outSa == 0 ? "SAR" : "AED", customerEmail: "CustID_${appState.getAuthenticatedUser()?.patientId ?? ''}@HMG.com", customerID: appState.getAuthenticatedUser()?.patientId, customerName: appState.getAuthenticatedUser() != null ? "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" : "", deviceToken: await Utils.getStringFromPrefs(CacheConst.pushToken), voipToken: await Utils.getStringFromPrefs(CacheConst.voipToken), doctorID: 0, projectID: widget.projectID.toString(), serviceID: "0", channelID: 3, patientID: appState.getAuthenticatedUser()?.patientId, patientTypeID: appState.getAuthenticatedUser()?.patientType, patientOutSA: appState.getAuthenticatedUser()?.outSa, appointmentDate: DateTime.now().toString(), appointmentNo: 0, orderDescription: "COVID Test Payment", liveServiceID: "0", latitude: "0.0", longitude: "0.0", amount: widget.amount.toString(), isSchedule: "0", language: appState.isArabic() ? 'ar' : 'en', languageID: appState.isArabic() ? 1 : 2, userName: appState.getAuthenticatedUser()?.patientId, responseContinueURL: "http://hmg.com/Documents/success.html", backClickUrl: "http://hmg.com/Documents/success.html", paymentOption: "ApplePay", isMobSDK: true, merchantReference: transID, merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel?.merchantIdentifier, commandType: "PURCHASE", signature: payfortViewModel.payfortProjectDetailsRespModel?.signature, accessCode: payfortViewModel.payfortProjectDetailsRespModel?.accessCode, shaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel?.shaRequest, shaResponsePhrase: payfortViewModel.payfortProjectDetailsRespModel?.shaResponse, returnURL: "", ); await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) { // Start apple pay flow payfortViewModel.paymentWithApplePay( customerName: appState.getAuthenticatedUser() != null ? "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" : "", customerEmail: "CustID_${appState.getAuthenticatedUser()?.patientId ?? ''}@HMG.com", orderDescription: "COVID Test Payment", orderAmount: widget.amount, merchantReference: transID, merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel?.merchantIdentifier ?? "", applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel?.accessCode ?? "", applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel?.shaRequest ?? "", currency: appState.getAuthenticatedUser() != null && appState.getAuthenticatedUser()!.outSa == 0 ? "SAR" : "AED", onFailed: (failureResult) async { Navigator.of(context).pop(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); }, onSucceeded: (successResult) async { Navigator.of(context).pop(); selectedPaymentMethod = successResult.paymentOption ?? "VISA"; await checkPaymentStatus(); }, ); }).catchError((e) { Navigator.of(context).pop(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: e.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); }); } catch (e) { Navigator.of(context).pop(); showCommonBottomSheetWithoutHeight( context, child: Utils.getErrorWidget(loadingText: e.toString()), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true, ); } } }