using Applications.FDC; using Edge.Core.Processor;using Edge.Core.IndustryStandardInterface.Pump; using Dfs.WayneChina.SinochemEps; using NozzleLockConfiguration; using Edge.Core.Parser.HttpMessageParser; using SinochemCarplateService.Models; using SinochemCloudClient.Models; using SinoChemFC2PosProxy; using SinochemInternetPlusApp.EpsTrxCleanup; using SinochemPosClient.Models; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; using Wayne.FDCPOSLibrary; using Wayne.Lib; using Wayne.Lib.Log; using WayneChina_IcCardReader_SinoChem; using WayneChina_IcCardReader_SinoChem.MessageEntity; using WayneChina_IcCardReader_SinoChem.MessageEntity.Incoming; namespace SinochemInternetPlusApp { /// /// Electronic payment service with WT 30 IC. /// public class Eps : IDisposable { static NLog.Logger logger = NLog.LogManager.LoadConfiguration("nlog.config").GetLogger("SinochemEpsApp"); private IEnumerable> icCardReaderProcessors; private List fuelingPoints; private EpsTrxCleanupManager epsTrxCleanupManager; private int requestId; private object requestSyncObj = new object(); #region devices and services private Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler carPlateServer; private IEnumerable cardReaderHandlers; private CloudManager cloudManager; private PosManager posManger; #endregion private IEnumerable processors; private IEnumerable pumpControllers; private FdcServerHostApp fdcServer; private Dictionary tcpHandlersDict = new Dictionary(); #region constants public int InvalidNozzleId => 9999; #endregion ForecourtTrxManager forecourtTrxManager; #region Constructor public Eps(IEnumerable fpIds, Dictionary> fpNozzlesDict, IEnumerable processors) { var identifiableEntity = new IdentifiableEntity(0, "EpsMain", "", null); fuelingPoints = new List(fpIds.Count()); foreach (int nozzleId in fpIds) { fuelingPoints.Add(new FuelingPoint(nozzleId, fpNozzlesDict[nozzleId], this)); } epsTrxCleanupManager = new EpsTrxCleanupManager(this); cloudManager = new CloudManager(); posManger = new PosManager(); NozzleLockAccessor.FillinNozzles(fpNozzlesDict); this.processors = processors; List pumpControllerList = new List(); foreach (dynamic processor in processors) { if (processor is IAppProcessor) { var fdcServer = processor as FdcServerHostApp; if (fdcServer != null) { this.fdcServer = fdcServer; } continue; } var handler = processor.Context.Handler; if (handler is TcpHandler) { if (!tcpHandlersDict.ContainsKey(handler.DispenserId)) tcpHandlersDict.Add(handler.DispenserId, handler); } else if (handler is IDeviceHandler>) { if (carPlateServer == null) { carPlateServer = handler; } } else if (handler is IFdcPumpController) { pumpControllerList.Add(handler); } else if (handler is IEnumerable) { pumpControllerList.AddRange(handler); } } pumpControllers = pumpControllerList; logger.Info($"There are {pumpControllers.Count()} pumps available for EPS"); forecourtTrxManager = new ForecourtTrxManager(pumpControllers, SinochemEpsApp.PumpSideMapping, SinochemEpsApp.ForceMappingFusionHoseToHuiTianHoseStr, SinochemEpsApp.PosDatabaseConnString, SinochemEpsApp.RawProductNameToPosProductNameStr); forecourtTrxManager.Init(); logger.Info("Eps instance has been constructed"); } internal TrxNotificationResponse NotifySuccessfulTrxToPos(EpsTransactionModel model, DebugLogger debugLogger) { return posManger.NotifyPosSuccessfulTrx(model, debugLogger); } internal PaymentResponse SendPaymentToCloud(EpsTransactionModel currentEpsTrxModel, DebugLogger debugLogger) { return cloudManager.Payment(currentEpsTrxModel, debugLogger); } internal BalanceInquiryResponse SendBalanceInquiryToCloud(string cardNo, string encryptedPin, string tid, int nozzleId, DebugLogger debugLogger) { return cloudManager.BalanceInquiry(cardNo, encryptedPin, tid, nozzleId, debugLogger); } #endregion public void Run() { SetupICCardReaderHandler(); if (fuelingPoints != null) { foreach (var fp in fuelingPoints) { fp.Start(); } } epsTrxCleanupManager.Start(); SetupCarplateServer(); SetupFccClient(); } private void SetupFccClient() { foreach (var pumpController in pumpControllers) { pumpController.OnStateChange += (s, a) => { var pump = s as IFdcPumpController; if (a.NewPumpState == LogicalDeviceState.FDC_CALLING) { logger.Info($"Pump {pump.PumpId} at state CALLING"); int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0); FccClient_NozzleLifted(pump.PumpId, pump); //FccClient_NozzleLifted(sitewiseNozzleId, pump); } else if (a.NewPumpState == LogicalDeviceState.FDC_READY) { int sitewiseNozzleId = 0; if (a.StateChangedNozzles != null) { SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0); } else { logger.Info("StateChangeNozzles null, use sitewiseNozzleId 0"); } logger.Info($"Pump {pump.PumpId} at state: FDC_READY, siteWiseNozzleId: {sitewiseNozzleId}"); FccClient_NozzleReplaced(pump.PumpId); //FccClient_NozzleReplaced(sitewiseNozzleId); } else if (a.NewPumpState == LogicalDeviceState.FDC_AUTHORISED) { int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.StateChangedNozzles.FirstOrDefault()?.LogicalId ?? 0); logger.Info($"Pump {pump.PumpId} at state FDC_AUTHORISED, siteWiseNozzleId: {sitewiseNozzleId}"); FccClient_AuthOk(pump.PumpId, 1999); //FccClient_AuthOk(sitewiseNozzleId, 1999); } else if (a.NewPumpState == LogicalDeviceState.FDC_FUELLING) { logger.Info($"Pump {pump.PumpId} is fueling"); } }; pumpController.OnCurrentFuellingStatusChange += async (s, a) => { var pump = s as IFdcPumpController; if (a.Transaction.Finished) { int sitewiseNozzleId = SiteConfigUtility.Default.GetSiteLevelNozzleIdByLogicalNozzleId(pump.PumpId, a.Transaction.Nozzle.LogicalId); logger.Info($"Pump {pump.PumpId} fueling finished, siteWiseNozzleId: {sitewiseNozzleId}"); FccClient_NozzleReplaced(pump.PumpId); //Give ForecourtTrxManager some time to insert filling transaction to xiaofei2. await Task.Delay(500); //Note: amount and volume are integer, directly from FC instead of Global FC. FccClient_FuelingDone(pump.PumpId, a.Transaction.SequenceNumberGeneratedOnPhysicalPump, a.Transaction.Amount, a.Transaction.Volumn, 1999); } }; } } // find car plate handler and listen to the new car plate event private void SetupCarplateServer() { if (carPlateServer != null) { carPlateServer.NewCarPlateScanned += CarPlateServer_NewCarPlateScanned; carPlateServer.OnMessageReceivedViaFdc += (msg) => { logger.Debug($"fdc message:\n {msg}"); if (msg.Length > 10 && msg.StartsWith("("DisplayResponse ACK", OverallResult.Success); }; } else { logger.Info("carPlateServer not found!!!"); } } private void CarPlateServer_NewCarPlateScanned(CarPlateTrxRequest request) { int sitewiseNozzleId = InvalidNozzleId; try { sitewiseNozzleId = int.Parse(request.gun); } catch (Exception ex) { logger.Error(ex); } if (IsNozzleConfiguredOpen(sitewiseNozzleId)) { var fp = GetFp(sitewiseNozzleId); fp?.SignalNewCarplate(request); } else { logger.Info($"Physical nozzle# {sitewiseNozzleId} is closed, so we ignore the car plate event!!!"); } } private void SetupICCardReaderHandler() { cardReaderHandlers = tcpHandlersDict.Values; if (cardReaderHandlers != null && cardReaderHandlers.Count() > 0) { foreach (var handler in cardReaderHandlers) { handler.OnCardReaderMessageReceived += Handler_OnCardReaderMessageReceived; } } else { logger.Info("card reader handlers not found!!"); } } private void Handler_OnCardReaderMessageReceived(object sender, CardReaderMessageEventArgs e) { logger.Info($"Receiving Card Reader message {e.CardReaderMessage.GetType().ToString()} for FP id = {e.FuelingPointId}"); //Heart beat messages are always from address 0x0F. if (e.CardReaderMessage.SourceAddress == 0x0F) { var currentHandler = sender as TcpHandler; if (currentHandler != null) { logger.Info($"Heart beat from dispenser of id: {currentHandler.DispenserId}"); currentHandler.Write(new EOT { RawMessageSeqNumberAndSourceAddress = 0x0F }); //GetHandler(1).Write(new EOT { RawMessageSeqNumberAndSourceAddress = 0x0F }); return; } } var targetFP = fuelingPoints.First(_ => _.FuelingPointId == e.FuelingPointId); if (e.CardReaderMessage is ACK) { targetFP?.SingalAckReceived((ACK)e.CardReaderMessage); } else if (e.CardReaderMessage is SignDataResponse) { targetFP?.SignalSignedDataArrived((SignDataResponse)e.CardReaderMessage); } else if (e.CardReaderMessage is CardReaderStateEvent) { targetFP?.SingalCardReaderStateEvent((CardReaderStateEvent)e.CardReaderMessage); } else if (e.CardReaderMessage is CardExternalCheckErrorRequest) { targetFP?.SignalCardExternalCheckFailure((CardExternalCheckErrorRequest)e.CardReaderMessage); } else if (e.CardReaderMessage is CardOnlineVerificationRequest) { targetFP?.SignalCardOnlineVerification((CardOnlineVerificationRequest)e.CardReaderMessage); } //else if (e.CardReaderMessage is HeartBeat) //{ // targetFP?.SignalCardReaderHeartbeat((HeartBeat)e.CardReaderMessage); //} } #region Cloud interactions internal RefundResponse SendRefundToCloud(EpsTransactionModel trxModel, DebugLogger debugLogger) { return cloudManager.Refund(trxModel, debugLogger); } internal TrxStatusInquiryResponse SendTrxQueryToCloud(EpsTransactionModel trxModel, DebugLogger debugLogger) { return cloudManager.TrxStatusInquiry(trxModel, debugLogger); } #endregion #region FDC client interactions private void FccClient_NozzleReplaced(int sitewiseNozzleId) { var fp = GetFp(sitewiseNozzleId); fp?.SignalNozzleReplaced(sitewiseNozzleId); } private void FccClient_AuthFailed(int sitewiseNozzleId) { var fp = GetFp(sitewiseNozzleId); fp?.SignalAuthFailed(); } private void FccClient_AuthOk(int sitewiseNozzleId, long? authId) { var fp = GetFp(sitewiseNozzleId); fp?.SignalAuthOk(authId); } private void FccClient_NozzleLifted(int sitewiseNozzleId, IFdcPumpController callingPump) { if (IsNozzleConfiguredOpen(sitewiseNozzleId)) { var fp = GetFp(sitewiseNozzleId); fp?.SignalNozzleLifted(callingPump, sitewiseNozzleId); } else { logger.Info(string.Format($"Physical nozzle# {sitewiseNozzleId} is closed, so we ignore the nozzle lift event!!!")); } } private bool IsNozzleConfiguredOpen(int sitewiseNozzleId) { return !NozzleLockAccessor.IsNozzleClosedInTermsOfSale(sitewiseNozzleId); } private void FccClient_FuelingDone(int sitewiseNozzleId, int seqNum, decimal fuelAmount, decimal fuelingQty, long authId) { var fp = GetFp(sitewiseNozzleId); logger.Info($"Fueling point is null? {fp == null}"); fp?.SignalFuelingDone(sitewiseNozzleId, seqNum, Convert.ToDecimal(fuelAmount)/100, Convert.ToDecimal(fuelingQty) / 100, authId); } public void AuthorizePumpAsync(IFdcPumpController callingPump, int sitewiseNozzleId, decimal authAmount) { //Have to put pump auth in a different thread due to FC reasons, 2019/06/19 Task.Run(() => { callingPump.AuthorizeWithAmountAsync((int)((double)authAmount * Math.Pow(10, callingPump.AmountDecimalDigits)), 1); }); } #endregion private void TeardownFccClient() { //if (fccClient != null) //{ // fccClient.NozzleLifted -= FccClient_NozzleLifted; // fccClient.NozzleReplaced -= FccClient_NozzleReplaced; // fccClient.AuthOk -= FccClient_AuthOk; // fccClient.AuthFailed -= FccClient_AuthFailed; // fccClient.FuelingDone -= FccClient_FuelingDone; //} } public void Shutdown() { TeardownCarplateServer(); TeardownCardReaderHandler(); if (fuelingPoints != null) { foreach (var fp in fuelingPoints) { fp.SignalShutdown(); } } } public WayneChina_IcCardReader_SinoChem.TcpHandler GetHandler(int nozzleId) { logger.Info($"Trying to get card reader handler for fueling point: {nozzleId}"); foreach (var handler in cardReaderHandlers) { if (handler.SupportedNozzles.Contains(nozzleId)) return handler; } return null; } private void TeardownCarplateServer() { if (carPlateServer != null) { carPlateServer.NewCarPlateScanned -= CarPlateServer_NewCarPlateScanned; var server = carPlateServer as IDisposable; if (server != null) server.Dispose(); } } private void TeardownCardReaderHandler() { if (cardReaderHandlers != null) { foreach (var handler in cardReaderHandlers) { handler.OnCardReaderMessageReceived -= Handler_OnCardReaderMessageReceived; } } } public Sinochem_CarPlateRecognizeCamera_HuLianWangJia.Handler CarPlateHandler => carPlateServer; public TcpHandler GetCardReader(int nozzleId) { return cardReaderHandlers.FirstOrDefault(h => h.SupportedNozzles.Contains(nozzleId)); } public FuelingPoint GetFp(int nozzleId) { foreach (var fp in fuelingPoints) { if (fp.AssociatedNozzles != null && fp.AssociatedNozzles.Contains(nozzleId)) return fp; } return null; } public FuelingPoint GetFuelingPoint(int fpId) { return fuelingPoints?.FirstOrDefault(_ => fpId == _.FuelingPointId); } public int GetNextRequestId() { lock (requestSyncObj) { if (requestId == int.MaxValue) { requestId = 0; } return requestId++; } } public string CreateDisplayTrxCommand(IEnumerable epsTrxList, out int requestId, int i) { Display display; string cmdText = ""; XmlSerializer serializer = new XmlSerializer(typeof(Display)); MemoryStream ms; StreamReader sr; display = new Display(); display.ScreenType = ScreenType.ShowTrxList; display.RequestId = GetNextRequestId(); display.TimeoutSpecified = true; display.Timeout = 300; requestId = display.RequestId; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; int count = epsTrxList.Count(); display.TrxList = new DisplayTrx[epsTrxList.Count()]; for (int index = 0; index < count; index++) { display.TrxList[index] = ConvertEpsTrxModelToDisplayTrx(epsTrxList.ToArray()[index]); } ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); ms.Close(); sr.Close(); return cmdText; } public DisplayTrx ConvertEpsTrxModelToDisplayTrx(EpsTransactionModel epsTrxModel) { TrxStatus state = TrxStatus.ReadyForFillingStart; if (epsTrxModel.trx_status == EpsTrxStatus.BeforeFueling) state = TrxStatus.ReadyForFillingStart; else if (epsTrxModel.trx_status == EpsTrxStatus.BeforePayment) state = TrxStatus.PendingForPayment; else if (epsTrxModel.trx_status == EpsTrxStatus.Fueling) state = TrxStatus.FillingOngoing; else if (epsTrxModel.trx_status == EpsTrxStatus.FuelingDone) state = TrxStatus.PendingForPayment; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentOk) state = TrxStatus.Success; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentFailed) state = TrxStatus.Failed; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentNeedConfirm) state = TrxStatus.Failed; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentOkButNeedRefund) state = TrxStatus.Failed; else if (epsTrxModel.trx_status == EpsTrxStatus.PaymentRefunded) state = TrxStatus.Failed; var displayTrx = new DisplayTrx { Id = epsTrxModel.id.ToString(), State = state, TimeStamp = new DisplayTrxTimeStamp { StartTime = epsTrxModel.created_time, FinishTime = string.IsNullOrEmpty(epsTrxModel.xf_time) ? epsTrxModel.created_time : Utilities.CombineDateAndTime(epsTrxModel.xf_date, epsTrxModel.xf_time) }, MemberInfo = new DisplayTrxMemberInfo { Id = epsTrxModel.cardNo_masked, LicensePlateNo = epsTrxModel.car_number }, FillingInfo = new DisplayTrxFillingInfo { PumpId = 1, NozzleId = epsTrxModel.jihao, ProductNo = 1, UnitPrice = (decimal)epsTrxModel.danjia, ProductType = epsTrxModel.youpin, ProductDiscription = "汽油", Amount = (decimal)epsTrxModel.real_pay_amount, Volume = (decimal)epsTrxModel.qty, } }; return displayTrx; } public string CreateDisplayCommand(ScreenType screenType) { Display display; string cmdText = ""; XmlSerializer serializer = new XmlSerializer(typeof(Display)); MemoryStream ms; StreamReader sr; switch (screenType) { case ScreenType.Idle: display = new Display(); display.ScreenType = screenType; display.CompanyContactInfo = new DisplayCompanyContactInfo { Tel = "010-59569575", Address = "世界500强企业\n中国第四大国家石油公司" }; display.StationInfo = new DisplayStationInfo { StationNo = "000001", StationName = "沈阳望花中街加油加气站" }; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; case ScreenType.Welcome: display = new Display(); display.ScreenType = screenType; display.CompanyContactInfo = new DisplayCompanyContactInfo { Tel = "010-59569575", Address = "世界500强企业\n中国第四大国家石油公司" }; display.MemberInfo = new DisplayMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; case ScreenType.ShowTrxList: display = new Display(); display.ScreenType = screenType; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; display.TrxList = new DisplayTrx[] { new DisplayTrx { Id = "100", FillingInfo = new DisplayTrxFillingInfo { Amount = 200, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "101", FillingInfo = new DisplayTrxFillingInfo { Amount = 211, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "102", FillingInfo = new DisplayTrxFillingInfo { Amount = 222, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "103", FillingInfo = new DisplayTrxFillingInfo { Amount = 233, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } }, new DisplayTrx { Id = "104", FillingInfo = new DisplayTrxFillingInfo { Amount = 244, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } } }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; case ScreenType.TrxResult: display = new Display(); display.ScreenType = screenType; display.PumpInfo = new DisplayPumpInfo { Id = 1, NozzleId = 1 }; display.TrxList = new DisplayTrx[] { new DisplayTrx { Id = "100", FillingInfo = new DisplayTrxFillingInfo { Amount = 200, NozzleId = 1, ProductType = "95#", UnitPrice = 6.78m, Volume = 35, ProductDiscription = "汽油" }, MemberInfo = new DisplayTrxMemberInfo { LicensePlateNo = "京A88888", Id = "1234567" }, State = TrxStatus.Success, TimeStamp = new DisplayTrxTimeStamp { StartTime = DateTime.Now, FinishTime =DateTime.Now } } }; ms = new MemoryStream(); serializer.Serialize(ms, display); ms.Position = 0; sr = new StreamReader(ms, true); cmdText = sr.ReadToEnd(); break; default: throw new Exception("Unsupported screen type"); } return cmdText; } #region IDisposable Support private bool disposedValue = false; // To detect redundant calls protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). if (fuelingPoints != null) { foreach (var fp in fuelingPoints) { fp.Dispose(); } } if (epsTrxCleanupManager != null) { epsTrxCleanupManager.Dispose(); } } // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. // TODO: set large fields to null. disposedValue = true; } } // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. // ~Eps() { // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. // Dispose(false); // } // This code added to correctly implement the disposable pattern. public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); // TODO: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } #endregion } }