Payfort in-app implementation contd.

haroon_dev
haroon amjad 1 day ago
parent bb89bb63af
commit 4c6485d6af

@ -86,3 +86,6 @@
@com.peng.bus.PISubscribe <methods>;
}
-keep class com.peng.pennavmap.models.bus.BackButtonEventBusData { *; }
-keep interface com.payfort.fortpaymentsdk.** {*;}
-keep class com.payfort.fortpaymentsdk.** {*;}

@ -163,7 +163,7 @@
android:enabled="true"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleInstance"
android:launchMode="singleTop"
android:showOnLockScreen="true"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize"

@ -1,10 +1,11 @@
import 'package:amazon_payfort/amazon_payfort.dart';
import 'package:flutter_amazonpaymentservices/environment_type.dart';
import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT
@ -23,6 +24,7 @@ class ApiConsts {
static var payFortEnvironment = FortEnvironment.production;
static var applePayMerchantId = "merchant.com.hmgwebservices";
static var payFortEnvironmentType = EnvironmentType.production;
static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx'; //Payfort Payment Gateway URL LIVE
// static String SERVICE_URL = 'https://hmgwebservices.com/PayFortWeb/pages/SendPayFortRequest.aspx'; // Payfort Payment Gateway URL UAT
@ -47,6 +49,7 @@ class ApiConsts {
case AppEnvironmentTypeEnum.prod:
baseUrl = "https://hmgwebservices.com/";
payFortEnvironment = FortEnvironment.production;
payFortEnvironmentType = EnvironmentType.production;
applePayMerchantId = "merchant.com.hmgwebservices";
SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx";
TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout";
@ -62,6 +65,7 @@ class ApiConsts {
case AppEnvironmentTypeEnum.dev:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -78,6 +82,7 @@ class ApiConsts {
case AppEnvironmentTypeEnum.uat:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -94,6 +99,7 @@ class ApiConsts {
case AppEnvironmentTypeEnum.preProd:
baseUrl = "https://webservices.hmg.com/";
payFortEnvironment = FortEnvironment.production;
payFortEnvironmentType = EnvironmentType.production;
applePayMerchantId = "merchant.com.hmgwebservices";
SERVICE_URL = "https://hmgwebservices.com/PayFortWebLive/pages/SendPayFortRequest.aspx";
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -110,6 +116,7 @@ class ApiConsts {
case AppEnvironmentTypeEnum.qa:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
@ -126,6 +133,7 @@ class ApiConsts {
case AppEnvironmentTypeEnum.staging:
baseUrl = "https://uat.hmgwebservices.com/";
payFortEnvironment = FortEnvironment.test;
payFortEnvironmentType = EnvironmentType.sandbox;
applePayMerchantId = "merchant.com.hmgwebservices.uat";
SERVICE_URL = 'https://uat.hmgwebservices.com/HMGPayment/pages/SendPayFortRequest.aspx';
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";

@ -48,9 +48,16 @@ class AppState {
bool isPaytabsEnabled = false;
bool isPayfortHostedPageEnabled = true;
void setIsPaytabsEnabled(bool value) {
isPaytabsEnabled = value;
// isPaytabsEnabled = false;
// isPaytabsEnabled = value;
isPaytabsEnabled = false;
}
void setIsPayfortHostedPageEnabled(bool value) {
isPayfortHostedPageEnabled = value;
// isPayfortHostedPageEnabled = false;
}
int? _superUserID;

@ -1,8 +1,13 @@
import 'dart:convert';
import 'dart:developer';
import 'package:amazon_payfort/amazon_payfort.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.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/dependencies.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_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/models/payfort_check_payment_status_response_model.dart';
@ -12,6 +17,7 @@ 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';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:crypto/crypto.dart';
class PayfortViewModel extends ChangeNotifier {
PayfortRepo payfortRepo;
@ -27,6 +33,8 @@ class PayfortViewModel extends ChangeNotifier {
GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel;
bool isTamaraDetailsLoading = false;
SdkTokenResponse? sdkTokenResponse;
PayfortViewModel({required this.payfortRepo, required this.errorHandlerService});
setIsApplePayConfigurationLoading(bool value) {
@ -142,6 +150,99 @@ class PayfortViewModel extends ChangeNotifier {
);
}
String buildManualPayfortSignature({
required String accessCode,
required String deviceId,
required String merchantIdentifier,
required String amount,
required String currency,
required String merchantReference,
required String shaRequestPhrase, // Your secret Sandbox phrase
}) {
// 1. Map all properties in strict ALPHABETICAL order by native key names
// Notice that "amount" and "currency" must come first alphabetically
final parameters = {
"access_code": accessCode,
"device_id": deviceId,
"language": "en",
"merchant_identifier": merchantIdentifier,
"service_command": "SDK_TOKEN",
};
// 2. Concatenate into a single continuous string wrapped in your SHA Phrase
StringBuffer buffer = StringBuffer();
buffer.write(shaRequestPhrase);
parameters.forEach((key, value) {
buffer.write('$key=$value');
});
buffer.write(shaRequestPhrase);
// 3. Convert string to SHA-256 hex signature
var bytes = utf8.encode(buffer.toString());
return sha256.convert(bytes).toString();
}
Future<SdkTokenResponse?> generateSdkResponseForNormalPayment({
String? accessCode,
String? merchantIdentifier,
String? shaType,
String? shaRequestPhrase,
String? merchantReference,
String? amount,
}) async {
SdkTokenResponse? sdkTokenResponse;
try {
// String? deviceId = await _payfort.getDeviceId();
String? deviceId = await FlutterAmazonpaymentservices.getUDID;
/// Step 2: Generate the Signature
SdkTokenRequest tokenRequest = SdkTokenRequest(
accessCode: accessCode!,
deviceId: deviceId ?? '',
merchantIdentifier: merchantIdentifier!,
);
// String? signature = await _payfort.generateSignature(
// shaType: shaType!,
// concatenatedString: tokenRequest.toConcatenatedString(shaRequestPhrase!),
// );
String? signature = buildManualPayfortSignature(
accessCode: accessCode,
deviceId: deviceId!,
merchantIdentifier: merchantIdentifier,
amount: "10000",
currency: "SAR",
merchantReference: merchantReference!,
shaRequestPhrase: shaRequestPhrase!,
);
tokenRequest = tokenRequest.copyWith(signature: signature);
this.sdkTokenResponse = SdkTokenResponse();
/// Step 3: Generate the SDK Token
final result = await payfortRepo.generateSdkSignatureFromAPI(tokenRequest: tokenRequest);
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) {
// payfortProjectDetailsRespModel = apiResponse.data!;
sdkTokenResponse = apiResponse.data;
this.sdkTokenResponse = sdkTokenResponse;
notifyListeners();
}
},
);
} catch (e) {
log("Error here: ${e.toString()}");
}
return sdkTokenResponse;
}
Future<SdkTokenResponse?> _generateSdkResponse({
String? applePayAccessCode,
String? merchantIdentifier,
@ -187,6 +288,73 @@ class PayfortViewModel extends ChangeNotifier {
return sdkTokenResponse;
}
Future<void> initiatePaymentWithCard( // var request, {
{
SucceededCallback? onSucceeded,
FailedCallback? onFailed,
String countryIsoCode = "SA",
String transactionID = "",
String amount = "0",
String fileNumber = "0",
}) async {
dynamic paymentResult;
try {
await generateSdkResponseForNormalPayment(
accessCode: payfortProjectDetailsRespModel!.accessCode,
merchantIdentifier: payfortProjectDetailsRespModel!.merchantIdentifier,
shaRequestPhrase: payfortProjectDetailsRespModel!.shaRequest,
shaType: "SHA-256",
merchantReference: transactionID,
amount: amount);
// Force a 100ms microtask buffer to isolate the UI rendering channel
await Future.delayed(const Duration(milliseconds: 350));
var request = {
"command": "PURCHASE",
"merchant_reference": transactionID, // Completely alphanumeric testing reference
"amount": (num.parse(amount) * 100).toInt(),
"currency": getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
"language": "en",
"customer_email": "CustID_$fileNumber@HMG.com",
"sdk_token": sdkTokenResponse!.sdkToken,
"customer_name": "${getIt.get<AppState>().getAuthenticatedUser()!.firstName} ${getIt.get<AppState>().getAuthenticatedUser()!.lastName}",
};
LoaderBottomSheet.hideLoader();
paymentResult = await FlutterAmazonpaymentservices.normalPay(request, ApiConsts.payFortEnvironmentType, isShowResponsePage: false);
final paymentMap = Map<String, dynamic>.from(paymentResult);
String responseCode = paymentMap['response_code'] ?? "";
print("Payment Success Data: ${paymentMap.toString()}");
if (responseCode.startsWith("14") || responseCode == "20064") {
if (onSucceeded != null) {
onSucceeded(PayFortResult.fromMap(paymentMap));
}
} else if (responseCode == "13000") {
// Push user to 3D Secure Web View
} else {
// Treat as a payment rejection/technical failure
if (onFailed != null) {
onFailed(PayFortFailureResult.fromMap(paymentMap));
}
}
} on PlatformException catch (e) {
// If the error is the MethodChannel duplicate reply bug, handle it safely
if (e.message != null && e.message!.contains("Reply already submitted")) {
print("Swallowed native channel race-condition crash safely.");
return;
}
// Process real payment rejection errors here
print("Actual Payment Failure: ${e.details.toString()}");
} catch (error) {
print("Generic Error: $error");
}
}
Future<void> paymentWithApplePay({
SucceededCallback? onSucceeded,
FailedCallback? onFailed,

@ -337,6 +337,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
);
});
} else {
openPaymentURL("visa");
}
}),

@ -99,7 +99,7 @@ class LoginScreenState extends State<LoginScreen> {
hintText: "xxxxxxxxx",
controller: authVm.nationalIdController,
focusNode: _nationalIdFocusNode,
keyboardType: TextInputType.text,
keyboardType: TextInputType.number,
isEnable: true,
prefix: null,
autoFocus: true,

@ -2,8 +2,12 @@ import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:amazon_payfort/amazon_payfort.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_amazonpaymentservices/flutter_amazonpaymentservices.dart';
import 'package:hmg_patient_app_new/core/api_consts.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/cache_consts.dart';
@ -26,6 +30,7 @@ import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:network_info_plus/network_info_plus.dart';
import 'package:provider/provider.dart';
class WalletPaymentConfirmPage extends StatefulWidget {
@ -206,7 +211,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
),
],
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
@ -267,7 +272,46 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
);
});
} else {
openPaymentURL("visa");
if (appState.isPayfortHostedPageEnabled) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingPaymentInformation.tr(context: context));
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
await payfortViewModel.getPayfortConfigurations(
serviceId: ServiceTypeEnum.advancePayment.getIdFromServiceEnum(), projectId: habibWalletVM.selectedHospital!.iD!, integrationId: 1);
// await payfortViewModel.generateSdkResponseForNormalPayment(
// accessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
// merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
// shaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
// shaType: "SHA-256",
// merchantReference: transID,
// amount: habibWalletVM.walletRechargeAmount.toString());
// var requestParam = {
// "command": "PURCHASE",
// "merchant_reference": transID, // Completely alphanumeric testing reference
// "amount": (habibWalletVM.walletRechargeAmount * 100).toInt(),
// "currency": appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
// "language": "en",
// "customer_email": "CustID_${habibWalletVM.fileNumber.toString()}@HMG.com",
// "sdk_token": payfortViewModel.sdkTokenResponse!.sdkToken,
// "customer_name": "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// };
payfortViewModel.initiatePaymentWithCard(
transactionID: transID,
fileNumber: habibWalletVM.fileNumber.toString(),
amount: habibWalletVM.walletRechargeAmount.toString(),
onSucceeded: (paymentResult) {
print("Payment Success Data: ${paymentResult.toString()}");
},
onFailed: (err) {
print("Payment Failed Data: ${err.toString()}");
},
);
} else {
openPaymentURL("visa");
}
}
}),
],
@ -626,4 +670,28 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
"",
"3");
}
Future<void> runSafePayment(Map<String, dynamic> requestParam, {String sdkToken = ""}) async {
dynamic result;
try {
// Force a 100ms microtask buffer to isolate the UI rendering channel
await Future.delayed(const Duration(milliseconds: 350));
result = await FlutterAmazonpaymentservices.normalPay(requestParam, ApiConsts.payFortEnvironmentType, isShowResponsePage: true);
print("Payment Success Data: ${result.toString()}");
} on PlatformException catch (e) {
// If the error is the MethodChannel duplicate reply bug, handle it safely
if (e.message != null && e.message!.contains("Reply already submitted")) {
print("Swallowed native channel race-condition crash safely.");
return;
}
// Process real payment rejection errors here
print("Actual Payment Failure: ${e.details.toString()}");
} catch (error) {
print("Generic Error: $error");
}
}
}

@ -18,7 +18,7 @@ import 'package:lottie/lottie.dart';
class AppUpdatePage extends StatelessWidget {
AppUpdatePage({super.key, required this.appUpdateMessage});
String appUpdateMessage;
final String appUpdateMessage;
@override
Widget build(BuildContext context) {
@ -72,21 +72,7 @@ class AppUpdatePage extends StatelessWidget {
openAppUpdateLink() {
if (Platform.isAndroid) {
// _launchURL("https://play.google.com/store/apps/details?id=com.ejada.hmg");
InAppUpdate.checkForUpdate().then((info) {
print("checkForUpdate!!!");
print(info.toString());
if (info.immediateUpdateAllowed) {
print("Immediate Allowed!!!");
InAppUpdate.performImmediateUpdate().then((value) {}).catchError((e) => print(e.toString()));
}
}).catchError((e) {
print(e.toString());
Utils.openWebView(
// url: "https://play.google.com/store/apps/details?id=com.ejada.hmg",
url: "https://play.google.com/store/apps/details?id=com.cloudsolutions.HMGPatientApp",
);
});
_handleAndroidAppUpdate();
}
if (Platform.isIOS) {
Utils.openWebView(
@ -95,4 +81,43 @@ class AppUpdatePage extends StatelessWidget {
);
}
}
Future<void> _handleAndroidAppUpdate() async {
const fallbackUrl = "https://play.google.com/store/apps/details?id=com.cloudsolutions.HMGPatientApp";
try {
final info = await InAppUpdate.checkForUpdate();
print("InAppUpdate.checkForUpdate -> $info");
if (info != null) {
final immediateAllowed = info.immediateUpdateAllowed ?? false;
final flexibleAllowed = info.flexibleUpdateAllowed ?? false;
print("immediateAllowed: $immediateAllowed, flexibleAllowed: $flexibleAllowed");
if (immediateAllowed) {
print("Performing immediate update...");
await InAppUpdate.performImmediateUpdate();
return;
}
if (flexibleAllowed) {
print("Starting flexible update...");
await InAppUpdate.startFlexibleUpdate();
try {
await InAppUpdate.completeFlexibleUpdate();
} catch (e) {
print("Error completing flexible update: $e");
}
return;
}
}
print("No in-app update allowed/available; opening Play Store page");
Utils.openWebView(url: fallbackUrl);
} catch (e, st) {
print("InAppUpdate.checkForUpdate threw an exception: $e");
print(st);
Utils.openWebView(url: fallbackUrl);
}
}
}

@ -37,11 +37,13 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/authentication/login.dart';
import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/app_update_page.dart';
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/lakum_wallet_card.dart';
@ -290,6 +292,16 @@ class _LandingPageState extends State<LandingPage> {
text: LocaleKeys.loginOrRegister.tr(context: context),
onPressed: () async {
await authVM.onLoginPressed();
// Navigator.of(context).push(
// CustomPageRoute(
// // page: NotificationsListPage(),
// page: AppUpdatePage(
// appUpdateMessage:
// "A new update for Dr.Alhabib app is now available with new features and services.\r\nFor technical support:\r\neservices.hmg@drsulaimanalhabib.com"),
// ),
// );
// ...existing commented code...
},
backgroundColor: AppColors.secondaryLightRedColor,

@ -2,7 +2,7 @@ name: hmg_patient_app_new
description: "New HMG Patient App"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 0.0.35+34
version: 0.0.37+36
#version: 0.0.6+3
environment:
@ -106,6 +106,7 @@ dependencies:
flutter_paytabs_bridge: ^2.7.13
clarity_flutter: 1.9.0
flutter_amazonpaymentservices: ^0.0.13
dev_dependencies:
flutter_test:

Loading…
Cancel
Save