@ -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 100 ms 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 3 D 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 ,